diff --git a/config/alfresco/web-client-config-wcm.xml b/config/alfresco/web-client-config-wcm.xml index c9e0e71651..6d5635602e 100644 --- a/config/alfresco/web-client-config-wcm.xml +++ b/config/alfresco/web-client-config-wcm.xml @@ -86,25 +86,21 @@ javascript-class-name="alfresco.xforms.FilePicker"> wcm:avmcontent,wcm:avmfolder - uploads = new HashSet(); - - // property instance variables - private AVMBrowseBean avmBrowseBean; - transient private AVMService avmService; - transient private NamespaceService namespaceService; - transient private SearchService searchService; - transient private NodeService nodeService; - transient private DictionaryService dictionaryService; - transient private ContentService contentService; - - /** cached reference to the global saved searches folder */ - private NodeRef globalSearchesRef = null; - - /** cached reference to the current users saved searches folder */ - private NodeRef userSearchesRef = null; - - public FilePickerBean() { - } - - public void clearUploadedFiles() { - this.uploads.clear(); - } - - public NodeRef[] getUploadedFiles() { - return (NodeRef[]) this.uploads.toArray(new NodeRef[this.uploads.size()]); - } - - /** - * Set avmBrowseBean property for this bean - * - * @param avmBrowseBean - * the AVMBrowseBean object to pass into this property - */ - public void setAvmBrowseBean(final AVMBrowseBean avmBrowseBean) { - this.avmBrowseBean = avmBrowseBean; - } - - /** - * Get avmBrowseBean property for this bean - * - * @return avmBrowseBean property value for this bean - */ - public AVMBrowseBean getAvmBrowseBean() { - return this.avmBrowseBean; - } - - /** - * Set avmService property for this bean - * - * @param avmService - * the avmService object to pass into this property - */ - public void setAvmService(final AVMService avmService) { - this.avmService = avmService; - } - - /** - * Get avmService property for this bean - * - * @return avmService property value for this bean - */ - public AVMService getAvmService() { - if (this.avmService == null) { - this.avmService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getAVMService(); - } - return this.avmService; - } - - /** - * Set nodeService property value for this bean - * - * @param nodeService - * the NodeService object to pass into this property - */ - public void setNodeService(final NodeService nodeService) { - this.nodeService = nodeService; - } - - /** - * Get nodeService property for this bean - * - * @return nodeService property value for this bean - */ - public NodeService getNodeService() { - if (this.nodeService == null) { - this.nodeService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getNodeService(); - } - return this.nodeService; - } - - /** - * Set searchService property for this bean - * - * @param searchService - * the SearchService object to pass into this property - */ - public void setSearchService(final SearchService searchService) { - this.searchService = searchService; - } - - /** - * Get SearchService property for this bean - * - * @return searchService property value for this bean - */ - public SearchService getSearchService() { - if (this.searchService == null) { - this.searchService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getSearchService(); - } - return this.searchService; - } - - /** - * Set dictionaryService property value for this bean - * - * @param dictionaryService - * the DictionaryService object to pass into this property - */ - public void setDictionaryService(final DictionaryService dictionaryService) { - this.dictionaryService = dictionaryService; - } - - /** - * Get dictionaryService property for this bean - * - * @return dictionaryService property value for this bean - */ - public DictionaryService getDictionaryService() { - if (this.dictionaryService == null) { - this.dictionaryService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getDictionaryService(); - } - return this.dictionaryService; - } - - /** - * Set namespaceService property for this bean - * - * @param namespaceService - * the NamespaceService object to pass into this property - */ - public void setNamespaceService(final NamespaceService namespaceService) { - this.namespaceService = namespaceService; - } - - /** - * Get the namespaceService property value for this bean - * - * @return namespaceService property value for this bean - */ - public NamespaceService getNamespaceService() { - if (this.namespaceService == null) { - this.namespaceService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getNamespaceService(); - } - return this.namespaceService; - } - - /** - * Set contentService property - * - * @param contentService - * the ContentService object to pass into this property - */ - public void setContentService(final ContentService contentService) { - this.contentService = contentService; - } - - /** - * Get contentService property value for this bean - * - * @return contentService property value for this bean - */ - public ContentService getContentService() { - if (this.contentService == null) { - this.contentService = Repository.getServiceRegistry( - FacesContext.getCurrentInstance()).getContentService(); - } - return this.contentService; - } - - /** - * Provides data for a file picker widget. - */ - @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_XML) - public void getFilePickerData() throws Exception { - final FacesContext facesContext = FacesContext.getCurrentInstance(); - final ExternalContext externalContext = facesContext.getExternalContext(); - - String currentPath = null; - - // get 'folderRestriction' parameter value - // - expecting an absolute AVM folder path to be held in this parameter - String folderPathRestriction = null; - String[] folderPathRestrictionParam = (String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_FOLDER_RESTRICTION); - if ((folderPathRestrictionParam != null) - && (folderPathRestrictionParam.length != 0)) { - folderPathRestriction = folderPathRestrictionParam[0]; - } - - // if folder restriction path is neither null nor an empty string, then - // assign it to current path - if ((folderPathRestriction != null) - && (folderPathRestriction.length() > 0)) { - currentPath = folderPathRestriction; - } - // else set current path to current path parameter - else { - String currentPathParam = (String) externalContext - .getRequestParameterMap().get(PARAM_CURRENT_PATH); - - // if current path parameter null then set current path to the current - // AVM path - if ((currentPathParam == null)) { - currentPath = this.getCurrentAVMPath(); - } - // else set current path to value help in current path - // parameter (converted to absolute AVM path) - else { - final String previewStorePath = AVMUtil - .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); - currentPath = AVMUtil.buildPath(previewStorePath, currentPathParam, - AVMUtil.PathRelation.WEBAPP_RELATIVE); - } - } - - // get savedSearchName parameter value - String savedSearchName = null; - String[] savedSearchNameParam = (String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_SAVED_SEARCH_NAME); - if ((savedSearchNameParam != null) && (savedSearchNameParam.length != 0)) { - savedSearchName = savedSearchNameParam[0]; - } - - // get savedSearchContext parameter value - String savedSearchContext = null; - String[] savedSearchContextParam = (String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_SAVED_SEARCH_CONTEXT); - if ((savedSearchContextParam != null) - && (savedSearchContextParam.length != 0)) { - savedSearchContext = savedSearchContextParam[0]; - } - - // get selectableTypes parameter value - final QName[] selectableTypes = this - .getSelectableTypes((String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_SELECTABLE_TYPES)); - - // / get filterMimetypes parameter value - final Pattern[] filterMimetypes = this - .getFilterMimetypes((String[]) externalContext - .getRequestParameterValuesMap().get(PARAM_FILTER_MIME_TYPES)); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(this + ".getFilePickerData(path = " + currentPath - + ", selectableTypes = [" - + StringUtils.join(selectableTypes, ",") - + "], filterMimetypes = [" - + StringUtils.join(filterMimetypes, ",") + "])"); - } - - // create file picker data XML document to return - // and append file picker data element to it - final org.w3c.dom.Document filePickerDataDoc = XMLUtil.newDocument(); - final org.w3c.dom.Element filePickerDataElement = filePickerDataDoc - .createElement("file-picker-data"); - filePickerDataDoc.appendChild(filePickerDataElement); - - // make sure that there is a node associated with current path - // if not, set an applicable error message as an attribute on - // the file picker data element - final AVMNodeDescriptor currentNode = this.getAvmService().lookup(-1, - currentPath); - if (currentNode == null) { - currentPath = AVMUtil.getWebappRelativePath(currentPath); - - filePickerDataElement.setAttribute("error", MessageFormat.format( - Application.getMessage(facesContext, "error_not_found"), - currentPath.substring(currentPath.lastIndexOf("/") + 1, - currentPath.length()), - (currentPath.lastIndexOf("/") == 0 ? "/" : currentPath - .substring(0, currentPath.lastIndexOf("/"))))); - - // TODO (Glen): Ariel's code - What on earth is this doing here? - // It is overriding the value assigned two statements above!!! - currentPath = this.getCurrentAVMPath(); - } - // if the path points to a file instead of a directory, - // then make sure that the current path points to - // just the directory part of the path - else if (!currentNode.isDirectory()) { - currentPath = AVMNodeConverter.SplitBase(currentPath)[0]; - } - - // create current-node element representing node for current path - // and append it to the file picker data element - org.w3c.dom.Element currentNodeElement = filePickerDataDoc - .createElement("current-node"); - currentNodeElement.setAttribute("avmPath", currentPath); - currentNodeElement.setAttribute("webappRelativePath", AVMUtil - .getWebappRelativePath(currentPath)); - currentNodeElement.setAttribute("type", "directory"); - - // TODO (Glen): This was in Ariel's code. Is this the correct - // image to set? - currentNodeElement.setAttribute("image", "/images/icons/space_small.gif"); - - filePickerDataElement.appendChild(currentNodeElement); - - // if saved search name and saved search context parameters supplied (i.e. - // not null), - // then get saved saved search node matching given name and context - if ((savedSearchName != null) && (savedSearchName.length() != 0) - && (savedSearchContext != null) - && (savedSearchContext.length() != 0)) { - // get node reference for named saved search in the given saved search - // context - NodeRef savedSearchNodeRef = getSavedSearches(savedSearchName, - savedSearchContext); - - // run search to get content nodes returned in search result - List searchResultNodes = runSavedSearch(savedSearchNodeRef); - - // add elements for content nodes from search results as child nodes - // of the file picker data element. - addAVMNodesToElement(filePickerDataDoc, filePickerDataElement, - searchResultNodes, selectableTypes, facesContext); - } else { - // add elements for child nodes of current path to file picker - // data element - addPathChildNodesToElement(filePickerDataDoc, filePickerDataElement, - currentPath, selectableTypes, filterMimetypes, facesContext); - } - - final ResponseWriter out = facesContext.getResponseWriter(); - XMLUtil.print(filePickerDataDoc, out); - } - - @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 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; - String returnPage = null; - InputStream fileInputStream = null; - for (FileItem item : fileItems) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("item = " + item); - } - if (item.isFormField() && item.getFieldName().equals("upload-id")) { - uploadId = item.getString(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("uploadId is " + uploadId); - } - } - if (item.isFormField() && item.getFieldName().equals("return-page")) { - returnPage = item.getString(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("returnPage is " + returnPage); - } - } else if (item.isFormField() - && item.getFieldName().equals("currentPath")) { - final String previewStorePath = AVMUtil - .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); - currentPath = AVMUtil.buildPath(previewStorePath, item.getString(), - AVMUtil.PathRelation.WEBAPP_RELATIVE); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("currentPath is " + currentPath); - } - } else { - filename = FilenameUtils.getName(item.getName()); - fileInputStream = item.getInputStream(); - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("uploading file " + filename); - } - } - } - - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("saving file " + filename + " to " + currentPath); - } - - try { - FileCopyUtils.copy(fileInputStream, this.getAvmService().createFile( - currentPath, filename)); - final Map props = new HashMap( - 1, 1.0f); - props.put(ContentModel.PROP_TITLE, new PropertyValue( - DataTypeDefinition.TEXT, filename)); - // props.put(ContentModel.PROP_DESCRIPTION, - // new PropertyValue(DataTypeDefinition.TEXT, - // "Uploaded for form " + this.xformsSession.getForm().getName())); - this.getAvmService().setNodeProperties(currentPath + "/" + filename, - props); - this.getAvmService().addAspect(currentPath + "/" + filename, - ContentModel.ASPECT_TITLED); - - this.uploads.add(AVMNodeConverter.ToNodeRef(-1, currentPath + "/" - + filename)); - returnPage = returnPage.replace("${_FILE_TYPE_IMAGE}", Utils - .getFileTypeImage(facesContext, filename, true)); - } catch (Exception e) { - LOGGER.debug(e.getMessage(), e); - returnPage = returnPage.replace("${_UPLOAD_ERROR}", e.getMessage()); - } - - LOGGER.debug("upload complete. sending response: " + returnPage); - final org.w3c.dom.Document result = XMLUtil.newDocument(); - final org.w3c.dom.Element htmlEl = result.createElement("html"); - result.appendChild(htmlEl); - final org.w3c.dom.Element bodyEl = result.createElement("body"); - htmlEl.appendChild(bodyEl); - - final org.w3c.dom.Element scriptEl = result.createElement("script"); - bodyEl.appendChild(scriptEl); - scriptEl.setAttribute("type", "text/javascript"); - final org.w3c.dom.Node scriptText = result.createTextNode(returnPage); - scriptEl.appendChild(scriptText); - - final ResponseWriter out = facesContext.getResponseWriter(); - XMLUtil.print(result, out); - } - - private String getCurrentAVMPath() { - final AVMNode node = this.getAvmBrowseBean().getAvmActionNode(); - if (node == null) { - return this.getAvmBrowseBean().getCurrentPath(); - } - - final String result = node.getPath(); - return node.isDirectory() ? result - : AVMNodeConverter.SplitBase(result)[0]; - } - - private QName[] getSelectableTypes(final String[] selectableTypes) { - final QName[] result = (selectableTypes == null ? new QName[] { - WCMModel.TYPE_AVM_CONTENT, WCMModel.TYPE_AVM_FOLDER } - : new QName[selectableTypes.length]); - - if (selectableTypes != null) { - for (int i = 0; i < selectableTypes.length; i++) { - result[i] = QName.resolveToQName(this.getNamespaceService(), - selectableTypes[i]); - } - } - return result; - } - - private Pattern[] getFilterMimetypes(final String[] filterMimetypes) { - final Pattern[] result = filterMimetypes == null ? new Pattern[0] - : new Pattern[filterMimetypes.length]; - if (filterMimetypes != null) { - for (int i = 0; i < filterMimetypes.length; i++) { - result[i] = Pattern.compile(filterMimetypes[i].replaceAll("\\*", - "\\.*").replaceAll("\\/", "\\\\/")); - } - } - return result; - } - - /** - * Add child nodes of supplied path to given parent element. Directory - * listing is done on given path. Elements representing the child nodes - * returned in the directory listing are added to the supplied parent - * element. - * - * @param doc - * XML document to which the parent node belongs - * @param parent - * parent element to add given nodes to as child elements - * @param path - * path from which to extract child nodes - * @param selectableTypes - * array of types which are the only ones that should be selectable - * in the file picker - * @param filterMimetypes - * array of MIME type patterns used to filter out child nodes - * extracted from the given path which don't match the given MIME - * type patterns - * @param facesContext - * faces context used to set image attribute on each child element - */ - private void addPathChildNodesToElement(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, String path, QName[] selectableTypes, - Pattern[] filterMimetypes, FacesContext facesContext) { - // append elements for the child AVM nodes of the current path - // to parent element - for (final Map.Entry entry : this - .getAvmService().getDirectoryListing(-1, path).entrySet()) { - // if AVM node is a content node and the filter MIME types parameter - // has been set, then only add child element for AVM node if it matches - // one of the specified MIME types in the parameter - if (!entry.getValue().isDirectory() && filterMimetypes.length != 0) { - final String contentMimetype = this.getAvmService() - .getContentDataForRead(entry.getValue()).getMimetype(); - - boolean matched = false; - for (final Pattern p : filterMimetypes) { - matched = p.matcher(contentMimetype).matches(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(p + ".matches(" + contentMimetype + ") = " - + matched); - } - if (matched) { - break; - } - } - - // if AVM node MIME type doesn't match any of the types in the - // filter MIME types parameter, then don't do any further processing - // on it and jump back to the start of the AVM node traversal loop - if (!matched) { - continue; - } - } - - // create child element representing AVM node and add to file picker - // data element - addAVMChildNodeToParentElement(doc, parent, entry.getValue(), - selectableTypes, facesContext); - } - } - - /** - * Add provided AVM node descriptors as child node elements to the provided - * parent element - * - * @param doc - * XML document to which the parent node belongs - * @param parent - * parent element to add given nodes as child elements - * @param nodes - * nodes which to add as child elements to parent elements - * @param selectableTypes - * AVM node types which must be marked as selectable - * @param facesContext - * faces context used to set image attribute on each child element - */ - private void addAVMNodesToElement(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, List nodes, - QName[] selectableTypes, FacesContext facesContext) { - for (AVMNodeDescriptor node : nodes) { - // create child element representing AVM node and add to file picker - // data element - addAVMChildNodeToParentElement(doc, parent, node, selectableTypes, - facesContext); - } - } - - /** - * Run a saved search represented by the given node reference, against the - * web project the XForm is currently within - * - * @param savedQueryNodRef - * NodeRef of the saved query with which to run the search - * - * @return content nodes returned by the search - */ - private List runSavedSearch(NodeRef savedSearchNodeRef) { - // get the store id used to run the saved search query - WebProject webProject = this.getAvmBrowseBean().getWebProject(); - String storeID = webProject.getStoreId(); - - // extract the content of saved search node to XML document - ContentReader contentReader = getContentService().getReader( - savedSearchNodeRef, ContentModel.PROP_CONTENT); - InputStream queryInpStream = contentReader.getContentInputStream(); - SAXReader reader = new SAXReader(); - Document savedQueryDoc = null; - try { - savedQueryDoc = reader.read(queryInpStream); - } catch (DocumentException de) { - // ignore exception and return null - return null; - } - - // extract search query from saved search XML document - String query = null; - XPath queryXPath = DocumentHelper.createXPath(SAVED_SEARCH_QUERY_XPATH); - List xpathResult = queryXPath.selectNodes(savedQueryDoc); - if ((xpathResult != null) && (xpathResult.size() != 0)) { - // get the text from the query element - Element queryElement = (Element) xpathResult.get(0); - String queryElemText = queryElement.getText(); - - // now extract the actual search query string from the CDATA section - // within that text - - int cdataStartDelimIndex = queryElemText.indexOf(CDATA_START_DELIM); - int cdataEndDelimIndex = queryElemText.indexOf(CDATA_END_DELIM); - - // if the CDATA start delimiter is found in the query element text - // && there is text between the CDATA start and end delimiters then - // extract - // the query string from the CDATA section - if ((cdataStartDelimIndex > -1) - && ((cdataStartDelimIndex + CDATA_START_DELIM.length()) < cdataEndDelimIndex)) { - query = queryElemText.substring(cdataStartDelimIndex - + CDATA_START_DELIM.length(), cdataEndDelimIndex); - } - } - - // perform the search against the repository - // if query was extracted from the saved search successfully - // (extracted query non-null) - List resultNodeDescriptors = null; - if ((query != null) && (query.length() != 0)) { - ResultSet results = null; - try { - results = this.getSearchService().query( - new StoreRef(StoreRef.PROTOCOL_AVM, storeID), - SearchService.LANGUAGE_LUCENE, query); - - if (results.length() != 0) { - resultNodeDescriptors = new ArrayList(); - for (int i = 0; i < results.length(); i++) { - ResultSetRow row = results.getRow(i); - NodeRef resultNodeRef = row.getNodeRef(); - Node resultNode = new Node(resultNodeRef); - - // only add content type nodes to the search result - // as we don't want the user to navigate down into folders - // in the search results - if (getDictionaryService().isSubClass(resultNode.getType(), - ContentModel.TYPE_CONTENT)) { - Pair pair = AVMNodeConverter - .ToAVMVersionPath(resultNodeRef); - Integer version = pair.getFirst(); - String path = pair.getSecond(); - resultNodeDescriptors.add(getAvmService().lookup(version, - path)); - } - } - } - } catch (Throwable err) { - throw new AlfrescoRuntimeException("Failed to execute search: " - + query, err); - } finally { - if (results != null) { - results.close(); - } - } - } - - return resultNodeDescriptors; - } - - /** - * Get the cached reference to the current user's saved searches folder. This - * method will first get a reference to the current user's saved searches - * folder and assign it to the cached reference if is it is null. - * - * @return the cached reference to the current user's Saved Searches folder - */ - private NodeRef getUserSearchesRef() { - // if the cached reference is null, then get a reference to the - // user's saved searches folder to assign to it - if (userSearchesRef == null) { - NodeRef globalRef = getGlobalSearchesRef(); - if (globalRef != null) { - // Use the search service get a reference to the - // current user's saved searches folder. - // Search within the context of the global saved searches - // folder - FacesContext fc = FacesContext.getCurrentInstance(); - User user = Application.getCurrentUser(fc); - String userName = ISO9075.encode(user.getUserName()); - String xpath = NamespaceService.APP_MODEL_PREFIX + ":" - + QName.createValidLocalName(userName); - - List results = null; - try { - results = getSearchService().selectNodes(globalRef, xpath, null, - getNamespaceService(), false); - } catch (AccessDeniedException err) { - // ignore and return null - } - - if ((results != null) && (results.size() != 0)) { - userSearchesRef = results.get(0); - } - } - } - - return userSearchesRef; - } - - /** - * Get the cached reference to the global saved searches folder. This method - * will first get a reference to the global saved searches folder and assign - * it to the cached reference if is it is null. - * - * @return the cached reference to the global Saved Searches folder - */ - private NodeRef getGlobalSearchesRef() { - // if the cached reference is null, then get a reference to the - // global saved searches folder to assign to it - if (globalSearchesRef == null) { - // Use the search service get a reference to the - // global saved searches folder. - FacesContext fc = FacesContext.getCurrentInstance(); - String xpath = Application.getRootPath(fc) + "/" - + Application.getGlossaryFolderName(fc) + "/" - + Application.getSavedSearchesFolderName(fc); - - List results = null; - try { - results = getSearchService().selectNodes( - getNodeService().getRootNode(Repository.getStoreRef()), - xpath, null, getNamespaceService(), false); - } catch (AccessDeniedException err) { - // ignore and return null - } - - if (results != null && results.size() != 0) { - globalSearchesRef = results.get(0); - } - } - - return globalSearchesRef; - } - - /** - * Get node for saved search by name. - * - * @param savedSearchName - * name of saved search for which to get node - * @param savedSearchContext - * either "user" or "global", which says whether to get saved - * search out of current user's saved searches folder or global - * saved searches folder respectively - * @return node reference for saved search - */ - public NodeRef getSavedSearches(String savedSearchName, - String savedSearchContext) { - NodeRef savedSearchNodeRef = null; - - // get the saved searches folder reference from the - // current user or global searches location - NodeRef savedSearchesFolderRef = null; - if (SAVED_SEARCHES_CONTEXT_USER.equals(savedSearchContext)) { - savedSearchesFolderRef = getUserSearchesRef(); - } else if (SAVED_SEARCHES_CONTEXT_GLOBAL.equals(savedSearchContext)) { - savedSearchesFolderRef = getGlobalSearchesRef(); - } - - // read the content nodes under the folder - List childRefs = getNodeService().getChildAssocs( - savedSearchesFolderRef, ContentModel.ASSOC_CONTAINS, - RegexQNamePattern.MATCH_ALL); - - // return content node with name matching given saved search name - if (childRefs.size() != 0) { - for (ChildAssociationRef ref : childRefs) { - NodeRef childNodeRef = ref.getChildRef(); - Node childNode = new Node(childNodeRef); - if (getDictionaryService().isSubClass(childNode.getType(), - ContentModel.TYPE_CONTENT)) { - String childNodeName = childNode.getName(); - if (childNodeName.equals(savedSearchName)) { - savedSearchNodeRef = childNodeRef; - break; - } - } - } - } - - return savedSearchNodeRef; - } - - /** - * Create child element representing given AVM node and add to given parent - * element - * - * @param doc - * Document to which given parent element belongs - * @param parent - * parent element to add AVM node to as child element - * @param node - * AVM node to add as child node to given parent - * @param selectableTypes - * AVM node types which must be marked as selectable - * @param facesContent - * Faces context used to get file-type icon for given AVM node - */ - private void addAVMChildNodeToParentElement(org.w3c.dom.Document doc, - org.w3c.dom.Element parent, AVMNodeDescriptor node, - QName[] selectableTypes, FacesContext facesContext) { - // create child node element to add to file picker data - org.w3c.dom.Element childNodeElement = doc.createElement("child-node"); - childNodeElement.setAttribute("avmPath", node.getPath()); - childNodeElement.setAttribute("webappRelativePath", AVMUtil - .getWebappRelativePath(node.getPath())); - childNodeElement.setAttribute("type", node.isDirectory() ? "directory" - : "file"); - - // Set image attribute on each child - // TODO (Glen): IS this the right image to set? - // originally from Ariel's code - childNodeElement.setAttribute("image", - (node.isDirectory() ? "/images/icons/space_small.gif" : Utils - .getFileTypeImage(facesContext, node.getName(), true))); - - boolean selectable = false; - - // set 'selectable' attribute on child node to mark whether node should - // be selectable in file picker or not - // - // TODO Ariel: faking this for now since i can't figure out how to - // efficiently get the type - // qname from the avmservice - for (final QName typeQName : selectableTypes) { - selectable = selectable - || (WCMModel.TYPE_AVM_FOLDER.equals(typeQName) && node - .isDirectory()); - selectable = selectable - || (WCMModel.TYPE_AVM_CONTENT.equals(typeQName) && !node - .isDirectory()); - } - childNodeElement.setAttribute("selectable", Boolean.toString(selectable)); - - // append child node element to parent - parent.appendChild(childNodeElement); - } -} +/* + * 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.wcm; + +import java.io.InputStream; +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.context.ResponseWriter; +import javax.servlet.http.HttpServletRequest; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ISO9075; +import org.alfresco.util.Pair; +import org.alfresco.web.app.Application; +import org.alfresco.web.app.servlet.ajax.InvokeCommand; +import org.alfresco.web.bean.FileUploadBean; +import org.alfresco.web.bean.repository.Node; +import org.alfresco.web.bean.repository.Repository; +import org.alfresco.web.bean.repository.User; +import org.alfresco.web.forms.XMLUtil; +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.io.FilenameUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.XPath; +import org.dom4j.io.SAXReader; +import org.springframework.util.FileCopyUtils; + +/** + * Bean for interacting with the file picker widget using ajax requests. + */ +public class FilePickerBean implements Serializable +{ + private static final Log LOGGER = LogFactory.getLog(FilePickerBean.class); + + private static final String CONFIGURED_SEARCH_QUERY_XPATH = "/search/query"; + private static final String CDATA_START_DELIM = "![CDATA["; + private static final String CDATA_END_DELIM = "]]"; + + // parameter names + private static final String PARAM_FOLDER_RESTRICTION = "folderRestriction"; + private static final String PARAM_CONFIGURED_SEARCH_NAME = "configSearchName"; + private static final String PARAM_SELECTABLE_TYPES = "selectableTypes"; + private static final String PARAM_FILTER_MIME_TYPES = "filterMimetypes"; + private static final String PARAM_CURRENT_PATH = "currentPath"; + + private final Set uploads = new HashSet(); + + // property instance variables + private AVMBrowseBean avmBrowseBean; + transient private AVMService avmService; + transient private NamespaceService namespaceService; + transient private SearchService searchService; + transient private NodeService nodeService; + transient private DictionaryService dictionaryService; + transient private ContentService contentService; + + // cached reference to the public saved searches folder + private NodeRef publicSearchesRef = null; + + // initial current folder - the current folder path which the + // the file picker opens at when first selected in the form + private String initialCurrentPath = null; + + public FilePickerBean() + { + } + + public void clearUploadedFiles() + { + this.uploads.clear(); + } + + public NodeRef[] getUploadedFiles() + { + return (NodeRef[]) this.uploads.toArray(new NodeRef[this.uploads.size()]); + } + + /** + * Set avmBrowseBean property for this bean + * + * @param avmBrowseBean + * the AVMBrowseBean object to pass into this property + */ + public void setAvmBrowseBean(final AVMBrowseBean avmBrowseBean) + { + this.avmBrowseBean = avmBrowseBean; + } + + /** + * Get avmBrowseBean property for this bean + * + * @return avmBrowseBean property value for this bean + */ + public AVMBrowseBean getAvmBrowseBean() + { + return this.avmBrowseBean; + } + + /** + * Set avmService property for this bean + * + * @param avmService + * the avmService object to pass into this property + */ + public void setAvmService(final AVMService avmService) + { + this.avmService = avmService; + } + + /** + * Get avmService property for this bean + * + * @return avmService property value for this bean + */ + public AVMService getAvmService() + { + if (this.avmService == null) + { + this.avmService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getAVMService(); + } + return this.avmService; + } + + /** + * Set nodeService property value for this bean + * + * @param nodeService + * the NodeService object to pass into this property + */ + public void setNodeService(final NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Get nodeService property for this bean + * + * @return nodeService property value for this bean + */ + public NodeService getNodeService() + { + if (this.nodeService == null) + { + this.nodeService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getNodeService(); + } + return this.nodeService; + } + + /** + * Set searchService property for this bean + * + * @param searchService + * the SearchService object to pass into this property + */ + public void setSearchService(final SearchService searchService) + { + this.searchService = searchService; + } + + /** + * Get SearchService property for this bean + * + * @return searchService property value for this bean + */ + public SearchService getSearchService() + { + if (this.searchService == null) + { + this.searchService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getSearchService(); + } + return this.searchService; + } + + /** + * Set dictionaryService property value for this bean + * + * @param dictionaryService + * the DictionaryService object to pass into this property + */ + public void setDictionaryService(final DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Get dictionaryService property for this bean + * + * @return dictionaryService property value for this bean + */ + public DictionaryService getDictionaryService() + { + if (this.dictionaryService == null) + { + this.dictionaryService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getDictionaryService(); + } + return this.dictionaryService; + } + + /** + * Set namespaceService property for this bean + * + * @param namespaceService + * the NamespaceService object to pass into this property + */ + public void setNamespaceService(final NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Get the namespaceService property value for this bean + * + * @return namespaceService property value for this bean + */ + public NamespaceService getNamespaceService() + { + if (this.namespaceService == null) + { + this.namespaceService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getNamespaceService(); + } + return this.namespaceService; + } + + /** + * Set contentService property + * + * @param contentService + * the ContentService object to pass into this property + */ + public void setContentService(final ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Get contentService property value for this bean + * + * @return contentService property value for this bean + */ + public ContentService getContentService() + { + if (this.contentService == null) + { + this.contentService = Repository.getServiceRegistry( + FacesContext.getCurrentInstance()).getContentService(); + } + return this.contentService; + } + + /** + * Provides data for a file picker widget. + */ + @InvokeCommand.ResponseMimetype(value = MimetypeMap.MIMETYPE_XML) + public void getFilePickerData() throws Exception + { + final FacesContext facesContext = FacesContext.getCurrentInstance(); + final ExternalContext externalContext = facesContext.getExternalContext(); + + // get configured search name parameter value + String configSearchName = null; + String[] configSearchNameParam = (String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_CONFIGURED_SEARCH_NAME); + if ((configSearchNameParam != null) + && (configSearchNameParam.length != 0)) + { + configSearchName = configSearchNameParam[0]; + } + + // get selectableTypes parameter value + final QName[] selectableTypes = this + .getSelectableTypes((String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_SELECTABLE_TYPES)); + + // / get filterMimetypes parameter value + final Pattern[] filterMimetypes = this + .getFilterMimetypes((String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_FILTER_MIME_TYPES)); + + // get 'folderRestriction' parameter value + // expecting a relative AVM folder path to be held in this parameter + // (relative to web project webapp root) + String folderPathRestriction = null; + String[] folderPathRestrictionParam = (String[]) externalContext + .getRequestParameterValuesMap().get(PARAM_FOLDER_RESTRICTION); + if ((folderPathRestrictionParam != null) + && (folderPathRestrictionParam.length != 0)) + { + folderPathRestriction = folderPathRestrictionParam[0]; + + // remove leading '/' or '\' (if present) from path restriction + if ((folderPathRestriction.charAt(0) == '/') + || (folderPathRestriction.charAt(0) == '\\')) + { + folderPathRestriction = folderPathRestriction.substring(1); + } + } + + // ### + // the following section sets file picker current path (the folder that + // file picker + // is opened at when selected/changed in the form) + + String currentPath = null; + + // get current path request parameter + String currentPathReqParam = (String) externalContext + .getRequestParameterMap().get(PARAM_CURRENT_PATH); + + // if current path request parameter null then set current path to the + // current AVM path + if ((currentPathReqParam == null)) + { + currentPath = this.getCurrentAVMPath(); + } + // else set current path to current path request parameter converted to + // AVM preview + // store path + else + { + final String previewStorePath = AVMUtil + .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); + currentPath = AVMUtil.buildPath(previewStorePath, currentPathReqParam, + AVMUtil.PathRelation.WEBAPP_RELATIVE); + } + + // if initial current path not set then set it to the current path + if (initialCurrentPath == null) + { + initialCurrentPath = currentPath; + + // insert '/' at end of initial current path if there isn't one + if (initialCurrentPath.charAt(initialCurrentPath.length() - 1) != '/') + { + initialCurrentPath = initialCurrentPath + "/"; + } + } + + // if folder path restriction (relative path) is set, + // then calculate the absolute restriction path (in context of file + // picker's + // initial current path - the path at which it was opened the first time) + // and set file picker current path to that + if ((folderPathRestriction != null) + && (folderPathRestriction.length() != 0)) + { + currentPath = initialCurrentPath + folderPathRestriction; + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(this + ".getFilePickerData(path = " + currentPath + + ", selectableTypes = [" + + StringUtils.join(selectableTypes, ",") + + "], filterMimetypes = [" + + StringUtils.join(filterMimetypes, ",") + "])"); + } + + // create file picker data XML document to return + // and append file picker data element to it + final org.w3c.dom.Document filePickerDataDoc = XMLUtil.newDocument(); + final org.w3c.dom.Element filePickerDataElement = filePickerDataDoc + .createElement("file-picker-data"); + filePickerDataDoc.appendChild(filePickerDataElement); + + // make sure that there is a node associated with current path + // if not, set an applicable error message as an attribute on + // the file picker data element + final AVMNodeDescriptor currentNode = this.getAvmService().lookup(-1, + currentPath); + + // if the current node is null (path held in current path variable is + // invalid), then add an error attribute to the file picker data + if (currentNode == null) + { + currentPath = AVMUtil.getWebappRelativePath(currentPath); + + filePickerDataElement.setAttribute("error", MessageFormat.format( + Application.getMessage(facesContext, "error_not_found"), + currentPath.substring(currentPath.lastIndexOf("/") + 1, + currentPath.length()), + (currentPath.lastIndexOf("/") == 0 ? "/" : currentPath + .substring(0, currentPath.lastIndexOf("/"))))); + + // If folder restriction has been set, since the derived + // current path is invalid just set it to null + if ((folderPathRestriction != null) + && (folderPathRestriction.length() != 0)) + { + currentPath = null; + } else + // otherwise folder restriction has not been set, so it should be safe + // to fall back to setting the current path to the current AVM path + // (as was the behaviour before the folder restriction feature + // was added) + { + currentPath = this.getCurrentAVMPath(); + } + } + // else current node is not null (path held in current path variable is + // valid) + else + { + // if node for current path points to a file instead of a directory, + // then make sure that the current path points to + // just the directory part of the path + if (!currentNode.isDirectory()) + { + currentPath = AVMNodeConverter.SplitBase(currentPath)[0]; + } + } + + // create current-node element representing node for current path + // and append it to the file picker data element + org.w3c.dom.Element currentNodeElement = filePickerDataDoc + .createElement("current-node"); + if (currentPath == null) + { + currentNodeElement.setAttribute("avmPath", ""); + currentNodeElement.setAttribute("webappRelativePath", ""); + } else + { + currentNodeElement.setAttribute("avmPath", currentPath); + currentNodeElement.setAttribute("webappRelativePath", AVMUtil + .getWebappRelativePath(currentPath)); + } + currentNodeElement.setAttribute("type", "directory"); + + // TODO (Glen): This was in Ariel's code. Is this the correct + // image to set? + currentNodeElement.setAttribute("image", "/images/icons/space_small.gif"); + + filePickerDataElement.appendChild(currentNodeElement); + + // if configured search name supplied (i.e. not null), + // then get configured search node matching given name + // and add the nodes from the search result to the file + // picker data + if ((configSearchName != null) && (configSearchName.length() != 0)) + { + // get node reference for named configured search + NodeRef configuredSearchNodeRef = getConfiguredSearches(configSearchName); + + // run search to get content nodes returned in search result + List searchResultNodes = runConfiguredSearch(configuredSearchNodeRef); + + // add elements for content nodes from search results as child nodes + // of the file picker data element. + addAVMNodesToElement(filePickerDataDoc, filePickerDataElement, + searchResultNodes, selectableTypes, facesContext); + } else + { + // add elements for child nodes of current path to file picker + // data element if current path is not null + if (currentPath != null) + { + addPathChildNodesToElement(filePickerDataDoc, + filePickerDataElement, currentPath, selectableTypes, + filterMimetypes, facesContext); + } + } + + final ResponseWriter out = facesContext.getResponseWriter(); + XMLUtil.print(filePickerDataDoc, out); + } + + @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 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; + String returnPage = null; + InputStream fileInputStream = null; + for (FileItem item : fileItems) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("item = " + item); + } + if (item.isFormField() && item.getFieldName().equals("upload-id")) + { + uploadId = item.getString(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("uploadId is " + uploadId); + } + } + if (item.isFormField() && item.getFieldName().equals("return-page")) + { + returnPage = item.getString(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("returnPage is " + returnPage); + } + } else if (item.isFormField() + && item.getFieldName().equals("currentPath")) + { + final String previewStorePath = AVMUtil + .getCorrespondingPathInPreviewStore(this.getCurrentAVMPath()); + currentPath = AVMUtil.buildPath(previewStorePath, item.getString(), + AVMUtil.PathRelation.WEBAPP_RELATIVE); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("currentPath is " + currentPath); + } + } else + { + filename = FilenameUtils.getName(item.getName()); + fileInputStream = item.getInputStream(); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("uploading file " + filename); + } + } + } + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("saving file " + filename + " to " + currentPath); + } + + try + { + FileCopyUtils.copy(fileInputStream, this.getAvmService().createFile( + currentPath, filename)); + final Map props = new HashMap( + 1, 1.0f); + props.put(ContentModel.PROP_TITLE, new PropertyValue( + DataTypeDefinition.TEXT, filename)); + // props.put(ContentModel.PROP_DESCRIPTION, + // new PropertyValue(DataTypeDefinition.TEXT, + // "Uploaded for form " + this.xformsSession.getForm().getName())); + this.getAvmService().setNodeProperties(currentPath + "/" + filename, + props); + this.getAvmService().addAspect(currentPath + "/" + filename, + ContentModel.ASPECT_TITLED); + + this.uploads.add(AVMNodeConverter.ToNodeRef(-1, currentPath + "/" + + filename)); + returnPage = returnPage.replace("${_FILE_TYPE_IMAGE}", Utils + .getFileTypeImage(facesContext, filename, true)); + } catch (Exception e) + { + LOGGER.debug(e.getMessage(), e); + returnPage = returnPage.replace("${_UPLOAD_ERROR}", e.getMessage()); + } + + LOGGER.debug("upload complete. sending response: " + returnPage); + final org.w3c.dom.Document result = XMLUtil.newDocument(); + final org.w3c.dom.Element htmlEl = result.createElement("html"); + result.appendChild(htmlEl); + final org.w3c.dom.Element bodyEl = result.createElement("body"); + htmlEl.appendChild(bodyEl); + + final org.w3c.dom.Element scriptEl = result.createElement("script"); + bodyEl.appendChild(scriptEl); + scriptEl.setAttribute("type", "text/javascript"); + final org.w3c.dom.Node scriptText = result.createTextNode(returnPage); + scriptEl.appendChild(scriptText); + + final ResponseWriter out = facesContext.getResponseWriter(); + XMLUtil.print(result, out); + } + + private String getCurrentAVMPath() + { + final AVMNode node = this.getAvmBrowseBean().getAvmActionNode(); + if (node == null) + { + return this.getAvmBrowseBean().getCurrentPath(); + } + + final String result = node.getPath(); + return node.isDirectory() ? result + : AVMNodeConverter.SplitBase(result)[0]; + } + + private QName[] getSelectableTypes(final String[] selectableTypes) + { + final QName[] result = (selectableTypes == null ? new QName[] + { WCMModel.TYPE_AVM_CONTENT, WCMModel.TYPE_AVM_FOLDER } + : new QName[selectableTypes.length]); + + if (selectableTypes != null) + { + for (int i = 0; i < selectableTypes.length; i++) + { + result[i] = QName.resolveToQName(this.getNamespaceService(), + selectableTypes[i]); + } + } + return result; + } + + private Pattern[] getFilterMimetypes(final String[] filterMimetypes) + { + final Pattern[] result = filterMimetypes == null ? new Pattern[0] + : new Pattern[filterMimetypes.length]; + if (filterMimetypes != null) + { + for (int i = 0; i < filterMimetypes.length; i++) + { + result[i] = Pattern.compile(filterMimetypes[i].replaceAll("\\*", + "\\.*").replaceAll("\\/", "\\\\/")); + } + } + return result; + } + + /** + * Add child nodes of supplied path to given parent element. Directory + * listing is done on given path. Elements representing the child nodes + * returned in the directory listing are added to the supplied parent + * element. + * + * @param doc + * XML document to which the parent node belongs + * @param parent + * parent element to add given nodes to as child elements + * @param path + * path from which to extract child nodes + * @param selectableTypes + * array of types which are the only ones that should be selectable + * in the file picker + * @param filterMimetypes + * array of MIME type patterns used to filter out child nodes + * extracted from the given path which don't match the given MIME + * type patterns + * @param facesContext + * faces context used to set image attribute on each child element + */ + private void addPathChildNodesToElement(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, String path, QName[] selectableTypes, + Pattern[] filterMimetypes, FacesContext facesContext) + { + // append elements for the child AVM nodes of the current path + // to parent element + for (final Map.Entry entry : this + .getAvmService().getDirectoryListing(-1, path).entrySet()) + { + // if AVM node is a content node and the filter MIME types parameter + // has been set, then only add child element for AVM node if it matches + // one of the specified MIME types in the parameter + if (!entry.getValue().isDirectory() && filterMimetypes.length != 0) + { + final String contentMimetype = this.getAvmService() + .getContentDataForRead(entry.getValue()).getMimetype(); + + boolean matched = false; + for (final Pattern p : filterMimetypes) + { + matched = p.matcher(contentMimetype).matches(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(p + ".matches(" + contentMimetype + ") = " + + matched); + } + if (matched) + { + break; + } + } + + // if AVM node MIME type doesn't match any of the types in the + // filter MIME types parameter, then don't do any further processing + // on it and jump back to the start of the AVM node traversal loop + if (!matched) + { + continue; + } + } + + // create child element representing AVM node and add to file picker + // data element + addAVMChildNodeToParentElement(doc, parent, entry.getValue(), + selectableTypes, facesContext); + } + } + + /** + * Add provided AVM node descriptors as child node elements to the provided + * parent element + * + * @param doc + * XML document to which the parent node belongs + * @param parent + * parent element to add given nodes as child elements + * @param nodes + * nodes which to add as child elements to parent elements + * @param selectableTypes + * AVM node types which must be marked as selectable + * @param facesContext + * faces context used to set image attribute on each child element + */ + private void addAVMNodesToElement(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, List nodes, + QName[] selectableTypes, FacesContext facesContext) + { + for (AVMNodeDescriptor node : nodes) + { + // create child element representing AVM node and add to file picker + // data element + addAVMChildNodeToParentElement(doc, parent, node, selectableTypes, + facesContext); + } + } + + /** + * Run a configured search represented by the given node reference, against + * the web project the XForm is currently within + * + * @param configuredQueryNodRef + * NodeRef of the configured query with which to run the search + * + * @return content nodes returned by the search + */ + private List runConfiguredSearch( + NodeRef configSearchNodeRef) + { + // get the store id used to run the configured search query + WebProject webProject = this.getAvmBrowseBean().getWebProject(); + String storeID = webProject.getStoreId(); + + // extract the content of configured search node to XML document + ContentReader contentReader = getContentService().getReader( + configSearchNodeRef, ContentModel.PROP_CONTENT); + InputStream queryInpStream = contentReader.getContentInputStream(); + SAXReader reader = new SAXReader(); + Document queryDoc = null; + try + { + queryDoc = reader.read(queryInpStream); + } catch (DocumentException de) + { + // ignore exception and return null + return null; + } + + // extract search query from configured search XML document + String query = null; + XPath queryXPath = DocumentHelper + .createXPath(CONFIGURED_SEARCH_QUERY_XPATH); + List xpathResult = queryXPath.selectNodes(queryDoc); + if ((xpathResult != null) && (xpathResult.size() != 0)) + { + // get the text from the query element + Element queryElement = (Element) xpathResult.get(0); + String queryElemText = queryElement.getText(); + + // now extract the actual search query string from the CDATA section + // within that text + + int cdataStartDelimIndex = queryElemText.indexOf(CDATA_START_DELIM); + int cdataEndDelimIndex = queryElemText.indexOf(CDATA_END_DELIM); + + // if the CDATA start delimiter is found in the query element text + // && there is text between the CDATA start and end delimiters then + // extract + // the query string from the CDATA section + if ((cdataStartDelimIndex > -1) + && ((cdataStartDelimIndex + CDATA_START_DELIM.length()) < cdataEndDelimIndex)) + { + query = queryElemText.substring(cdataStartDelimIndex + + CDATA_START_DELIM.length(), cdataEndDelimIndex); + } + } + + // perform the search against the repository + // if query was extracted from the configured search successfully + // (extracted query non-null) + List resultNodeDescriptors = null; + if ((query != null) && (query.length() != 0)) + { + ResultSet results = null; + try + { + results = this.getSearchService().query( + new StoreRef(StoreRef.PROTOCOL_AVM, storeID), + SearchService.LANGUAGE_LUCENE, query); + + if (results.length() != 0) + { + resultNodeDescriptors = new ArrayList(); + for (int i = 0; i < results.length(); i++) + { + ResultSetRow row = results.getRow(i); + NodeRef resultNodeRef = row.getNodeRef(); + Node resultNode = new Node(resultNodeRef); + + // only add content type nodes to the search result + // as we don't want the user to navigate down into folders + // in the search results + if (getDictionaryService().isSubClass(resultNode.getType(), + ContentModel.TYPE_CONTENT)) + { + Pair pair = AVMNodeConverter + .ToAVMVersionPath(resultNodeRef); + Integer version = pair.getFirst(); + String path = pair.getSecond(); + resultNodeDescriptors.add(getAvmService().lookup(version, + path)); + } + } + } + } catch (Throwable err) + { + throw new AlfrescoRuntimeException("Failed to execute search: " + + query, err); + } finally + { + if (results != null) + { + results.close(); + } + } + } + + return resultNodeDescriptors; + } + + /** + * Get the cached reference to the public saved searches folder. This method + * will first get a reference to the public saved searches folder and assign + * it to the cached reference if is it is null. + * + * @return the cached reference to the public Saved Searches folder + */ + private NodeRef getPublicSearchesRef() + { + // if the cached reference is null, then get a reference to the + // public saved searches folder to assign to it + if (publicSearchesRef == null) + { + // Use the search service get a reference to the + // public saved searches folder. + FacesContext fc = FacesContext.getCurrentInstance(); + String xpath = Application.getRootPath(fc) + "/" + + Application.getGlossaryFolderName(fc) + "/" + + Application.getSavedSearchesFolderName(fc); + + List results = null; + try + { + results = getSearchService().selectNodes( + getNodeService().getRootNode(Repository.getStoreRef()), + xpath, null, getNamespaceService(), false); + } catch (AccessDeniedException err) + { + // ignore and return null + } + + if (results != null && results.size() != 0) + { + publicSearchesRef = results.get(0); + } + } + + return publicSearchesRef; + } + + /** + * Get node for configured search by name. + * + * @param configSearchName + * name of configured search for which to get node + * @return node reference for configured search + */ + public NodeRef getConfiguredSearches(String configSearchName) + { + NodeRef configSearchNodeRef = null; + + // get the folder reference from the + // public searches location + NodeRef publicSearchesFolderRef = getPublicSearchesRef(); + + // read the content nodes under the folder + List childRefs = getNodeService().getChildAssocs( + publicSearchesFolderRef, ContentModel.ASSOC_CONTAINS, + RegexQNamePattern.MATCH_ALL); + + // return content node with name matching given configured search name + if (childRefs.size() != 0) + { + for (ChildAssociationRef ref : childRefs) + { + NodeRef childNodeRef = ref.getChildRef(); + Node childNode = new Node(childNodeRef); + if (getDictionaryService().isSubClass(childNode.getType(), + ContentModel.TYPE_CONTENT)) + { + String childNodeName = childNode.getName(); + if (childNodeName.equals(configSearchName)) + { + configSearchNodeRef = childNodeRef; + break; + } + } + } + } + + return configSearchNodeRef; + } + + /** + * Create child element representing given AVM node and add to given parent + * element + * + * @param doc + * Document to which given parent element belongs + * @param parent + * parent element to add AVM node to as child element + * @param node + * AVM node to add as child node to given parent + * @param selectableTypes + * AVM node types which must be marked as selectable + * @param facesContent + * Faces context used to get file-type icon for given AVM node + */ + private void addAVMChildNodeToParentElement(org.w3c.dom.Document doc, + org.w3c.dom.Element parent, AVMNodeDescriptor node, + QName[] selectableTypes, FacesContext facesContext) + { + // create child node element to add to file picker data + org.w3c.dom.Element childNodeElement = doc.createElement("child-node"); + childNodeElement.setAttribute("avmPath", node.getPath()); + childNodeElement.setAttribute("webappRelativePath", AVMUtil + .getWebappRelativePath(node.getPath())); + childNodeElement.setAttribute("type", node.isDirectory() ? "directory" + : "file"); + + // Set image attribute on each child + // TODO (Glen): IS this the right image to set? + // originally from Ariel's code + childNodeElement.setAttribute("image", + (node.isDirectory() ? "/images/icons/space_small.gif" : Utils + .getFileTypeImage(facesContext, node.getName(), true))); + + boolean selectable = false; + + // set 'selectable' attribute on child node to mark whether node should + // be selectable in file picker or not + // + // TODO Ariel: faking this for now since i can't figure out how to + // efficiently get the type + // qname from the avmservice + for (final QName typeQName : selectableTypes) + { + selectable = selectable + || (WCMModel.TYPE_AVM_FOLDER.equals(typeQName) && node + .isDirectory()); + selectable = selectable + || (WCMModel.TYPE_AVM_CONTENT.equals(typeQName) && !node + .isDirectory()); + } + childNodeElement.setAttribute("selectable", Boolean.toString(selectable)); + + // append child node element to parent + parent.appendChild(childNodeElement); + } +} diff --git a/source/web/scripts/ajax/file_picker_widget.js b/source/web/scripts/ajax/file_picker_widget.js index 141be5233a..57988e1ae8 100644 --- a/source/web/scripts/ajax/file_picker_widget.js +++ b/source/web/scripts/ajax/file_picker_widget.js @@ -1,760 +1,754 @@ -/* - * 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. - */ -//////////////////////////////////////////////////////////////////////////////// -// FilePickerWidget -// -// This script communicates with the XFormBean to manage a file picker widget -// for selecting and uploading files in the avm. -// -// This script requires dojo.js, ajax_helper.js and upload_helper.js to be -// loaded in advance. -//////////////////////////////////////////////////////////////////////////////// -if (typeof alfresco == "undefined") -{ - throw new Error("file_picker_widget requires alfresco be defined"); -} - -if (typeof alfresco.constants == "undefined") -{ - throw new Error("file_picker_widget requires alfresco.constants be defined"); -} - -if (typeof alfresco.resources == "undefined") -{ - throw new Error("file_picker_widget requires alfresco.resources be defined"); -} - -/** - * The file picker widget. - */ -alfresco.FilePickerWidget = function(uploadId, - node, - value, - readonly, - change_callback, - cancel_callback, - resize_callback, - selectableTypes, - filterMimetypes, - folderRestriction, - savedSearchName, - savedSearchContext) -{ - this.uploadId = uploadId; - this.node = node; - this.value = value == null || value.length == 0 ? null : value; - this.readonly = readonly || false; - this.change_callback = change_callback; - this.cancel_callback = cancel_callback || function() {}; - this.resize_callback = resize_callback || function() {}; - this.selectableTypes = selectableTypes; - this.filterMimetypes = filterMimetypes; - this.folderRestriction = folderRestriction; - this.savedSearchName = savedSearchName; - this.savedSearchContext = savedSearchContext; -} - -// static methods and properties - -alfresco.FilePickerWidget._uploads = []; -alfresco.FilePickerWidget._handleUpload = function(id, fileInput, webappRelativePath, widget) -{ - alfresco.FilePickerWidget._uploads[id] = - { - widget:widget, - path: fileInput.value, - webappRelativePath: webappRelativePath - }; - handle_upload_helper(fileInput, - id, - alfresco.FilePickerWidget._upload_completeHandler, - alfresco.constants.WEBAPP_CONTEXT, - "/ajax/invoke/FilePickerBean.uploadFile", - { currentPath: webappRelativePath }); -} - -alfresco.FilePickerWidget._upload_completeHandler = function(id, path, fileName, fileTypeImage, error) -{ - var upload = alfresco.FilePickerWidget._uploads[id]; - upload.widget._upload_completeHandler(fileName, - upload.webappRelativePath, - fileTypeImage, - error); -} - -// instance methods and properties - -alfresco.FilePickerWidget.prototype = { - -getValue: function() -{ - return this.value; -}, - -setValue: function(v) -{ - this.value = (v == null || v.length == 0 ? null : v); - if (this.selectedPathInput) - { - this.selectedPathInput.value = v; - } - - this.change_callback(this); -}, - -setReadonly: function(r) -{ - this.readonly = r; - if (this._selectButton) - { - this._selectButton.disabled = this.readonly; - } - else if (this.readonly) - { - this._showSelectedValue(); - } -}, - -destroy: function() -{ - dojo.dom.removeChildren(this.node); - this.node.parentNode.removeChild(this.node); - this.node = null; -}, - -render: function() -{ - this._showSelectedValue(); -}, - -_showStatus: function(text, isError) -{ - var d = this.node.ownerDocument; - if (!this.statusDiv || !this.statusDiv.parentNode) - { - this.statusDiv = d.createElement("div"); - this.statusDiv.setAttribute("id", this.uploadId + "-status"); - this.statusDiv.widget = this; - this.node.insertBefore(this.statusDiv, this.node.firstChild); - dojo.html.setClass(this.statusDiv, "infoText xformsFilePickerStatus"); - if (isError) - { - dojo.html.addClass(this.statusDiv, "statusErrorText"); - } - this.statusDiv.appendChild(d.createTextNode(text)); - this.node.style.height = (parseInt(this.node.style.height) + - dojo.html.getMargin(this.statusDiv).height + - this.statusDiv.offsetHeight) + "px"; - this.resize_callback(this); - } - else - { - this.statusDiv.firstChild.nodeValue = text; - } - setTimeout("var _status = document.getElementById('" + this.uploadId + - "-status'); if (_status && _status) { _status.widget._hideStatus(); }", 5000); -}, - -_hideStatus: function() -{ - if (this.statusDiv) - { - var anim = dojo.lfx.html.fadeOut(this.statusDiv, 500); - var _fp_widget = this; - anim.onEnd = function() - { - if (_fp_widget.statusDiv && _fp_widget.statusDiv.parentNode) - { - _fp_widget.node.style.height = (parseInt(_fp_widget.node.style.height) - - _fp_widget.statusDiv.offsetHeight) + "px"; - dojo.dom.removeChildren(_fp_widget.statusDiv); - dojo.dom.removeNode(_fp_widget.statusDiv); - _fp_widget.resize_callback(_fp_widget); - _fp_widget.statusDiv = null; - } - }; - - anim.play(); - } -}, - -_showSelectedValue: function() -{ - if (this.node == null) - { - return; - } - var d = this.node.ownerDocument; - dojo.dom.removeChildren(this.node); - this.statusDiv = null; - this.contentDiv = null; - this.addContentDiv = null; - - this.node.style.height = "20px"; - this.node.style.lineHeight = this.node.style.height; - this.node.style.position = "relative"; - this.node.style.whiteSpace = "nowrap"; - - this.resize_callback(this); - - this.selectedPathInput = d.createElement("input"); - this.selectedPathInput.type = "text"; - this.selectedPathInput.value = this.value == null ? "" : this.value; - this.node.appendChild(this.selectedPathInput); - - dojo.event.connect(this.selectedPathInput, "onblur", this, this._selectPathInput_changeHandler); - - this._selectButton = d.createElement("input"); - this._selectButton.filePickerWidget = this; - this._selectButton.type = "button"; - - this._selectButton.value = (this.value == null - ? alfresco.resources["select"] - : alfresco.resources["change"]); - this._selectButton.disabled = this.readonly; - this._selectButton.style.margin = "0px 10px"; - this.node.appendChild(this._selectButton); - - this.selectedPathInput.style.width = (1 - - ((this._selectButton.offsetWidth + - dojo.html.getMargin(this._selectButton).width) / - dojo.html.getContentBox(this.node).width)) * 100 + "%"; - - dojo.event.browser.addListener(this._selectButton, - "onclick", - this._selectButton_clickHandler); -}, - -_selectButton_clickHandler: function(event) -{ - var w = event.target.filePickerWidget; - w._navigateToNode(w.getValue() || ""); -}, - -_selectPathInput_changeHandler: function(event) -{ - this.setValue(event.target.value); -}, - -_navigateToNode: function(path) -{ - var params = { currentPath: path}; - if (this.selectableTypes) - { - params.selectableTypes = this.selectableTypes; - } - if (this.filterMimetypes) - { - params.filterMimetypes = this.filterMimetypes; - } - if (this.folderRestriction) - { - req.content.folderRestriction = this.folderRestriction; - } - if (this.savedSearchName) - { - req.content.savedSearchName = this.savedSearchName; - } - if (this.savedSearchContext) - { - req.content.savedSearchContext = this.savedSearchContext; - } - - alfresco.AjaxHelper.sendRequest("FilePickerBean.getFilePickerData", - params, - true, - this._showPicker.bindAsEventListener(this)); -}, - -_showPicker: function(data) -{ - data = data.documentElement; - while (this.node.hasChildNodes() && - this.node.lastChild != this.statusDiv) - { - this.node.removeChild(this.node.lastChild); - } - - var d = this.node.ownerDocument; - this.node.style.height = (200 + - (this.statusDiv - ? (parseInt(this.statusDiv.style.height) + - parseInt(this.statusDiv.style.marginTop) + - parseInt(this.statusDiv.style.marginBottom)) - : 0) + "px"); - this.resize_callback(this); - - var currentPath = data.getElementsByTagName("current-node")[0]; - currentPath = currentPath.getAttribute("webappRelativePath"); - var currentPathName = currentPath.replace(/.*\/([^/]+)/, "$1") - - var headerDiv = d.createElement("div"); - dojo.html.setClass(headerDiv, "xformsFilePickerHeader"); - this.node.appendChild(headerDiv); - headerDiv.appendChild(d.createTextNode("In: ")); - this.headerMenuTriggerLink = d.createElement("a"); - this.headerMenuTriggerLink.filePickerWidget = this; - this.headerMenuTriggerLink.style.textDecoration = "none"; - this.headerMenuTriggerLink.setAttribute("href", "javascript:void(0)"); - this.headerMenuTriggerLink.setAttribute("webappRelativePath", currentPath); - dojo.html.setClass(this.headerMenuTriggerLink, "xformsFilePickerHeaderMenuTrigger"); - headerDiv.appendChild(this.headerMenuTriggerLink); - - dojo.event.connect(this.headerMenuTriggerLink, - "onmouseover", - function(event) - { - event.currentTarget.style.backgroundColor = "#fefefe"; - event.currentTarget.style.borderStyle = "inset"; - }); - dojo.event.connect(this.headerMenuTriggerLink, - "onmouseout", - function(event) - { - var w = event.currentTarget.filePickerWidget; - if (!w.parentPathMenu) - { - event.currentTarget.style.backgroundColor = - event.currentTarget.parentNode.style.backgroundColor; - event.currentTarget.style.borderStyle = "solid"; - } - }); - // can't use dojo's event handling since it screws up when opened in another window - var filePickerWidget = this; - var headerMenuTriggerLink = this.headerMenuTriggerLink; - this.headerMenuTriggerLink.onclick = function(event) - { - if (filePickerWidget.parentPathMenu) - { - filePickerWidget._closeParentPathMenu(); - } - else - { - filePickerWidget._openParentPathMenu(headerMenuTriggerLink, - headerMenuTriggerLink.getAttribute("webappRelativePath")); - } - }; - - this.headerMenuTriggerLink.appendChild(d.createTextNode(currentPathName)); - - headerMenuTriggerImage = d.createElement("img"); - this.headerMenuTriggerLink.appendChild(headerMenuTriggerImage); - this.headerMenuTriggerLink.image = headerMenuTriggerImage; - headerMenuTriggerImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/menu.gif"); - headerMenuTriggerImage.style.borderWidth = "0px"; - headerMenuTriggerImage.style.marginLeft = "4px"; - headerMenuTriggerImage.align = "absmiddle"; - - var headerRightDiv = d.createElement("div"); - - var addContentLink = d.createElement("a"); - headerRightDiv.appendChild(addContentLink); - addContentLink.setAttribute("webappRelativePath", currentPath); - addContentLink.style.textDecoration = "none"; - addContentLink.filePickerWidget = this; - addContentLink.setAttribute("href", "javascript:void(0)"); - dojo.event.connect(addContentLink, - "onclick", - function(event) - { - var w = event.target.filePickerWidget; - if (w.addContentDiv) - { - w._hideAddContent(); - } - else - { - w._showAddContent(event.target.getAttribute("webappRelativePath")); - } - }); - - var addContentImage = d.createElement("img"); - addContentImage.style.borderWidth = "0px"; - addContentImage.style.margin = "0px 2px 0px 2px"; - addContentImage.align = "absmiddle"; - addContentImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/add.gif"); - addContentLink.appendChild(addContentImage); - - addContentLink.appendChild(d.createTextNode(alfresco.resources["add_content"])); - - var navigateToParentLink = d.createElement("a"); - headerRightDiv.appendChild(navigateToParentLink); - navigateToParentLink.setAttribute("webappRelativePath", currentPath); - navigateToParentLink.filePickerWidget = this; - navigateToParentLink.style.textDecoration = "none"; - navigateToParentLink.setAttribute("href", "javascript:void(0)"); - if (currentPathName != "/") - { - dojo.event.connect(navigateToParentLink, - "onclick", - function(event) - { - var w = event.target.filePickerWidget; - var parentPath = event.target.getAttribute("webappRelativePath"); - parentPath = (parentPath.lastIndexOf("/") == 0 - ? "/" - : parentPath.substring(0, parentPath.lastIndexOf("/"))); - w._navigateToNode(parentPath); - }); - } - - var navigateToParentNodeImage = d.createElement("img"); - navigateToParentNodeImage.style.borderWidth = "0px"; - dojo.html.setOpacity(navigateToParentNodeImage, (currentPathName == "/" ? .3 : 1)); - navigateToParentNodeImage.style.margin = "0px 2px 0px 2px"; - navigateToParentNodeImage.align = "absmiddle"; - navigateToParentNodeImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/up.gif"); - navigateToParentLink.appendChild(navigateToParentNodeImage); - navigateToParentLink.appendChild(d.createTextNode(alfresco.resources["go_up"])); - - 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); - - this.contentDiv = d.createElement("div"); - dojo.html.setClass(this.contentDiv, "xformsFilePickerFileList"); - this.node.appendChild(this.contentDiv); - - var footerDiv = d.createElement("div"); - dojo.html.setClass(footerDiv, "xformsFilePickerFooter"); - this.node.appendChild(footerDiv); - - var cancelButton = d.createElement("input"); - cancelButton.type = "button"; - cancelButton.filePickerWidget = this; - - cancelButton.value = alfresco.resources["cancel"]; - footerDiv.appendChild(cancelButton); - - cancelButton.style.margin = ((.5 * footerDiv.offsetHeight) - - (.5 * cancelButton.offsetHeight)) + "px 0px"; - dojo.event.connect(cancelButton, "onclick", function(event) - { - var w = event.target.filePickerWidget; - w.cancel_callback(this); - w._showSelectedValue(); - }); - this.contentDiv.style.height = (this.node.offsetHeight - - (this.statusDiv ? this.statusDiv.offsetHeight : 0) - - footerDiv.offsetHeight - - headerDiv.offsetHeight - 10) + "px"; - var childNodes = data.getElementsByTagName("child-node"); - for (var i = 0; i < childNodes.length; i++) - { - if (childNodes[i].nodeType != document.ELEMENT_NODE) - { - continue; - } - var webappRelativePath = childNodes[i].getAttribute("webappRelativePath"); - var fileName = webappRelativePath.replace(/.*\/([^/]+)/, "$1"); - var row = this._createRow(fileName, - webappRelativePath, - childNodes[i].getAttribute("type") == "directory", - eval(childNodes[i].getAttribute("selectable")), - childNodes[i].getAttribute("image"), - "xformsRow" + (i % 2 ? "Even" : "Odd")); - this.contentDiv.appendChild(row); - } - - if (data.getAttribute("error") && data.getAttribute("error").length != 0) - { - this._showStatus(data.getAttribute("error"), true); - } -}, - -_createRow: function(fileName, webappRelativePath, isDirectory, isSelectable, fileTypeImage, rowClass) -{ - var d = this.contentDiv.ownerDocument; - var result = d.createElement("div"); - result.setAttribute("id", fileName + "-row"); - result.setAttribute("webappRelativePath", webappRelativePath); - result.filePickerWidget = this; - dojo.html.setClass(result, "xformsFilePickerRow " + rowClass); - result.onmouseover = function() - { - var prevHover = result.parentNode.hoverNode; - if (prevHover) - { - dojo.html.removeClass(prevHover, "xformsRowHover"); - } - result.parentNode.hoverNode = result; - dojo.html.addClass(result, "xformsRowHover"); - }; - var e = d.createElement("img"); - e.align = "absmiddle"; - e.style.margin = "0px 4px 0px 4px"; - e.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + fileTypeImage); - result.appendChild(e); - - if (isDirectory) - { - e = d.createElement("a"); - e.style.textDecoration = "none"; - e.setAttribute("href", "javascript:void(0)"); - - e.onclick = function() - { - var w = result.filePickerWidget; - w._navigateToNode(result.getAttribute("webappRelativePath")); - return true; - }; - e.appendChild(d.createTextNode(fileName)); - result.appendChild(e); - } - else - { - result.appendChild(d.createTextNode(fileName)); - } - if (isSelectable) - { - e = d.createElement("input"); - e.type = "button"; - e.name = webappRelativePath; - e.value = "Select"; - result.appendChild(e); - - e.style.position = "absolute"; - e.style.right = "10px"; - e.style.top = (.5 * result.offsetHeight) - (.5 * e.offsetHeight) + "px"; - e.onclick = function() - { - var w = result.filePickerWidget; - w.setValue(result.getAttribute("webappRelativePath")); - w._showSelectedValue(); - }; - } - return result; -}, - -_hideAddContent: function() -{ - if (this.addContentDiv) - { - dojo.dom.removeChildren(this.addContentDiv); - dojo.dom.removeNode(this.addContentDiv); - this.addContentDiv = null; - } -}, - -_showAddContent: function(currentPath) -{ - if (this.addContentDiv) - { - return; - } - var d = this.node.ownerDocument; - this.addContentDiv = d.createElement("div"); - dojo.html.setClass(this.addContentDiv, "xformsFilePickerAddContent"); - - if (this.contentDiv.firstChild) - { - this.contentDiv.insertBefore(this.addContentDiv, this.contentDiv.firstChild); - } - else - { - this.contentDiv.appendChild(this.addContentDiv); - } - var e = d.createElement("div"); - e.style.marginLeft = "4px"; - this.addContentDiv.appendChild(e); - e.appendChild(d.createTextNode("Upload: ")); - - var fileInputDiv = d.createElement("div"); - this.addContentDiv.appendChild(fileInputDiv); - var fileInput = d.createElement("input"); - fileInput.type = "file"; - fileInput.widget = this; - fileInput.name = this.uploadId + "_file_input"; - fileInput.size = "35"; - fileInput.setAttribute("webappRelativePath", currentPath); - fileInputDiv.appendChild(fileInput); - fileInputDiv.style.position = "absolute"; - fileInputDiv.style.right = "10px"; - fileInputDiv.style.top = (.5 * this.addContentDiv.offsetHeight) - (.5 * fileInputDiv.offsetHeight) + "px"; - - fileInput.onchange = function(event) - { - event = event || fileInput.ownerDocument.parentWindow.event; - var target = event.target || event.srcElement; - var w = target.widget; - if (w.addContentDiv) - { - var d = w.addContentDiv.ownerDocument; - dojo.dom.removeChildren(w.addContentDiv); - - var fileName = target.value.replace(/.*[\/\\]([^\/\\]+)/, "$1"); - w.addContentDiv.appendChild(d.createTextNode(alfresco.resources["upload"] + ": " + fileName)); - var img = d.createElement("img"); - img.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + - "/images/icons/process_animation.gif"); - img.style.position = "absolute"; - img.style.right = "10px"; - img.style.height = (.5 * w.addContentDiv.offsetHeight) + "px"; - img.style.top = (.25 * w.addContentDiv.offsetHeight) + "px"; - w.addContentDiv.appendChild(img); - } - - alfresco.FilePickerWidget._handleUpload(w.uploadId, - target, - target.getAttribute("webappRelativePath"), - w); - }; -}, - -_upload_completeHandler: function(fileName, webappRelativePath, fileTypeImage, error) -{ - if (error) - { - this._showStatus(error, true); - this._hideAddContent(); - this._showAddContent(webappRelativePath); - } - else - { - var nextRow = dojo.dom.nextElement(this.addContentDiv); - var rowClass = (nextRow - ? ("xformsRow" + (dojo.html.hasClass(nextRow, "xformsRowEven") - ? "Odd" - : "Even")) - : "xformsRowEvent"); - var row = this._createRow(fileName, - webappRelativePath == "/" ? "/" + fileName : webappRelativePath + "/" + fileName, - false, - true /* this is potentially inaccurate - need to add some checks in the backing bean to check selectable */, - fileTypeImage, - rowClass); - this.contentDiv.replaceChild(row, this.addContentDiv); - this.addContentDiv = null; - } -}, - -_closeParentPathMenu: function() -{ - if (this.parentPathMenu) - { - dojo.dom.removeChildren(this.parentPathMenu); - dojo.dom.removeNode(this.parentPathMenu); - this.parentPathMenu = null; - } - this.headerMenuTriggerLink.style.borderStyle = "solid"; -}, - -_openParentPathMenu: function(target, path) -{ - var d = target.ownerDocument; - this.parentPathMenu = d.createElement("div"); - this.parentPathMenu.filePickerWidget = this; - d.currentParentPathMenu = this.parentPathMenu; - - // handler for closing the menu if the mouse is clicked - // outside of the menu - var parentPathMenu_documentClickHandler = function(event) - { - event = event || d.parentWindow.event; - var t = event.target || event.srcElement; - - // always remove - this handler only ever needs to handle a single click - dojo.event.browser.removeListener(d, "click", parentPathMenu_documentClickHandler, true, true); - while (t && t != d) - { - if (t == d.currentParentPathMenu || - t == d.currentParentPathMenu.filePickerWidget.headerMenuTriggerLink) - { - // the click is coming from within the component - ignore it - return true; - } - t = t.parentNode; - } - d.currentParentPathMenu.filePickerWidget._closeParentPathMenu(); - }; - - dojo.event.browser.addListener(d, "click", parentPathMenu_documentClickHandler, true, true); - - dojo.html.setClass(this.parentPathMenu, "xformsFilePickerParentPathMenu"); - - var left = 0; - var top = 0; - var n = target; - do - { - left += n.offsetLeft;// + parseInt(n.style.marginLeft) + parseInt(n.style.borderLeft); - top += n.offsetTop;// + parseInt(n.style.marginTop) + parseInt(n.style.borderTop); - n = n.parentNode; - } - while (n != this.node); - this.parentPathMenu.style.top = top + target.offsetHeight + "px"; - this.parentPathMenu.style.left = left + "px"; - var parentNodes = null; - if (path == "/") - { - parentNodes = [ "/" ]; - } - else - { - parentNodes = path.split("/"); - parentNodes[0] = "/"; - } - - var pathTextDiv = d.createElement("div"); - pathTextDiv.style.fontWeight = "bold"; - pathTextDiv.style.paddingLeft = "5px"; - - pathTextDiv.appendChild(d.createTextNode(alfresco.resources["path"])); - this.parentPathMenu.appendChild(pathTextDiv); - var currentPathNodes = []; - for (var i = 0; i < parentNodes.length; i++) - { - if (i != 0) - { - currentPathNodes.push(parentNodes[i]); - } - var path = i == 0 ? "/" : "/" + currentPathNodes.join("/"); - var parentNodeDiv = d.createElement("div"); - parentNodeDiv.setAttribute("webappRelativePath", path); - this.parentPathMenu.appendChild(parentNodeDiv); - parentNodeDiv.style.display = "block"; - parentNodeDiv.style.paddingLeft = (i * 16) + parseInt(pathTextDiv.style.paddingLeft) + "px"; - parentNodeDiv.style.paddingRight = parseInt(pathTextDiv.style.paddingLeft) + "px"; - parentNodeDiv.style.whiteSpace = "nowrap"; - - var parentNodeImage = d.createElement("img"); - parentNodeImage.align = "absmiddle"; - parentNodeImage.style.marginRight = "4px"; - parentNodeDiv.appendChild(parentNodeImage); - parentNodeImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + - "/images/icons/space_small.gif"); - parentNodeDiv.appendChild(parentNodeImage); - parentNodeDiv.appendChild(d.createTextNode(path)); - dojo.event.connect(parentNodeDiv, - "onclick", - function(event) - { - var w = event.currentTarget; - var path = w.getAttribute("webappRelativePath"); - w = w.parentNode; - w.filePickerWidget._closeParentPathMenu(); - w.filePickerWidget._navigateToNode(path); - }); - } - this.node.appendChild(this.parentPathMenu); -} -}; +/* + * 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. + */ +//////////////////////////////////////////////////////////////////////////////// +// FilePickerWidget +// +// This script communicates with the XFormBean to manage a file picker widget +// for selecting and uploading files in the avm. +// +// This script requires dojo.js, ajax_helper.js and upload_helper.js to be +// loaded in advance. +//////////////////////////////////////////////////////////////////////////////// +if (typeof alfresco == "undefined") +{ + throw new Error("file_picker_widget requires alfresco be defined"); +} + +if (typeof alfresco.constants == "undefined") +{ + throw new Error("file_picker_widget requires alfresco.constants be defined"); +} + +if (typeof alfresco.resources == "undefined") +{ + throw new Error("file_picker_widget requires alfresco.resources be defined"); +} + +/** + * The file picker widget. + */ +alfresco.FilePickerWidget = function(uploadId, + node, + value, + readonly, + change_callback, + cancel_callback, + resize_callback, + selectableTypes, + filterMimetypes, + folderRestriction, + configSearchName) +{ + this.uploadId = uploadId; + this.node = node; + this.value = value == null || value.length == 0 ? null : value; + this.readonly = readonly || false; + this.change_callback = change_callback; + this.cancel_callback = cancel_callback || function() {}; + this.resize_callback = resize_callback || function() {}; + this.selectableTypes = selectableTypes; + this.filterMimetypes = filterMimetypes; + this.folderRestriction = folderRestriction; + this.configSearchName = configSearchName; +} + +// static methods and properties + +alfresco.FilePickerWidget._uploads = []; +alfresco.FilePickerWidget._handleUpload = function(id, fileInput, webappRelativePath, widget) +{ + alfresco.FilePickerWidget._uploads[id] = + { + widget:widget, + path: fileInput.value, + webappRelativePath: webappRelativePath + }; + handle_upload_helper(fileInput, + id, + alfresco.FilePickerWidget._upload_completeHandler, + alfresco.constants.WEBAPP_CONTEXT, + "/ajax/invoke/FilePickerBean.uploadFile", + { currentPath: webappRelativePath }); +} + +alfresco.FilePickerWidget._upload_completeHandler = function(id, path, fileName, fileTypeImage, error) +{ + var upload = alfresco.FilePickerWidget._uploads[id]; + upload.widget._upload_completeHandler(fileName, + upload.webappRelativePath, + fileTypeImage, + error); +} + +// instance methods and properties + +alfresco.FilePickerWidget.prototype = { + +getValue: function() +{ + return this.value; +}, + +setValue: function(v) +{ + this.value = (v == null || v.length == 0 ? null : v); + if (this.selectedPathInput) + { + this.selectedPathInput.value = v; + } + + this.change_callback(this); +}, + +setReadonly: function(r) +{ + this.readonly = r; + if (this._selectButton) + { + this._selectButton.disabled = this.readonly; + } + else if (this.readonly) + { + this._showSelectedValue(); + } +}, + +destroy: function() +{ + dojo.dom.removeChildren(this.node); + this.node.parentNode.removeChild(this.node); + this.node = null; +}, + +render: function() +{ + this._showSelectedValue(); +}, + +_showStatus: function(text, isError) +{ + var d = this.node.ownerDocument; + if (!this.statusDiv || !this.statusDiv.parentNode) + { + this.statusDiv = d.createElement("div"); + this.statusDiv.setAttribute("id", this.uploadId + "-status"); + this.statusDiv.widget = this; + this.node.insertBefore(this.statusDiv, this.node.firstChild); + dojo.html.setClass(this.statusDiv, "infoText xformsFilePickerStatus"); + if (isError) + { + dojo.html.addClass(this.statusDiv, "statusErrorText"); + } + this.statusDiv.appendChild(d.createTextNode(text)); + this.node.style.height = (parseInt(this.node.style.height) + + dojo.html.getMargin(this.statusDiv).height + + this.statusDiv.offsetHeight) + "px"; + this.resize_callback(this); + } + else + { + this.statusDiv.firstChild.nodeValue = text; + } + setTimeout("var _status = document.getElementById('" + this.uploadId + + "-status'); if (_status && _status) { _status.widget._hideStatus(); }", 5000); +}, + +_hideStatus: function() +{ + if (this.statusDiv) + { + var anim = dojo.lfx.html.fadeOut(this.statusDiv, 500); + var _fp_widget = this; + anim.onEnd = function() + { + if (_fp_widget.statusDiv && _fp_widget.statusDiv.parentNode) + { + _fp_widget.node.style.height = (parseInt(_fp_widget.node.style.height) - + _fp_widget.statusDiv.offsetHeight) + "px"; + dojo.dom.removeChildren(_fp_widget.statusDiv); + dojo.dom.removeNode(_fp_widget.statusDiv); + _fp_widget.resize_callback(_fp_widget); + _fp_widget.statusDiv = null; + } + }; + + anim.play(); + } +}, + +_showSelectedValue: function() +{ + if (this.node == null) + { + return; + } + var d = this.node.ownerDocument; + dojo.dom.removeChildren(this.node); + this.statusDiv = null; + this.contentDiv = null; + this.addContentDiv = null; + + this.node.style.height = "20px"; + this.node.style.lineHeight = this.node.style.height; + this.node.style.position = "relative"; + this.node.style.whiteSpace = "nowrap"; + + this.resize_callback(this); + + this.selectedPathInput = d.createElement("input"); + this.selectedPathInput.type = "text"; + this.selectedPathInput.value = this.value == null ? "" : this.value; + this.node.appendChild(this.selectedPathInput); + + dojo.event.connect(this.selectedPathInput, "onblur", this, this._selectPathInput_changeHandler); + + this._selectButton = d.createElement("input"); + this._selectButton.filePickerWidget = this; + this._selectButton.type = "button"; + + this._selectButton.value = (this.value == null + ? alfresco.resources["select"] + : alfresco.resources["change"]); + this._selectButton.disabled = this.readonly; + this._selectButton.style.margin = "0px 10px"; + this.node.appendChild(this._selectButton); + + this.selectedPathInput.style.width = (1 - + ((this._selectButton.offsetWidth + + dojo.html.getMargin(this._selectButton).width) / + dojo.html.getContentBox(this.node).width)) * 100 + "%"; + + dojo.event.browser.addListener(this._selectButton, + "onclick", + this._selectButton_clickHandler); +}, + +_selectButton_clickHandler: function(event) +{ + var w = event.target.filePickerWidget; + w._navigateToNode(w.getValue() || ""); +}, + +_selectPathInput_changeHandler: function(event) +{ + this.setValue(event.target.value); +}, + +_navigateToNode: function(path) +{ + var params = { currentPath: path}; + if (this.selectableTypes) + { + params.selectableTypes = this.selectableTypes; + } + if (this.filterMimetypes) + { + params.filterMimetypes = this.filterMimetypes; + } + if (this.folderRestriction) + { + req.content.folderRestriction = this.folderRestriction; + } + if (this.configSearchName) + { + req.content.configSearchName = this.configSearchName; + } + + alfresco.AjaxHelper.sendRequest("FilePickerBean.getFilePickerData", + params, + true, + this._showPicker.bindAsEventListener(this)); +}, + +_showPicker: function(data) +{ + data = data.documentElement; + while (this.node.hasChildNodes() && + this.node.lastChild != this.statusDiv) + { + this.node.removeChild(this.node.lastChild); + } + + var d = this.node.ownerDocument; + this.node.style.height = (200 + + (this.statusDiv + ? (parseInt(this.statusDiv.style.height) + + parseInt(this.statusDiv.style.marginTop) + + parseInt(this.statusDiv.style.marginBottom)) + : 0) + "px"); + this.resize_callback(this); + + var currentPath = data.getElementsByTagName("current-node")[0]; + currentPath = currentPath.getAttribute("webappRelativePath"); + var currentPathName = currentPath.replace(/.*\/([^/]+)/, "$1") + + var headerDiv = d.createElement("div"); + dojo.html.setClass(headerDiv, "xformsFilePickerHeader"); + this.node.appendChild(headerDiv); + headerDiv.appendChild(d.createTextNode("In: ")); + this.headerMenuTriggerLink = d.createElement("a"); + this.headerMenuTriggerLink.filePickerWidget = this; + this.headerMenuTriggerLink.style.textDecoration = "none"; + this.headerMenuTriggerLink.setAttribute("href", "javascript:void(0)"); + this.headerMenuTriggerLink.setAttribute("webappRelativePath", currentPath); + dojo.html.setClass(this.headerMenuTriggerLink, "xformsFilePickerHeaderMenuTrigger"); + headerDiv.appendChild(this.headerMenuTriggerLink); + + dojo.event.connect(this.headerMenuTriggerLink, + "onmouseover", + function(event) + { + event.currentTarget.style.backgroundColor = "#fefefe"; + event.currentTarget.style.borderStyle = "inset"; + }); + dojo.event.connect(this.headerMenuTriggerLink, + "onmouseout", + function(event) + { + var w = event.currentTarget.filePickerWidget; + if (!w.parentPathMenu) + { + event.currentTarget.style.backgroundColor = + event.currentTarget.parentNode.style.backgroundColor; + event.currentTarget.style.borderStyle = "solid"; + } + }); + // can't use dojo's event handling since it screws up when opened in another window + var filePickerWidget = this; + var headerMenuTriggerLink = this.headerMenuTriggerLink; + this.headerMenuTriggerLink.onclick = function(event) + { + if (filePickerWidget.parentPathMenu) + { + filePickerWidget._closeParentPathMenu(); + } + else + { + filePickerWidget._openParentPathMenu(headerMenuTriggerLink, + headerMenuTriggerLink.getAttribute("webappRelativePath")); + } + }; + + this.headerMenuTriggerLink.appendChild(d.createTextNode(currentPathName)); + + headerMenuTriggerImage = d.createElement("img"); + this.headerMenuTriggerLink.appendChild(headerMenuTriggerImage); + this.headerMenuTriggerLink.image = headerMenuTriggerImage; + headerMenuTriggerImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/menu.gif"); + headerMenuTriggerImage.style.borderWidth = "0px"; + headerMenuTriggerImage.style.marginLeft = "4px"; + headerMenuTriggerImage.align = "absmiddle"; + + var headerRightDiv = d.createElement("div"); + + var addContentLink = d.createElement("a"); + headerRightDiv.appendChild(addContentLink); + addContentLink.setAttribute("webappRelativePath", currentPath); + addContentLink.style.textDecoration = "none"; + addContentLink.filePickerWidget = this; + addContentLink.setAttribute("href", "javascript:void(0)"); + dojo.event.connect(addContentLink, + "onclick", + function(event) + { + var w = event.target.filePickerWidget; + if (w.addContentDiv) + { + w._hideAddContent(); + } + else + { + w._showAddContent(event.target.getAttribute("webappRelativePath")); + } + }); + + var addContentImage = d.createElement("img"); + addContentImage.style.borderWidth = "0px"; + addContentImage.style.margin = "0px 2px 0px 2px"; + addContentImage.align = "absmiddle"; + addContentImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/add.gif"); + addContentLink.appendChild(addContentImage); + + addContentLink.appendChild(d.createTextNode(alfresco.resources["add_content"])); + + var navigateToParentLink = d.createElement("a"); + headerRightDiv.appendChild(navigateToParentLink); + navigateToParentLink.setAttribute("webappRelativePath", currentPath); + navigateToParentLink.filePickerWidget = this; + navigateToParentLink.style.textDecoration = "none"; + navigateToParentLink.setAttribute("href", "javascript:void(0)"); + if (currentPathName != "/") + { + dojo.event.connect(navigateToParentLink, + "onclick", + function(event) + { + var w = event.target.filePickerWidget; + var parentPath = event.target.getAttribute("webappRelativePath"); + parentPath = (parentPath.lastIndexOf("/") == 0 + ? "/" + : parentPath.substring(0, parentPath.lastIndexOf("/"))); + w._navigateToNode(parentPath); + }); + } + + var navigateToParentNodeImage = d.createElement("img"); + navigateToParentNodeImage.style.borderWidth = "0px"; + dojo.html.setOpacity(navigateToParentNodeImage, (currentPathName == "/" ? .3 : 1)); + navigateToParentNodeImage.style.margin = "0px 2px 0px 2px"; + navigateToParentNodeImage.align = "absmiddle"; + navigateToParentNodeImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/up.gif"); + navigateToParentLink.appendChild(navigateToParentNodeImage); + navigateToParentLink.appendChild(d.createTextNode(alfresco.resources["go_up"])); + + 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); + + this.contentDiv = d.createElement("div"); + dojo.html.setClass(this.contentDiv, "xformsFilePickerFileList"); + this.node.appendChild(this.contentDiv); + + var footerDiv = d.createElement("div"); + dojo.html.setClass(footerDiv, "xformsFilePickerFooter"); + this.node.appendChild(footerDiv); + + var cancelButton = d.createElement("input"); + cancelButton.type = "button"; + cancelButton.filePickerWidget = this; + + cancelButton.value = alfresco.resources["cancel"]; + footerDiv.appendChild(cancelButton); + + cancelButton.style.margin = ((.5 * footerDiv.offsetHeight) - + (.5 * cancelButton.offsetHeight)) + "px 0px"; + dojo.event.connect(cancelButton, "onclick", function(event) + { + var w = event.target.filePickerWidget; + w.cancel_callback(this); + w._showSelectedValue(); + }); + this.contentDiv.style.height = (this.node.offsetHeight - + (this.statusDiv ? this.statusDiv.offsetHeight : 0) - + footerDiv.offsetHeight - + headerDiv.offsetHeight - 10) + "px"; + var childNodes = data.getElementsByTagName("child-node"); + for (var i = 0; i < childNodes.length; i++) + { + if (childNodes[i].nodeType != document.ELEMENT_NODE) + { + continue; + } + var webappRelativePath = childNodes[i].getAttribute("webappRelativePath"); + var fileName = webappRelativePath.replace(/.*\/([^/]+)/, "$1"); + var row = this._createRow(fileName, + webappRelativePath, + childNodes[i].getAttribute("type") == "directory", + eval(childNodes[i].getAttribute("selectable")), + childNodes[i].getAttribute("image"), + "xformsRow" + (i % 2 ? "Even" : "Odd")); + this.contentDiv.appendChild(row); + } + + if (data.getAttribute("error") && data.getAttribute("error").length != 0) + { + this._showStatus(data.getAttribute("error"), true); + } +}, + +_createRow: function(fileName, webappRelativePath, isDirectory, isSelectable, fileTypeImage, rowClass) +{ + var d = this.contentDiv.ownerDocument; + var result = d.createElement("div"); + result.setAttribute("id", fileName + "-row"); + result.setAttribute("webappRelativePath", webappRelativePath); + result.filePickerWidget = this; + dojo.html.setClass(result, "xformsFilePickerRow " + rowClass); + result.onmouseover = function() + { + var prevHover = result.parentNode.hoverNode; + if (prevHover) + { + dojo.html.removeClass(prevHover, "xformsRowHover"); + } + result.parentNode.hoverNode = result; + dojo.html.addClass(result, "xformsRowHover"); + }; + var e = d.createElement("img"); + e.align = "absmiddle"; + e.style.margin = "0px 4px 0px 4px"; + e.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + fileTypeImage); + result.appendChild(e); + + if (isDirectory) + { + e = d.createElement("a"); + e.style.textDecoration = "none"; + e.setAttribute("href", "javascript:void(0)"); + + e.onclick = function() + { + var w = result.filePickerWidget; + w._navigateToNode(result.getAttribute("webappRelativePath")); + return true; + }; + e.appendChild(d.createTextNode(fileName)); + result.appendChild(e); + } + else + { + result.appendChild(d.createTextNode(fileName)); + } + if (isSelectable) + { + e = d.createElement("input"); + e.type = "button"; + e.name = webappRelativePath; + e.value = "Select"; + result.appendChild(e); + + e.style.position = "absolute"; + e.style.right = "10px"; + e.style.top = (.5 * result.offsetHeight) - (.5 * e.offsetHeight) + "px"; + e.onclick = function() + { + var w = result.filePickerWidget; + w.setValue(result.getAttribute("webappRelativePath")); + w._showSelectedValue(); + }; + } + return result; +}, + +_hideAddContent: function() +{ + if (this.addContentDiv) + { + dojo.dom.removeChildren(this.addContentDiv); + dojo.dom.removeNode(this.addContentDiv); + this.addContentDiv = null; + } +}, + +_showAddContent: function(currentPath) +{ + if (this.addContentDiv) + { + return; + } + var d = this.node.ownerDocument; + this.addContentDiv = d.createElement("div"); + dojo.html.setClass(this.addContentDiv, "xformsFilePickerAddContent"); + + if (this.contentDiv.firstChild) + { + this.contentDiv.insertBefore(this.addContentDiv, this.contentDiv.firstChild); + } + else + { + this.contentDiv.appendChild(this.addContentDiv); + } + var e = d.createElement("div"); + e.style.marginLeft = "4px"; + this.addContentDiv.appendChild(e); + e.appendChild(d.createTextNode("Upload: ")); + + var fileInputDiv = d.createElement("div"); + this.addContentDiv.appendChild(fileInputDiv); + var fileInput = d.createElement("input"); + fileInput.type = "file"; + fileInput.widget = this; + fileInput.name = this.uploadId + "_file_input"; + fileInput.size = "35"; + fileInput.setAttribute("webappRelativePath", currentPath); + fileInputDiv.appendChild(fileInput); + fileInputDiv.style.position = "absolute"; + fileInputDiv.style.right = "10px"; + fileInputDiv.style.top = (.5 * this.addContentDiv.offsetHeight) - (.5 * fileInputDiv.offsetHeight) + "px"; + + fileInput.onchange = function(event) + { + event = event || fileInput.ownerDocument.parentWindow.event; + var target = event.target || event.srcElement; + var w = target.widget; + if (w.addContentDiv) + { + var d = w.addContentDiv.ownerDocument; + dojo.dom.removeChildren(w.addContentDiv); + + var fileName = target.value.replace(/.*[\/\\]([^\/\\]+)/, "$1"); + w.addContentDiv.appendChild(d.createTextNode(alfresco.resources["upload"] + ": " + fileName)); + var img = d.createElement("img"); + img.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + + "/images/icons/process_animation.gif"); + img.style.position = "absolute"; + img.style.right = "10px"; + img.style.height = (.5 * w.addContentDiv.offsetHeight) + "px"; + img.style.top = (.25 * w.addContentDiv.offsetHeight) + "px"; + w.addContentDiv.appendChild(img); + } + + alfresco.FilePickerWidget._handleUpload(w.uploadId, + target, + target.getAttribute("webappRelativePath"), + w); + }; +}, + +_upload_completeHandler: function(fileName, webappRelativePath, fileTypeImage, error) +{ + if (error) + { + this._showStatus(error, true); + this._hideAddContent(); + this._showAddContent(webappRelativePath); + } + else + { + var nextRow = dojo.dom.nextElement(this.addContentDiv); + var rowClass = (nextRow + ? ("xformsRow" + (dojo.html.hasClass(nextRow, "xformsRowEven") + ? "Odd" + : "Even")) + : "xformsRowEvent"); + var row = this._createRow(fileName, + webappRelativePath == "/" ? "/" + fileName : webappRelativePath + "/" + fileName, + false, + true /* this is potentially inaccurate - need to add some checks in the backing bean to check selectable */, + fileTypeImage, + rowClass); + this.contentDiv.replaceChild(row, this.addContentDiv); + this.addContentDiv = null; + } +}, + +_closeParentPathMenu: function() +{ + if (this.parentPathMenu) + { + dojo.dom.removeChildren(this.parentPathMenu); + dojo.dom.removeNode(this.parentPathMenu); + this.parentPathMenu = null; + } + this.headerMenuTriggerLink.style.borderStyle = "solid"; +}, + +_openParentPathMenu: function(target, path) +{ + var d = target.ownerDocument; + this.parentPathMenu = d.createElement("div"); + this.parentPathMenu.filePickerWidget = this; + d.currentParentPathMenu = this.parentPathMenu; + + // handler for closing the menu if the mouse is clicked + // outside of the menu + var parentPathMenu_documentClickHandler = function(event) + { + event = event || d.parentWindow.event; + var t = event.target || event.srcElement; + + // always remove - this handler only ever needs to handle a single click + dojo.event.browser.removeListener(d, "click", parentPathMenu_documentClickHandler, true, true); + while (t && t != d) + { + if (t == d.currentParentPathMenu || + t == d.currentParentPathMenu.filePickerWidget.headerMenuTriggerLink) + { + // the click is coming from within the component - ignore it + return true; + } + t = t.parentNode; + } + d.currentParentPathMenu.filePickerWidget._closeParentPathMenu(); + }; + + dojo.event.browser.addListener(d, "click", parentPathMenu_documentClickHandler, true, true); + + dojo.html.setClass(this.parentPathMenu, "xformsFilePickerParentPathMenu"); + + var left = 0; + var top = 0; + var n = target; + do + { + left += n.offsetLeft;// + parseInt(n.style.marginLeft) + parseInt(n.style.borderLeft); + top += n.offsetTop;// + parseInt(n.style.marginTop) + parseInt(n.style.borderTop); + n = n.parentNode; + } + while (n != this.node); + this.parentPathMenu.style.top = top + target.offsetHeight + "px"; + this.parentPathMenu.style.left = left + "px"; + var parentNodes = null; + if (path == "/") + { + parentNodes = [ "/" ]; + } + else + { + parentNodes = path.split("/"); + parentNodes[0] = "/"; + } + + var pathTextDiv = d.createElement("div"); + pathTextDiv.style.fontWeight = "bold"; + pathTextDiv.style.paddingLeft = "5px"; + + pathTextDiv.appendChild(d.createTextNode(alfresco.resources["path"])); + this.parentPathMenu.appendChild(pathTextDiv); + var currentPathNodes = []; + for (var i = 0; i < parentNodes.length; i++) + { + if (i != 0) + { + currentPathNodes.push(parentNodes[i]); + } + var path = i == 0 ? "/" : "/" + currentPathNodes.join("/"); + var parentNodeDiv = d.createElement("div"); + parentNodeDiv.setAttribute("webappRelativePath", path); + this.parentPathMenu.appendChild(parentNodeDiv); + parentNodeDiv.style.display = "block"; + parentNodeDiv.style.paddingLeft = (i * 16) + parseInt(pathTextDiv.style.paddingLeft) + "px"; + parentNodeDiv.style.paddingRight = parseInt(pathTextDiv.style.paddingLeft) + "px"; + parentNodeDiv.style.whiteSpace = "nowrap"; + + var parentNodeImage = d.createElement("img"); + parentNodeImage.align = "absmiddle"; + parentNodeImage.style.marginRight = "4px"; + parentNodeDiv.appendChild(parentNodeImage); + parentNodeImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + + "/images/icons/space_small.gif"); + parentNodeDiv.appendChild(parentNodeImage); + parentNodeDiv.appendChild(d.createTextNode(path)); + dojo.event.connect(parentNodeDiv, + "onclick", + function(event) + { + var w = event.currentTarget; + var path = w.getAttribute("webappRelativePath"); + w = w.parentNode; + w.filePickerWidget._closeParentPathMenu(); + w.filePickerWidget._navigateToNode(path); + }); + } + this.node.appendChild(this.parentPathMenu); +} +}; diff --git a/source/web/scripts/ajax/xforms.js b/source/web/scripts/ajax/xforms.js index 7a958813d8..8dbcd741f9 100644 --- a/source/web/scripts/ajax/xforms.js +++ b/source/web/scripts/ajax/xforms.js @@ -1,4610 +1,4607 @@ -/* - * 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. - */ -//////////////////////////////////////////////////////////////////////////////// -// XForms user interface -// -// This script communicates with the XFormBean to produce and manage an xform. -// -// This script requires mootools.js, dojo.js, tiny_mce.js, -// tiny_mce_wcm_extensions.js, and upload_helper.js to be loaded in advance. -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// initialization -// -// Initiliaze dojo requirements, tinymce, and add a hook to load the xform. -//////////////////////////////////////////////////////////////////////////////// - -djConfig.parseWidgets = false; -dojo.require("dojo.lfx.html"); -alfresco.log = alfresco.constants.DEBUG ? log : Class.empty; - -//////////////////////////////////////////////////////////////////////////////// -// constants -// -// These are the client side declared constants. Others relating to namespaces -// and the webapp context path are expected to be provided by the jsp including -// this script. -//////////////////////////////////////////////////////////////////////////////// -alfresco.xforms.constants.XFORMS_ERROR_DIV_ID = "alfresco-xforms-error"; - -alfresco.xforms.constants.EXPANDED_IMAGE = new Image(); -alfresco.xforms.constants.EXPANDED_IMAGE.src = - alfresco.constants.WEBAPP_CONTEXT + "/images/icons/expanded.gif"; - -alfresco.xforms.constants.COLLAPSED_IMAGE = new Image(); -alfresco.xforms.constants.COLLAPSED_IMAGE.src = - alfresco.constants.WEBAPP_CONTEXT + "/images/icons/collapsed.gif"; - -//////////////////////////////////////////////////////////////////////////////// -// widgets -//////////////////////////////////////////////////////////////////////////////// - -/** - * Base class for all xforms widgets. Each widget has a set of common properties, - * particularly a corresponding xforms node, a node within the browser DOM, - * a parent widget, and state variables. - */ -alfresco.xforms.Widget = new Class({ - initialize: function(xform, xformsNode, domNode) - { - this.xform = xform; - this.xformsNode = xformsNode; - this.id = this.xformsNode.getAttribute("id"); - this._modified = false; - this._valid = true; - var b = this.xform.getBinding(this.xformsNode); - if (b) - { - alfresco.log("adding " + this.id + " to binding " + b.id); - b.widgets[this.id] = this; - } - else - { - alfresco.log("no binding found for " + this.id); - } - this.domNode = domNode || new Element("div"); - this.domNode.setAttribute("id", this.id + "-domNode"); - this.domNode.widget = this; - this.domNode.addClass("xformsItem"); - }, - - ///////////////////////////////////////////////////////////////// - // properties - ///////////////////////////////////////////////////////////////// - - /** A reference to the xform. */ - xform: null, - - /** The xformsNode managed by this widget. */ - xformsNode: null, - - /** The dom node containing the label for this widget. */ - labelNode: null, - - /** The parent widget, or null if this is the root widget. */ - parentWidget: null, - - /** The dom node for this widget. */ - domNode: null, - - /** The dom node containing this widget. */ - domContainer: null, - - /** The parent widget which is using this as a composite. */ - _compositeParent: null, - - ///////////////////////////////////////////////////////////////// - // - ///////////////////////////////////////////////////////////////// - - /** Sets the widget's modified state, as indicated by an XFormsEvent. */ - setModified: function(b) - { - if (this._modified != b) - { - this._modified = b; - this._updateDisplay(false); - if (this.isValidForSubmit()) - { - this.hideAlert(); - } - } - }, - - /** Sets the widget's valid state, as indicated by an XFormsEvent */ - setValid: function(b) - { - if (this._valid != b) - { - this._valid = b; - this._updateDisplay(false); - if (this.isValidForSubmit()) - { - this.hideAlert(); - } - else - { - this.showAlert(); - } - } - }, - - /** - * Heuristic approach to determine if the widget is valid for submit or - * if it's causing an xforms-error. - */ - isValidForSubmit: function() - { - if (typeof this._valid != "undefined" && !this._valid) - { - alfresco.log(this.id + " is invalid"); - return false; - } - if (!this._modified && - this.isRequired() && - this.getInitialValue() == null) - { - alfresco.log(this.id + " is unmodified and required and empty"); - return false; - } - if (this.isRequired() && this.getValue() == null) - { - alfresco.log(this.id + " is required and empty"); - return false; - } - alfresco.log(this.id + " is valid: {" + - "modified: " + this._modified + - ", required: " + this.isRequired() + - ", initial_value: " + this.getInitialValue() + - ", value: " + this.getValue() + "}"); - return true; - }, - - /** Returns the depth of the widget within the widget heirarchy. */ - getDepth: function() - { - var result = 1; - var p = this.parentWidget; - while (p) - { - result++; - p = p.parentWidget; - } - return result; - }, - - /** Returns the root group element */ - getViewRoot: function() - { - var p = this; - while (p.parentWidget) - { - p = p.parentWidget; - } - if (! (p instanceof alfresco.xforms.ViewRoot)) - { - throw new Error("expected root widget " + p + " to be a view root"); - } - return p; - }, - - /** Returns true if the parent is an ancestor of the given parent */ - isAncestorOf: function(parentWidget) - { - var p = this; - while (p.parentWidget) - { - if (p.parentWidget == parentWidget) - { - return true; - } - p = p.parentWidget; - } - return false; - }, - - /** Sets the widget's enabled state, as indicated by an XFormsEvent */ - setEnabled: function(enabled) - { - }, - - /** Returns the widget's enabled state */ - isEnabled: function() - { - return true; - }, - - /** Sets the widget's required state, as indicated by an XFormsEvent */ - setRequired: function(b) - { - if (this._required != b) - { - this._required = b; - this._updateDisplay(false); - } - }, - - /** Indicates if a value is required for the widget. */ - isRequired: function() - { - if (typeof this._required != "undefined") - { - return this._required; - } - var binding = this.xform.getBinding(this.xformsNode); - return binding && binding.isRequired(); - }, - - /** Sets the widget's readonly state, as indicated by an XFormsEvent */ - setReadonly: function(readonly) - { - this._readonly = readonly; - }, - - /** Indicates if the widget's value is readonly. */ - isReadonly: function() - { - if (typeof this._readonly != "undefined") - { - return this._readonly; - } - var binding = this.xform.getBinding(this.xformsNode); - return binding && binding.isReadonly(); - }, - - isVisible: function() - { - return true; - }, - - /** Commits the changed value to the server */ - _commitValueChange: function(value) - { - if (this._compositeParent) - { - this._compositeParent._commitValueChange(value); - } - else - { - this.xform.setXFormsValue(this.id, value || this.getValue()); - } - }, - - /** Sets the value contained by the widget */ - setValue: function(value, forceCommit) - { - if (forceCommit) - { - this.xform.setXFormsValue(this.id, value); - } - }, - - /** Returns the value contained by the widget, or null if none is set */ - getValue: function() - { - return null; - }, - - /** Sets the widget's initial value. */ - setInitialValue: function(value, forceCommit) - { - this._initialValue = - (typeof value == "string" && value.length == 0 ? null : value); - if (forceCommit) - { - this.xform.setXFormsValue(this.id, value); - } - }, - - /** - * Returns the widget's local value, either with a local variable, or by - * looking it up within the model section. - */ - getInitialValue: function() - { - if (typeof this._initialValue != "undefined") - { - return this._initialValue; - } - - var xpath = this._getXPathInInstanceDocument(); - var d = this.xformsNode.ownerDocument; - var contextNode = this.xform.getInstance(); - alfresco.log("locating " + xpath + " in " + contextNode.nodeName); - this._initialValue = _evaluateXPath("/" + xpath, - this.xform.getInstance(), - XPathResult.FIRST_ORDERED_NODE_TYPE); - if (!this._initialValue) - { - alfresco.log("unable to resolve xpath /" + xpath + " for " + this.id); - this._initialValue = null; - } - else - { - this._initialValue = (this._initialValue.nodeType == document.ELEMENT_NODE - ? (this._initialValue.firstChild - ? this._initialValue.firstChild.nodeValue - : null) - : this._initialValue.nodeValue); - if (typeof this._initialValue == "string" && this._initialValue.length == 0) - { - this._initialValue = null; - } - alfresco.log("resolved xpath " + xpath + " to " + this._initialValue); - } - return this._initialValue; - }, - - /** Produces an xpath to the model node within the instance data document. */ - _getXPathInInstanceDocument: function() - { - var binding = this.xform.getBinding(this.xformsNode); - var xpath = ''; - var repeatIndices = this.getRepeatIndices(); - do - { - var s = binding.nodeset; - if (binding.nodeset == '.') - { - binding = binding.parentBinding; - } - if (binding.nodeset.match(/.+\[.+\]/)) - { - s = binding.nodeset.replace(/([^\[]+)\[.*/, "$1"); - s += '[' + (repeatIndices.shift().index) + ']'; - } - xpath = s + (xpath.length != 0 ? '/' + xpath : ""); - binding = binding.parentBinding; - } - while (binding); - return xpath; - }, - - /** Returns a child node by name within the xform. */ - _getChildXFormsNode: function(nodeName) - { - var x = _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - nodeName); - for (var i = 0; i < x.length; i++) - { - if (x[i].parentNode == this.xformsNode) - { - return x[i]; - } - } - return null; - }, - - /** Returns the widget's label. */ - getLabel: function() - { - var node = this._getChildXFormsNode("label"); - var result = node ? node.firstChild.nodeValue : ""; - if (alfresco.constants.DEBUG) - { - result += " [" + this.id + "]"; - } - return result; - }, - - /** Returns the widget's alert text. */ - getAlert: function() - { - var node = this._getChildXFormsNode("alert"); - return node ? node.firstChild.nodeValue : ""; - }, - - /** Returns the widget's alert text. */ - getHint: function() - { - var node = this._getChildXFormsNode("hint"); - return node ? node.firstChild.nodeValue : null; - }, - - /** Makes the label red. */ - showAlert: function() - { - if (!this.labelNode.hasClass("xformsItemLabelSubmitError")) - { - this.labelNode.addClass("xformsItemLabelSubmitError"); - } - }, - - /** Restores the label to its original color. */ - hideAlert: function() - { - if (this.labelNode.hasClass("xformsItemLabelSubmitError")) - { - this.labelNode.removeClass("xformsItemLabelSubmitError"); - } - }, - - /** Returns the value of the appearance attribute for widget */ - getAppearance: function() - { - var result = (this.xformsNode.getAttribute("appearance") || - this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance")); - return result == null || result.length == 0 ? null : result; - }, - - /** Updates the display of the widget. This is intended to be overridden. */ - _updateDisplay: function(recursively) - { - }, - - /** Destroy the widget and any resources no longer needed. */ - _destroy: function() - { - alfresco.log("destroying " + this.id); - }, - - /** - * Returns an array of RepeatIndexDatas corresponding to all enclosing repeats. - * The closest repeat will be at index 0. - */ - getRepeatIndices: function() - { - var result = []; - var w = this; - while (w.parentWidget) - { - if (w.parentWidget instanceof alfresco.xforms.Repeat) - { - result.push(new alfresco.xforms.RepeatIndexData(w.parentWidget, - w.parentWidget.getChildIndex(w) + 1)); - } - w = w.parentWidget; - } - return result; - }, - - /** - */ - getParentGroups: function(appearance) - { - var result = []; - var w = this; - while (w.parentWidget) - { - if (w.parentWidget instanceof alfresco.xforms.AbstractGroup) - { - if (appearance && w.parentWidget.getAppearance() == appearance) - { - result.push(w.parentWidget); - } - } - w = w.parentWidget; - } - return result; - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// widgets for atomic types -//////////////////////////////////////////////////////////////////////////////// - -/** The file picker widget which handles xforms widget xf:upload. */ -alfresco.xforms.FilePicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode, params) - { - this.parent(xform, xformsNode); - this._selectableTypes = "selectable_types" in params ? params["selectable_types"].split(",") : null; - this._filterMimetypes = "filter_mimetypes" in params ? params["filter_mimetypes"].split(",") : []; - this._folderRestriction = "folder_restriction" in params ? params["folder_restriction"] : null; - this._savedSearchName = "saved_search_name" in params ? params["saved_search_name"] : null; - this._savedSearchContext = "saved_search_context" in params ? params["saved_search_context"] : null; - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - this.domNode.addClass("xformsFilePicker"); - attach_point.appendChild(this.domNode); - //XXXarielb support readonly and disabled - this.widget = new alfresco.FilePickerWidget(this.id, - this.domNode, - this.getInitialValue(), - false, - this._filePicker_changeHandler.bindAsEventListener(this), - null /* cancel is ignored */, - this._filePicker_resizeHandler.bindAsEventListener(this), - this._selectableTypes, - this._filterMimetypes, - this._folderRestriction, - this._savedSearchName, - this._savedSearchContext); - this.widget.render(); - }, - - getValue: function() - { - return this.widget.getValue(); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this.widget.setValue(value); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _filePicker_changeHandler: function(fpw) - { - this._commitValueChange(); - }, - - _filePicker_resizeHandler: function(fpw) - { - this.domContainer.style.height = - Math.max(fpw.node.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - } -}); - -/** The textfield widget which handle xforms widget xf:input with any string or numerical type */ -alfresco.xforms.TextField = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("input", { type: "text" })); - this._maxLength = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxLength") - ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxLength")) - : -1); - this._length = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":length") - ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":length")) - : -1); - - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue() || ""; - attach_point.appendChild(this.domNode); - - this.widget = this.domNode; - this.widget.setAttribute("value", initial_value); - if (this._maxLength >= 0 || this._length >= 0) - { - this.widget.setAttribute("maxlength", this._maxLength >= 0 ? this._maxLength : this._length); - this.widget.style.maxWidth = "100%"; - this.widget.setAttribute("size", this._maxLength >= 0 ? this._maxLength : this._length); - } - else if (this.getAppearance() == "full") - { - var borderWidth = (this.widget.offsetWidth - this.widget.clientWidth); - var marginRight = 2; - this.widget.style.marginRight = marginRight + "px"; - this.widget.style.width = (((attach_point.offsetWidth - borderWidth - marginRight) / attach_point.offsetWidth) * 100) + "%"; - this.widget.style.minWidth = "50px"; - } - - if (this.isReadonly()) - { - this.widget.setAttribute("readonly", this.isReadonly()); - this.widget.setAttribute("disabled", this.isReadonly()); - } - else - { - this.widget.onblur = this._widget_changeHandler.bindAsEventListener(this); - } - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this.widget.value = value; - } - }, - - getValue: function() - { - return (this.widget.value != null && this.widget.value.length == 0 - ? null - : this.widget.value); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _widget_changeHandler: function(event) - { - this._commitValueChange(); - } -}); - -/** The number range widget which handle xforms widget xf:range with any numerical type */ -alfresco.xforms.NumericalRange = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - dojo.require("dojo.widget.Slider"); - this._fractionDigits = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":fractionDigits") - ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":fractionDigits")) - : -1); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue() || ""; - attach_point.appendChild(this.domNode); - var sliderDiv = document.createElement("div"); - sliderDiv.style.fontWeight = "bold"; - sliderDiv.style.marginBottom = "5px"; - this.domNode.appendChild(sliderDiv); - - var minimum = Number(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":start")); - var maximum = Number(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":end")); - var snapValues = 0; - if (this._fractionDigits == 0) - { - snapValues = maximum - minimum + 1; - } - sliderDiv.appendChild(document.createTextNode(minimum)); - - var sliderWidgetDiv = document.createElement("div"); - sliderDiv.appendChild(sliderWidgetDiv); - this.widget = dojo.widget.createWidget("SliderHorizontal", - { - initialValue: initial_value, - minimumX: minimum, - maximumX: maximum, - showButtons: false, - activeDrag: false, - snapValues: snapValues - }, - sliderWidgetDiv); - sliderDiv.appendChild(document.createTextNode(maximum)); - - this.currentValueDiv = document.createElement("div"); - this.domNode.appendChild(this.currentValueDiv); - this.currentValueDiv.appendChild(document.createTextNode("Value: " + initial_value)); - - dojo.event.connect(this.widget, - "onValueChanged", - this, - this._hSlider_valueChangedHandler); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this.widget.setValue(value); - } - }, - - getValue: function() - { - return this.widget.getValue(); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _hSlider_valueChangedHandler: function(value) - { - if (this._fractionDigits >= 0) - { - value = Math.round(value * Math.pow(10, this._fractionDigits)) / Math.pow(10, this._fractionDigits); - } - this.currentValueDiv.replaceChild(document.createTextNode("Value: " + value), - this.currentValueDiv.firstChild); - if (!this.widget._isDragInProgress) - { - this._commitValueChange(); - } - } -}); - -/** The text area widget handles xforms widget xf:textarea with appearance minimal */ -alfresco.xforms.PlainTextEditor = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("textarea")); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - attach_point.appendChild(this.domNode); - this.domNode.addClass("xformsTextArea"); - var initialValue = this.getInitialValue() || ""; - this.widget = this.domNode; - this.widget.appendChild(document.createTextNode(initialValue)); - if (this.isReadonly()) - { - this.widget.setAttribute("readonly", this.isReadonly()); - } - var borderWidth = (this.widget.offsetWidth - this.widget.clientWidth); - var marginRight = 2; - this.widget.style.marginRight = marginRight + "px"; - this.widget.style.width = (((attach_point.offsetWidth - borderWidth - marginRight) / attach_point.offsetWidth) * 100) + "%"; - this.widget.onchange =this._textarea_changeHandler.bindAsEventListener(this); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this.widget.value = value; - } - }, - - getValue: function() - { - return this.widget.value; - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _textarea_changeHandler: function(event) - { - this._commitValueChange(); - } -}); - -/** The textfield widget which handle xforms widget xf:textarea. with appearance full or compact */ -alfresco.xforms.RichTextEditor = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode, params) - { - this.parent(xform, xformsNode); - this._focused = false; - this._params = params; - this._oldValue = null; - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - _removeTinyMCE: function() - { - var value = this.getValue(); //tinyMCE.getContent(this.id); - if (value != this._oldValue) - { - alfresco.log("commitValueChange from _removeTinyMCE [" + value + "]"); - this._commitValueChange(value); - this._oldValue = value; - } - tinyMCE.removeMCEControl(this.id); - this._focused = false; - }, - - _createTinyMCE:function() - { - if (alfresco.xforms.RichTextEditor.currentInstance && - alfresco.xforms.RichTextEditor.currentInstance != this) - { - alfresco.xforms.RichTextEditor.currentInstance._removeTinyMCE(); - } - - alfresco.xforms.RichTextEditor.currentInstance = this; - - for (var i in alfresco.constants.TINY_MCE_DEFAULT_SETTINGS) - { - if (!(i in this._params)) - { - this._params[i] = alfresco.constants.TINY_MCE_DEFAULT_SETTINGS[i]; - } - } - for (var i in this._params) - { - if (i in tinyMCE.settings) - { - alfresco.log("setting tinyMCE.settings[" + i + "] = " + this._params[i]); - tinyMCE.settings[i] = this._params[i]; - } - } - tinyMCE.settings.height = this._params["height"] ? parseInt(this._params["height"]) : -1; - tinyMCE.settings.auto_focus = this.id; - tinyMCE.addMCEControl(this.widget, this.id); - - tinyMCE.getInstanceById(this.id).getWin().focus(); - var editorDocument = tinyMCE.getInstanceById(this.id).getDoc(); - editorDocument.widget = this; - - tinyMCE.addEvent(editorDocument, - window.ie ? "beforedeactivate" : "blur", - this._tinyMCE_blurHandler); - tinyMCE.addEvent(editorDocument, "focus", this._tinyMCE_focusHandler); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - attach_point.appendChild(this.domNode); - this.domNode.addClass("xformsTextArea"); - if (this._params.height) - { - this.domNode.setStyle("height", parseInt(this._params["height"]) + "px"); - } - this.widget = new Element("div"); - this.domNode.appendChild(this.widget); - this.widget.addClass("xformsTextArea"); - if (this._params["height"]) - { - this.widget.setStyle("height", parseInt(this._params["height"]) + "px"); - } - this.widget.style.border = "2px inset #f0f0f0"; - this.widget.style.marginRight = "2px"; - this.widget.style.overflow = "auto"; - this._oldValue = this.getInitialValue() || ""; - this.widget.innerHTML = this._oldValue; - - $each(this.widget.getElementsByTagName("img"), - function(img, index) - { - if (img.getAttribute("src") && img.getAttribute("src").match("^/")) - { - img.setAttribute("src", alfresco.constants.AVM_WEBAPP_URL + img.getAttribute("src")); - } - }); - if (!this.isReadonly()) - { - this.widget.onmouseover = this._div_mouseoverHandler.bindAsEventListener(this); - } - }, - - setValue: function(value, forceCommit) - { - if (value != this._oldValue || forceCommit) - { - if (alfresco.xforms.RichTextEditor.currentInstance == this) - { - tinyMCE.selectedInstance = tinyMCE.getInstanceById(this.id); - try - { - tinyMCE.setContent(value); - } - catch (e) - { - //XXXarielb figure this out - getting intermittent errors in IE. - alfresco.log(e); - } - } - else - { - this.widget.innerHTML = value; - } - } - this.parent(value, forceCommit); - }, - - getValue: function() - { - var result = (alfresco.xforms.RichTextEditor.currentInstance == this - ? tinyMCE.getContent(this.id) - : this.widget.innerHTML); - result = result.replace(new RegExp(alfresco.constants.AVM_WEBAPP_URL, "g"), ""); - return result; - }, - - setReadonly: function(readonly) - { - this.parent(readonly); - if (readonly && alfresco.xforms.RichTextEditor.currentInstance == this) - { - this._removeTinyMCE(); - } - }, - - _destroy: function() - { - this.parent(); - if (!this.isReadonly()) - { - alfresco.log("removing mce control " + this.id); - tinyMCE.removeMCEControl(this.id); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _tinyMCE_blurHandler: function(event) - { - if (event.type == "beforedeactivate") - { - event.target = event.srcElement.ownerDocument; - } - var widget = event.target.widget; - var value = widget.getValue(); - if (value != widget._oldValue) - { - alfresco.log("commitValueChange from _tinyMCE_blurHandler [" + value + "]"); - widget._commitValueChange(value); - widget._oldValue = value; - } - widget._focused = false; - }, - - _tinyMCE_focusHandler: function(event) - { - var widget = event.target.widget; - var repeatIndices = widget.getRepeatIndices(); - if (repeatIndices.length != 0 && !widget._focused) - { - var r = repeatIndices[repeatIndices.length - 1].repeat; - var p = widget; - while (p && p.parentWidget != r) - { - if (p.parentWidget instanceof alfresco.xforms.Repeat) - { - throw new Error("unexpected parent repeat " + p.parentWidget.id); - } - p = p.parentWidget; - } - if (!p) - { - throw new Error("unable to find parent repeat " + r.id + - " of " + widget.id); - } - repeatIndices[repeatIndices.length - 1].repeat.setFocusedChild(p); - } - widget._focused = true; - }, - - _div_mouseoverHandler: function(event) - { - if (!this.hoverLayer) - { - this.hoverLayer = new Element("div"); - this.hoverLayer.addClass("xformsRichTextEditorHoverLayer"); - this.hoverLayer.setText(alfresco.resources["click_to_edit"]); - } - if (this.hoverLayer.parentNode != this.widget) - { - this.widget.appendChild(this.hoverLayer); - - this.hoverLayer.style.lineHeight = this.hoverLayer.offsetHeight + "px"; - this.hoverLayer.setOpacity(.8); - this.hoverLayer.onmouseout = this._hoverLayer_mouseoutHandler.bindAsEventListener(this); - this.hoverLayer.onclick = this._hoverLayer_clickHandler.bindAsEventListener(this); - } - }, - - _hoverLayer_mouseoutHandler: function(event) - { - if (this.hoverLayer.parentNode == this.widget) - { - this.hoverLayer.setOpacity(1); - this.widget.removeChild(this.hoverLayer); - } - }, - - _hoverLayer_clickHandler: function(event) - { - if (this.hoverLayer.parentNode == this.widget) - { - this.hoverLayer.setOpacity(1); - this.widget.removeChild(this.hoverLayer); - this._createTinyMCE(); - } - } -}); - -/** The currently rendered rich text editor instance */ -alfresco.xforms.RichTextEditor.currentInstance = null; - -/** - * Reads the widget configuration to determine which plugins will - * be needed by tinymce. All plugins must be loaded into tinymce at - * startup so they must be accumulated in advance. - */ -alfresco.xforms.RichTextEditor.determineNecessaryTinyMCEPlugins = function(config) -{ - var result = []; - for (var widget in config) - { - for (var schemaType in config[widget]) - { - for (var appearance in config[widget][schemaType]) - { - if (config[widget][schemaType][appearance].className == "alfresco.xforms.RichTextEditor" && - config[widget][schemaType][appearance].params && - config[widget][schemaType][appearance].params.plugins) - { - alfresco.log("found plugins definition " + config[widget][schemaType][appearance].params.plugins + - " for text editor at config[" + widget + "][" + schemaType + "][" + appearance + "]"); - var plugins = config[widget][schemaType][appearance].params.plugins.split(","); - for (var p = 0; p < plugins.length; p++) - { - if (result.indexOf(plugins[p]) < 0) - { - result.push(plugins[p]); - } - } - } - } - } - } - return result.join(","); -} - -/** Base class for all select widgets. */ -alfresco.xforms.AbstractSelectWidget = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode, domNode) - { - this.parent(xform, xformsNode, domNode); - }, - - ///////////////////////////////////////////////////////////////// - // methods - ///////////////////////////////////////////////////////////////// - - /** - * Returns the possible item values for the select control as an array - * of anonymous objects with properties id, label, value, and valid. - */ - _getItemValues: function() - { - var binding = this.xform.getBinding(this.xformsNode); - var values = _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "item"); - var result = []; - for (var i = 0; i < values.length; i++) - { - var label = _getElementsByTagNameNS(values[i], - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "label")[0]; - label = label.firstChild.nodeValue; - var value = _getElementsByTagNameNS(values[i], - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "value")[0]; - var valueText = value.firstChild.nodeValue; - var itemId = value.getAttribute("id"); - var valid = true; - if (binding.constraint) - { - if (!window.ie) - { - valid = _evaluateXPath(binding.constraint, value, XPathResult.BOOLEAN_TYPE); - if (alfresco.constants.DEBUG) - { - alfresco.log("evaludated constraint " + binding.constraint + - " on " + value + " to " + valid); - } - } - else - { - valid = !(valueText == label && valueText.match(/^\[.+\]$/)); - } - } - result.push({ - id: itemId, - label: valid ? label : "", - value: valid ? valueText : "_invalid_value_", - valid: valid - }); - - if (alfresco.constants.DEBUG) - { - alfresco.log("values["+ i + "] = {id: " + result[i].id + - ",label: " + result[i].label + ",value: " + result[i].value + - ",valid: " + result[i].valid + "}"); - } - } - return result; - } -}); - -/** - * Handles xforms widget xf:select. Produces either a multiselect list or a set of - * checkboxes depending on the number of inputs. - */ -alfresco.xforms.CheckboxSelect = alfresco.xforms.AbstractSelectWidget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var values = this._getItemValues(); - var initial_value = this.getInitialValue(); - initial_value = initial_value ? initial_value.split(' ') : []; - this._selectedValues = []; - this.widget = this.domNode; - attach_point.appendChild(this.domNode); - for (var i = 0; i < values.length; i++) - { - var checkboxDiv = document.createElement("div"); - checkboxDiv.style.lineHeight = "16px"; - this.widget.appendChild(checkboxDiv); - - var checkbox = new Element("input"); - checkbox.setAttribute("id", this.id + "_" + i + "-widget"); - checkbox.setAttribute("name", this.id + "_" + i + "-widget"); - checkbox.setAttribute("type", "checkbox"); - checkbox.setAttribute("value", values[i].value); - if (initial_value.indexOf(values[i].value) != -1) - { - this._selectedValues.push(values[i].value); - checkbox.checked = true; - } - checkboxDiv.appendChild(checkbox); - checkboxDiv.appendChild(document.createTextNode(values[i].label)); - checkbox.onclick = this._checkbox_clickHandler.bindAsEventListener(this); - } - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this._selectedValues = value.split(' '); - var checkboxes = this.widgets.getElementsByTagName("input"); - for (var i = 0; i < checkboxes.length; i++) - { - checkboxes[i].checked = - this._selectedValues.indexOf(checkboxes[i].getAttribute("value")) != -1; - } - } - }, - - getValue: function() - { - return this._selectedValues.length == 0 ? null : this._selectedValues.join(" "); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _checkbox_clickHandler: function(event) - { - this._selectedValues = []; - var all_checkboxes = this.widget.getElementsByTagName("input"); - for (var i = 0; i < all_checkboxes.length; i++) - { - if (all_checkboxes[i] && all_checkboxes[i].checked) - { - this._selectedValues.push(all_checkboxes[i].getAttribute("value")); - } - } - this._commitValueChange(); - } -}); - -/** - * Handles xforms widget xf:select. Produces either a multiselect list or a set of - * checkboxes depending on the number of inputs. - */ -alfresco.xforms.ListSelect = alfresco.xforms.AbstractSelectWidget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("select")); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var values = this._getItemValues(); - var initial_value = this.getInitialValue(); - initial_value = initial_value ? initial_value.split(' ') : []; - this._selectedValues = []; - attach_point.appendChild(this.domNode); - this.widget = this.domNode; - this.widget.setAttribute("multiple", true); - attach_point.appendChild(this.widget); - for (var i = 0; i < values.length; i++) - { - var option = document.createElement("option"); - option.appendChild(document.createTextNode(values[i].label)); - option.setAttribute("value", values[i].value); - if (initial_value.indexOf(values[i].value) != -1) - { - this._selectedValues.push(values[i].value); - option.selected = true; - } - this.widget.appendChild(option); - } - this.widget.onblur = this._list_changeHandler.bindAsEventListener(this); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this._selectedValues = value.split(' '); - var options = this.widgets.getElementsByTagName("option"); - for (var i = 0; i < options.length; i++) - { - options[i].selected = - this._selectedValues.indexOf(options[i].getAttribute("value")) != -1; - } - } - }, - - getValue: function() - { - return this._selectedValues.length == 0 ? null : this._selectedValues.join(" "); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _list_changeHandler: function(event) - { - this._selectedValues = []; - for (var i = 0; i < event.target.options.length; i++) - { - if (event.target.options[i].selected) - { - this._selectedValues.push(event.target.options[i].getAttribute("value")); - } - } - this._commitValueChange(); - } -}); - -/** - * Handles xforms widget xf:select1. Produces either a combobox or a set of - * radios depending on the number of inputs. - */ -alfresco.xforms.RadioSelect1 = alfresco.xforms.AbstractSelectWidget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var values = this._getItemValues(); - var initial_value = this.getInitialValue(); - this.widget = this.domNode; - attach_point.appendChild(this.domNode); - for (var i = 0; i < values.length; i++) - { - if (!values[i].valid) - { - // always skip the invalid values for radios - continue; - } - - var radio_div = document.createElement("div"); - radio_div.style.lineHeight = "16px"; - this.widget.appendChild(radio_div); - var radio = new Element("input"); - radio.setAttribute("id", this.id + "-widget"); - radio.setAttribute("name", this.id + "-widget"); - radio.setAttribute("type", "radio"); - radio_div.appendChild(radio); - radio_div.appendChild(document.createTextNode(values[i].label)); - - radio.setAttribute("value", values[i].value); - if (values[i].value == initial_value) - { - this._selectedValue = initial_value; - radio.checked = true; - } - if (this.isReadonly()) - { - radio.setAttribute("disabled", true); - } - radio.onclick = this._radio_clickHandler.bindAsEventListener(this); - } - this.widget.style.height = this.widget.offsetHeight + "px"; - }, - - /** */ - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this._selectedValue = value; - var radios = this.widget.getElementsByTagName("input"); - for (var i = 0; i < radios.length; i++) - { - radios[i].checked = radios[i].getAttribute("value") == this._selectedValue; - } - } - }, - - getValue: function() - { - return this._selectedValue; - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _radio_clickHandler: function(event) - { - if (!event.target.checked) - { - var all_radios = this.widget.getElementsByTagName("input"); - for (var i = 0; i < all_radios.length; i++) - { - if (all_radios[i].name == event.target.name) - { - all_radios[i].checked = event.target == all_radios[i]; - } - } - } - this._selectedValue = event.target.value; - this._commitValueChange(); - } -}); - -/** - * Handles xforms widget xf:select1. Produces either a combobox or a set of - * radios depending on the number of inputs. - */ -alfresco.xforms.ComboboxSelect1 = alfresco.xforms.AbstractSelectWidget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("select")); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var values = this._getItemValues(); - var initial_value = this.getInitialValue(); - this.domNode = new Element("select"); - attach_point.appendChild(this.domNode); - this.widget = this.domNode; - for (var i = 0; i < values.length; i++) - { - if (initial_value && !values[i].valid) - { - // skip the invalid value if we have a default value - continue; - } - var option = new Element("option"); - this.widget.appendChild(option); - option.appendChild(document.createTextNode(values[i].label)); - option.setAttribute("value", values[i].value); - if (values[i].value == initial_value) - { - this._selectedValue = initial_value; - option.selected = true; - } - - if (this.isReadonly()) - { - this.widget.setAttribute("disabled", true); - } - } - this.widget.onchange = this._combobox_changeHandler.bindAsEventListener(this); - }, - - /** */ - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this._selectedValue = value; - var options = this.widget.getElementsByTagName("option"); - for (var i = 0; i < options.length; i++) - { - options[i].selected = options[i].getAttribute("value") == this._selectedValue; - } - } - }, - - getValue: function() - { - return this._selectedValue; - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _combobox_changeHandler: function(event) - { - this._selectedValue = event.target.options[event.target.selectedIndex].value; - this._commitValueChange(); - } -}); - -/** - * Handles xforms widget xf:select1 with a type of boolean. - */ -alfresco.xforms.Checkbox = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, - xformsNode, - new Element("input", { type: "checkbox" })); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue() == "true"; - attach_point.appendChild(this.domNode); - this.widget = this.domNode; - - if (initial_value) - { - this.widget.setAttribute("checked", true); - } - if (this.isReadonly()) - { - this.widget.setAttribute("disabled", true); - } - this.widget.onclick = this._checkbox_clickHandler.bindAsEventListener(this); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - this.widget.checked = value == "true"; - } - }, - - getValue: function() - { - return this.widget.checked; - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _checkbox_clickHandler: function(event) - { - this._commitValueChange(); - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// widgets for date types -//////////////////////////////////////////////////////////////////////////////// - -/** The date picker widget which handles xforms widget xf:input with type xf:date */ -alfresco.xforms.DatePicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - this._minInclusive = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":minInclusive") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":minInclusive") - : null); - this._maxInclusive = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxInclusive") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxInclusive") - : null); - - dojo.require("dojo.date.format"); - // XXXarielb - change to a static - this._noValueSet = (alfresco.resources["eg"] + " " + - dojo.date.format(new Date(), - {datePattern: alfresco.xforms.constants.DATE_FORMAT, - selector: 'dateOnly'})); - }, - - _createPicker: function() - { - dojo.require("dojo.widget.DatePicker"); - var datePickerDiv = document.createElement("div"); - this.domNode.parentNode.appendChild(datePickerDiv); - - var dp_initial_value = this.getValue() || null; //dojo.date.toRfc3339(new Date()); - var datePickerProperties = { value: dp_initial_value }; - if (this._minInclusive) - { - datePickerProperties.startDate = this._minInclusive; - } - if (this._maxInclusive) - { - datePickerProperties.endDate = this._maxInclusive; - } - - this.widget.picker = dojo.widget.createWidget("DatePicker", - datePickerProperties, - datePickerDiv); - this.domContainer.style.height = - Math.max(this.widget.picker.domNode.offsetHeight + - this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - - dojo.event.connect(this.widget.picker, - "onValueChanged", - this, - this._datePicker_valueChangedHandler); - }, - - _destroyPicker: function() - { - if (this.widget.picker) - { - this.domNode.parentNode.removeChild(this.widget.picker.domNode); - this.widget.picker = null; - this.domContainer.style.height = - Math.max(this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-bottom").toInt() + - this.domNode.parentNode.getStyle("margin-top").toInt(), - 20) + "px"; - } - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue(); - attach_point.appendChild(this.domNode); - this.widget = new Element("input", { "id": this.id + "-widget", "type": "text"}); - if (initial_value) - { - var jsDate = dojo.date.fromRfc3339(initial_value); - this.widget.setAttribute("value", - dojo.date.format(jsDate, - {datePattern: alfresco.xforms.constants.DATE_FORMAT, - selector: 'dateOnly'})); - } - else - { - this.widget.setAttribute("value", this._noValueSet); - this.widget.addClass("xformsGhostText"); - } - if (this.isReadonly()) - { - this.widget.setAttribute("disabled", true); - } - this.domNode.appendChild(this.widget); - - var expandoImage = new Element("img"); - expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); - expandoImage.align = "absmiddle"; - expandoImage.style.margin = "0px 5px"; - - this.domNode.appendChild(expandoImage); - - if (!this.isReadonly()) - { - expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); - this.widget.onfocus = this._dateTextBox_focusHandler.bindAsEventListener(this); - this.widget.onchange = this._dateTextBox_changeHandler.bindAsEventListener(this); - } - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - var jsDate = dojo.date.fromRfc3339(value); - this.widget.value = dojo.date.format(jsDate, - {datePattern: alfresco.xforms.constants.DATE_FORMAT, - selector: 'dateOnly'}); - this.widget.removeClass("xformsGhostText"); - } - }, - - getValue: function() - { - if (this.widget.value == null || - this.widget.value.length == 0 || - this.widget.value == this._noValueSet) - { - return null; - } - else - { - var jsDate = dojo.date.parse(this.widget.value, - {datePattern: alfresco.xforms.constants.DATE_FORMAT, - selector: 'dateOnly'}); - return dojo.date.toRfc3339(jsDate, "dateOnly"); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _dateTextBox_focusHandler: function(event) - { - this._destroyPicker(); - }, - - _dateTextBox_changeHandler: function(event) - { - this._commitValueChange(); - }, - - _datePicker_valueChangedHandler: function(date) - { - var rfcDate = dojo.date.toRfc3339(date, "dateOnly"); - this._destroyPicker(); - this.setValue(rfcDate); - this._commitValueChange(); - }, - - _expando_clickHandler: function() - { - if (this.widget.picker) - { - this._destroyPicker(); - } - else - { - this._createPicker(); - } - } -}); - -/** The date picker widget which handles xforms widget xf:input with type xf:date */ -alfresco.xforms.TimePicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - dojo.require("dojo.date.format"); - this._noValueSet = (alfresco.resources["eg"] + " " + - dojo.date.format(new Date(), - {timePattern: alfresco.xforms.constants.TIME_FORMAT, - selector: "timeOnly"})); - this._xformsFormat = "HH:mm:ss.S"; - }, - /** */ - _createPicker: function() - { - dojo.require("dojo.widget.TimePicker"); - var timePickerDiv = document.createElement("div"); - this.domNode.appendChild(timePickerDiv); - var jsDate = (this.getValue() - ? dojo.date.parse(this.getValue(), - {timePattern: this._xformsFormat, - selector: "timeOnly"}) - : new Date()); - this.widget.picker = dojo.widget.createWidget("TimePicker", - { - value: jsDate - }, - timePickerDiv); - this.widget.picker.anyTimeContainerNode.innerHTML = ""; - - // don't let it float - it screws up layout somehow - this.widget.picker.domNode.style.cssFloat = "none"; - this.domContainer.style.height = - Math.max(this.widget.picker.domNode.offsetHeight + - this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - dojo.event.connect(this.widget.picker, - "onValueChanged", - this, - this._timePicker_valueChangedHandler); - }, - - _destroyPicker: function() - { - if (this.widget.picker) - { - this.domNode.removeChild(this.widget.picker.domNode); - this.widget.picker = null; - this.domContainer.style.height = - Math.max(this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - } - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue(); - - attach_point.appendChild(this.domNode); - this.widget = new Element("input", { "id": this.id + "-widget", "type": "text" }); - if (initial_value) - { - var jsDate = dojo.date.parse(initial_value, {timePattern: this._xformsFormat, selector: "timeOnly"}); - this.widget.setAttribute("value", - dojo.date.format(jsDate, - {timePattern: alfresco.xforms.constants.TIME_FORMAT, - selector: "timeOnly"})); - } - else - { - this.widget.setAttribute("value", this._noValueSet); - this.widget.addClass("xformsGhostText"); - } - if (this.isReadonly()) - { - this.widget.setAttribute("disabled", true); - } - this.domNode.appendChild(this.widget); - - var expandoImage = new Element("img"); - expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); - expandoImage.align = "absmiddle"; - expandoImage.style.margin = "0px 5px"; - - this.domNode.appendChild(expandoImage); - - if (!this.isReadonly()) - { - expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); - this.widget.onfocus = this._timeTextBox_focusHandler.bindAsEventListener(this); - this.widget.onchange = this._timeTextBox_changeHandler.bindAsEventListener(this); - } - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - var jsDate = dojo.date.parse(value, {timePattern: this._xformsFormat, selector: "timeOnly"}); - this.widget.value = dojo.date.format(jsDate, - {timePattern: alfresco.xforms.constants.TIME_FORMAT, - selector: "timeOnly"}); - this.widget.removeClass("xformsghosttext"); - } - }, - - getValue: function() - { - if (this.widget.value == null || - this.widget.value.length == 0 || - this.widget.value == this._noValueSet) - { - return null; - } - else - { - var jsDate = dojo.date.parse(this.widget.value, - {timePattern: alfresco.xforms.constants.TIME_FORMAT, - selector: "timeOnly"}); - return dojo.date.format(jsDate, {timePattern: this._xformsFormat, selector: "timeOnly"}); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _timeTextBox_focusHandler: function(event) - { - this._destroyPicker(); - }, - - _timeTextBox_changeHandler: function(event) - { - this._commitValueChange(); - }, - - _timePicker_valueChangedHandler: function(date) - { - var xfDate = dojo.date.format(date, {timePattern: this._xformsFormat, selector: "timeOnly"}); - this.setValue(xfDate); - this._commitValueChange(); - }, - - _expando_clickHandler: function() - { - if (this.widget.picker) - { - this._destroyPicker(); - } - else - { - this._createPicker(); - } - } -}); - -/** The date time picker widget which handles xforms widget xf:input with type xf:datetime */ -alfresco.xforms.DateTimePicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - dojo.require("dojo.date.format"); - this._noValueSet = (alfresco.resources["eg"] + " " + - dojo.date.format(new Date(), - {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, - selector: "dateOnly"})); - }, - - /** */ - _createPicker: function() - { - dojo.require("dojo.widget.DatePicker"); - dojo.require("dojo.widget.TimePicker"); - - this._pickerDiv = document.createElement("div"); - this._pickerDiv.style.position = "relative"; - this._pickerDiv.style.width = this.widget.offsetWidth + "px"; - this.domNode.appendChild(this._pickerDiv); - - var datePickerDiv = document.createElement("div"); - datePickerDiv.style.position = "absolute"; - datePickerDiv.style.left = "0px"; - datePickerDiv.style.top = "0px"; - this._pickerDiv.appendChild(datePickerDiv); - - var dp_initial_value = this.getValue() || dojo.date.toRfc3339(new Date()); - this.widget.datePicker = dojo.widget.createWidget("DatePicker", - { - value: dp_initial_value - }, - datePickerDiv); - var timePickerDiv = document.createElement("div"); - timePickerDiv.style.position = "absolute"; - timePickerDiv.style.right = "0px"; - timePickerDiv.style.top = "0px"; - this._pickerDiv.appendChild(timePickerDiv); - - var jsDate = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); - this.widget.timePicker = dojo.widget.createWidget("TimePicker", - { - value: jsDate - }, - timePickerDiv); - this.widget.timePicker.anyTimeContainerNode.innerHTML = ""; - - // don't let it float - it screws up layout somehow - this.widget.timePicker.domNode.style.cssFloat = "none"; - this._pickerDiv.style.height = Math.max(this.widget.timePicker.domNode.offsetHeight, - this.widget.datePicker.domNode.offsetHeight); - this.domContainer.style.height = - Math.max(this._pickerDiv.offsetHeight + - this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - dojo.event.connect(this.widget.datePicker, - "onValueChanged", - this, - this._datePicker_valueChangedHandler); - dojo.event.connect(this.widget.timePicker, - "onValueChanged", - this, - this._timePicker_valueChangedHandler); - }, - - _destroyPicker: function() - { - if (this._pickerDiv) - { - this.domNode.removeChild(this._pickerDiv); - this.widget.datePicker = null; - this.widget.timePicker = null; - this._pickerDiv = null; - this.domContainer.style.height = - Math.max(this.widget.offsetHeight + - this.domNode.parentNode.getStyle("margin-top").toInt() + - this.domNode.parentNode.getStyle("margin-bottom").toInt(), - 20) + "px"; - } - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - var initial_value = this.getInitialValue(); - - attach_point.appendChild(this.domNode); - this.widget = new Element("input", { "id": this.id + "-widget", "type": "text" }); - if (initial_value) - { - var jsDate = dojo.date.fromRfc3339(initial_value); - this.widget.setAttribute("value", - dojo.date.format(jsDate, - {timePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, - selector: "timeOnly"})); - } - else - { - this.widget.setAttribute("value", this._noValueSet); - this.widget.addClass("xformsGhostText"); - } - this.domNode.appendChild(this.widget); - this.widget.style.width = (3 * this.widget.offsetWidth) + "px"; - - var expandoImage = new Element("img"); - expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); - expandoImage.align = "absmiddle"; - expandoImage.style.margin = "0px 5px"; - - this.domNode.appendChild(expandoImage); - - expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); - this.widget.onfocus = this._dateTimeTextBox_focusHandler.bindAsEventListener(this); - this.widget.onchange = this._dateTimeTextBox_changeHandler.bindAsEventListener(this); - }, - - setValue: function(value, forceCommit) - { - if (!this.widget) - { - this.setInitialValue(value, forceCommit); - } - else - { - this.parent(value, forceCommit); - var jsDate = dojo.date.fromRfc3339(value); - this.widget.value = dojo.date.format(jsDate, - {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, - selector: "dateOnly"}); - this.widget.removeClass("xformsGhostText"); - } - }, - - getValue: function() - { - if (this.widget.value == null || - this.widget.value.length == 0 || - this.widget.value == this._noValueSet) - { - return null; - } - else - { - var jsDate = dojo.date.parse(this.widget.value, - {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, - selector: "dateOnly"}); - return dojo.date.toRfc3339(jsDate); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _dateTimeTextBox_focusHandler: function(event) - { - this._destroyPicker(); - }, - - _dateTimeTextBox_changeHandler: function(event) - { - this._commitValueChange(); - }, - - _timePicker_valueChangedHandler: function(date) - { - var value = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); - value.setHours(date.getHours()); - value.setMinutes(date.getMinutes()); - value = dojo.date.toRfc3339(value); - this.setValue(value); - this._commitValueChange(); - }, - - _datePicker_valueChangedHandler: function(date) - { - var value = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); - value.setYear(date.getYear()); - value.setMonth(date.getMonth()); - value.setDate(date.getDate()); - value = dojo.date.toRfc3339(value); - this.setValue(value); - this._commitValueChange(); - }, - - _expando_clickHandler: function() - { - if (this._pickerDiv) - { - this._destroyPicker(); - } - else - { - this._createPicker(); - } - } -}); - -/** The year picker handles xforms widget xf:input with a gYear type */ -alfresco.xforms.YearPicker = alfresco.xforms.TextField.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - render: function(attach_point) - { - this.parent(attach_point); - this.widget.size = "4"; - this.widget.setAttribute("maxlength", "4"); - }, - - getInitialValue: function() - { - var result = this.parent(); - return result ? result.replace(/^0*([^0]+)$/, "$1") : result; - }, - - setValue: function(value, forceCommit) - { - this.parent((value - ? value.replace(/^0*([^0]+)$/, "$1") - : null), - forceCommit); - }, - - getValue: function() - { - var result = this.parent(); - return result ? dojo.string.padLeft(result, 4, "0") : null; - } -}); - -/** The day picker widget which handles xforms widget xf:input with type xf:gDay */ -alfresco.xforms.DayPicker = alfresco.xforms.ComboboxSelect1.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - _getItemValues: function() - { - var result = []; - result.push({id: "day_empty", label: "", value: "", valid: false}); - for (var i = 1; i <= 31; i++) - { - result.push({ - id: "day_" + i, - label: i, - value: "---" + (i < 10 ? "0" + i : i), - valid: true - }); - } - return result; - } -}); - -/** The month picker widget which handles xforms widget xf:input with type xf:gMonth */ -alfresco.xforms.MonthPicker = alfresco.xforms.ComboboxSelect1.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - _getItemValues: function() - { - var result = []; - result.push({id: "month_empty", label: "", value: "", valid: false}); - for (var i = 0; i < 12; i++) - { - var d = new Date(); - d.setMonth(i); - result.push({ - id: "month_" + i, - label: dojo.date.getMonthName(d), - value: "--" + (i + 1 < 10 ? "0" + (i + 1) : i + 1), - valid: true - }); - } - return result; - } -}); - -/** The month day picker widget which handles xforms widget xf:input with type xf:gMonthDay */ -alfresco.xforms.MonthDayPicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - this.monthPicker = new alfresco.xforms.MonthPicker(xform, xformsNode); - this.monthPicker._compositeParent = this; - - this.dayPicker = new alfresco.xforms.DayPicker(xform, xformsNode); - this.dayPicker._compositeParent = this; - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - render: function(attach_point) - { - this.setValue(this.getInitialValue()); - attach_point.appendChild(this.domNode); - this.dayPicker.render(this.domNode); - this.dayPicker.widget.style.marginRight = "10px"; - this.monthPicker.render(this.domNode); - this.domNode.style.width = this.monthPicker.domNode.offsetWidth + this.dayPicker.domNode.offsetWidth + 10 + "px"; - }, - - setValue: function(value) - { - this.monthPicker.setValue(value ? value.match(/^--[^-]+/)[0] : null); - this.dayPicker.setValue(value ? "---" + value.replace(/^--[^-]+-/, "") : null); - }, - - getValue: function() - { - // format is --MM-DD - var day = this.dayPicker.getValue(); - var month = this.monthPicker.getValue(); - return month && day ? day.replace(/^--/, month) : null; - } -}); - -/** The year month picker widget which handles xforms widget xf:input with type xf:gYearMonth */ -alfresco.xforms.YearMonthPicker = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - this.yearPicker = new alfresco.xforms.YearPicker(xform, xformsNode); - this.yearPicker._compositeParent = this; - - this.monthPicker = new alfresco.xforms.MonthPicker(xform, xformsNode); - this.monthPicker._compositeParent = this; - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - render: function(attach_point) - { - this.setValue(this.getInitialValue()); - attach_point.appendChild(this.domNode); - this.monthPicker.render(this.domNode); - this.monthPicker.widget.style.marginRight = "10px"; - this.yearPicker.domNode.style.display = "inline"; - this.yearPicker.render(this.domNode); - this.domNode.style.width = this.yearPicker.domNode.offsetWidth + this.monthPicker.domNode.offsetWidth + 10 + "px"; - }, - - setValue: function(value) - { - this.monthPicker.setValue(value ? value.replace(/^[^-]+-/, "--") : null); - this.yearPicker.setValue(value ? value.match(/^[^-]+/)[0] : null); - }, - - getValue: function() - { - // format is CCYY-MM - var year = this.yearPicker.getValue(); - var month = this.monthPicker.getValue(); - return year && month ? month.replace(/^-/, year) : null; - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// widgets for group types -//////////////////////////////////////////////////////////////////////////////// - -/** - * Handles xforms widget xf:group. A group renders and manages a set of children - * and provides a header for expanding and collapsing the group. A group header - * is shown for all group that don't have xf:appearance set to 'repeated' and - * that are not the root group. - */ -alfresco.xforms.AbstractGroup = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode, domNode) - { - this.parent(xform, xformsNode, domNode); - this._children = []; - this.domNode.removeClass("xformsItem"); - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - /** Returns the child at the specified index or null if the index is out of range. */ - getChildAt: function(index) - { - return index < this._children.length ? this._children[index] : null; - }, - - /** Returns the index of a particular child or -1 if the child was not found. */ - getChildIndex: function(child) - { - for (var i = 0; i < this._children.length; i++) - { - if (alfresco.constants.DEBUG) - { - alfresco.log(this.id + "[" + i + "]: " + - " is " + this._children[i].id + - " the same as " + child.id + "?"); - } - if (this._children[i] == child) - { - return i; - } - } - return -1; - }, - - /** Adds the child to end of the list of children. */ - addChild: function(child) - { - return this._insertChildAt(child, this._children.length); - }, - - _isIndented: function() - { - return !(this.parentWidget instanceof alfresco.xforms.ViewRoot) && this._children.length > 1; - }, - - /** Inserts a child at the specified position. */ - _insertChildAt: function(child, position, nodeName, attach_point) - { - alfresco.log(this.id + "._insertChildAt(" + child.id + ", " + position + ")"); - child.parentWidget = this; - - child.domContainer = new Element($pick(nodeName, "div")); - child.domContainer.setAttribute("id", child.id + "-domContainer"); - child.domContainer.addClass("xformsItemDOMContainer"); - - if (position == this._children.length) - { - $pick(attach_point, this.domNode.childContainerNode).appendChild(child.domContainer); - this._children.push(child); - } - else - { - $pick(attach_point, this.domNode.childContainerNode).insertBefore(child.domContainer, - this.getChildAt(position).domContainer); - this._children.splice(position, 0, child); - } - return child.domContainer; - }, - - /** Removes the child at the specified position. */ - _removeChildAt: function(position) - { - var child = this.getChildAt(position); - if (!child) - { - throw new Error("unable to find child at " + position); - } - - this._children.splice(position, 1); - child.domContainer.group = this; - var anim = dojo.lfx.html.fadeOut(child.domContainer, 500); - anim.onEnd = function() - { - child.domContainer.style.display = "none"; - child._destroy(); - - child.domContainer.remove(); - - child.domContainer.group._updateDisplay(false); - }; - anim.play(); - - this._childRemoved(child); - - return child; - }, - - /** Event handler for when a child has been added. */ - _childAdded: function(child) { }, - - /** Event handler for when a child has been removed. */ - _childRemoved: function(child) { }, - - /** Utility function to create the a label container */ - _createLabelContainer: function(child, nodeName, attach_point) - { - var labelNode = new Element($pick(nodeName, "div"), - { - id: child.id + "-label", - "class": "xformsItemLabelContainer" - }); - var requiredImage = new Element("img", { "class": "xformsItemRequiredImage" }); - requiredImage.src = alfresco.xforms.AbstractGroup._requiredImage.src; - - labelNode.appendChild(requiredImage); - - if (!child.isRequired()) - { - requiredImage.style.visibility = "hidden"; - } - var label = child.getLabel(); - if (label) - { - child.labelNode = labelNode; - child.labelNode.appendChild(document.createTextNode(label)); - } - var hint = child.getHint(); - if (hint) - { - labelNode.setAttribute("title", hint); - requiredImage.setAttribute("alt", hint); - } - labelNode.style.width = "0px"; - $pick(attach_point, child.domContainer).appendChild(labelNode); - labelNode.style.width = labelNode.scrollWidth + "px"; - return labelNode; - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - isValidForSubmit: function() - { - return true; - }, - - /** Iterates all children a produces an array of widgets which are invalid for submit. */ - getWidgetsInvalidForSubmit: function() - { - var result = []; - for (var i = 0; i < this._children.length; i++) - { - if (this._children[i] instanceof alfresco.xforms.AbstractGroup) - { - result = result.concat(this._children[i].getWidgetsInvalidForSubmit()); - } - else if (!this._children[i].isValidForSubmit()) - { - result.push(this._children[i]); - } - } - return result; - }, - - /** Recusively destroys all children. */ - _destroy: function() - { - this.parent(); - this._children.each(function(c) { c._destroy() }); - }, - - setReadonly: function(readonly) - { - this.parent(readonly); - this._children.each(function(c) { c.setReadonly(readonly); }); - }, - - render: function(attach_point) - { - this.domNode.widget = this; - return this.domNode; - }, - - _updateDisplay: function(recursively) - { - if (recursively) - { - this._children.each(function(c) { c._updateDisplay(recursively); }); - } - }, - - showAlert: function() - { - this._children.each(function(c) { c.showAlert(); }); - }, - - hideAlert: function() - { - this._children.each(function(c) { c.hideAlert(); }); - } -}); - -alfresco.xforms.AbstractGroup._requiredImage = new Image(); -alfresco.xforms.AbstractGroup._requiredImage.src = alfresco.constants.WEBAPP_CONTEXT + "/images/icons/required_field.gif"; - -/** - * Handles xforms widget xf:group. A group renders and manages a set of children - * and provides a header for expanding and collapsing the group. A group header - * is shown for all group that don't have xf:appearance set to 'repeated' and - * that are not the root group. - */ -alfresco.xforms.VGroup = alfresco.xforms.AbstractGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - _groupHeaderNode: null, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - /** Inserts a child at the specified position. */ - _insertChildAt: function(child, position) - { - if (!this.domNode.childContainerNode.parentNode) - { - // only add this to the dom once we're adding a child - this.domNode.appendChild(this.domNode.childContainerNode); - this._contentDivs = {}; - } - - child.domContainer = this.parent(child, position); - - if (this.parentWidget && this.parentWidget.domNode) - { - child.domContainer.style.top = this.parentWidget.domNode.style.bottom; - } - - function shouldInsertDivider(group, child, position) - { - if (group.getAppearance() != "full") - { - return false; - } - if (group instanceof alfresco.xforms.Repeat) - { - return false; - } - - if (!child.isVisible()) - { - return false; - } - if (group._children[position - 1] instanceof alfresco.xforms.AbstractGroup) - { - return true; - } - if (child instanceof alfresco.xforms.AbstractGroup) - { - for (var i = position - 1; i > 0; i--) - { - if (group._children[i].isVisible()) - { - return true; - } - } - } - return false; - } - - if (shouldInsertDivider(this, child, position)) - { - var divider = new Element("div", { "class": "xformsGroupDivider"}); - this.domNode.childContainerNode.insertBefore(divider, - child.domContainer); - } - - var contentDiv = new Element("div", { "id": child.id + "-content", "class": "xformsGroupItem"}); - this._contentDivs[child.id] = contentDiv; - if (!(child instanceof alfresco.xforms.AbstractGroup)) - { - contentDiv.labelNode = this._createLabelContainer(child); - child.domContainer.appendChild(contentDiv.labelNode); - } - - child.domContainer.appendChild(contentDiv); - contentDiv.style.left = (child instanceof alfresco.xforms.AbstractGroup - ? "0px" - : "30%"); - - contentDiv.style.width = (child instanceof alfresco.xforms.AbstractGroup - ? "100%" - : (1 - (contentDiv.offsetLeft / - child.domContainer.offsetWidth)) * 100 + "%"); - child.render(contentDiv); - if (!(child instanceof alfresco.xforms.AbstractGroup)) - { - child.domContainer.style.height = - Math.max(contentDiv.offsetHeight + - contentDiv.getStyle("margin-top").toInt() + - contentDiv.getStyle("margin-bottom").toInt(), - 20) + "px"; - } - - alfresco.log(contentDiv.getAttribute("id") + " offsetTop is " + contentDiv.offsetTop); - contentDiv.style.top = "-" + Math.max(0, contentDiv.offsetTop - contentDiv.getStyle("margin-top").toInt()) + "px"; - if (contentDiv.labelNode) - { -// contentDiv.labelNode.style.top = (contentDiv.offsetTop + ((.5 * contentDiv.offsetHeight) - -// (.5 * contentDiv.labelNode.offsetHeight))) + "px"; - contentDiv.labelNode.style.position = "relative"; - contentDiv.labelNode.style.top = contentDiv.offsetTop + "px"; - contentDiv.labelNode.style.height = contentDiv.offsetHeight + "px"; - contentDiv.labelNode.style.lineHeight = contentDiv.labelNode.style.height; - - } - contentDiv.widget = child; - - this._updateDisplay(false); - this._childAdded(child); - return child.domContainer; - }, - - render: function(attach_point) - { - this.domNode.widget = this; - - if (this.getAppearance() == "full") - { - this.domNode.addClass("xformsGroup"); - this.domNode.style.position = "relative"; - this.domNode.style.marginRight = (this.domNode.getStyle("margin-left").toInt() / 3) + "px"; - if (window.ie) - { - this.domNode.style.width = "100%"; - } - else - { - var x = ((this.domNode.offsetWidth - this.domNode.clientWidth) + - this.domNode.getStyle("margin-left").toFloat() + - this.domNode.getStyle("margin-right").toFloat()); - this.domNode.style.width = (1 - (x / attach_point.offsetWidth)) * 100 + "%"; - } - - this._groupHeaderNode = new Element("div", - { - "id": this.id + "-groupHeaderNode", - "class": "xformsGroupHeader" - }); - this.domNode.appendChild(this._groupHeaderNode); - - this.toggleExpandedImage = new Element("img", - { - "align": "absmiddle", - "styles": { "margin": "0px 5px" }, - "src": alfresco.xforms.constants.EXPANDED_IMAGE.src - }); - this._groupHeaderNode.appendChild(this.toggleExpandedImage); - this.toggleExpandedImage.onclick = this._toggleExpanded_clickHandler.bindAsEventListener(this); - - this._groupHeaderNode.appendChild(document.createTextNode(this.getLabel())); - } - attach_point.appendChild(this.domNode); - this.domNode.childContainerNode = new Element("div", - { - "id": this.id + "-childContainerNode", - "styles": { "position": "relative", "width": "100%" } - }); - return this.domNode; - }, - - /** Indicates if the group is expanded. */ - isExpanded: function() - { - return (this.toggleExpandedImage.getAttribute("src") == - alfresco.xforms.constants.EXPANDED_IMAGE.src); - }, - - /** - * Sets the expanded state of the widget. If collapsed, everything but the header - * will be hidden. - */ - setExpanded: function(expanded) - { - if (expanded != this.isExpanded()) - { - this.toggleExpandedImage.src = - (expanded - ? alfresco.xforms.constants.EXPANDED_IMAGE.src - : alfresco.xforms.constants.COLLAPSED_IMAGE.src); - this.domNode.childContainerNode.style.display = expanded ? "block" : "none"; - } - }, - - _updateDisplay: function(recursively) - { - if (this._isIndented()) - { - this.domNode.style.marginLeft = 10 + "px"; - this.domNode.style.marginRight = 5 + "px"; - // XXXarielb can this be moved to render or insertChild? - this.domNode.style.width = (((this.domNode.offsetWidth - 15) / this.domNode.offsetWidth) * 100) + "%"; - } - if (window.ie) - { - this.domNode.style.width = "100%"; - } - else - { -// var x = ((this.domNode.offsetWidth - this.domNode.clientWidth) + -// this.domNode.getStyle("margin-left").toFloat() + -// this.domNode.getStyle("margin-right").toFloat()); -// this.domNode.style.width = (1 - (x / this.domNode.parentNode.offsetWidth)) * 100 + "%"; - } - - for (var i = 0; i < this._children.length; i++) - { - if (!this._children[i].isVisible()) - { - continue; - } - var contentDiv = this._contentDivs[this._children[i].id]; - - contentDiv.style.position = "static"; - contentDiv.style.top = "0px"; - contentDiv.style.left = "0px"; - - contentDiv.style.position = "relative"; - contentDiv.style.left = (this._children[i] instanceof alfresco.xforms.AbstractGroup - ? "0px" - : "30%"); - contentDiv.style.width = (this._children[i] instanceof alfresco.xforms.AbstractGroup - ? "100%" - : (1 - (contentDiv.offsetLeft / - this._children[i].domContainer.parentNode.offsetWidth)) * 100 + "%"); - - if (recursively) - { - this._children[i]._updateDisplay(recursively); - } - - if (!(this._children[i] instanceof alfresco.xforms.AbstractGroup)) - { - this._children[i].domContainer.style.height = - Math.max(contentDiv.offsetHeight + - contentDiv.getStyle("margin-top").toInt() + - contentDiv.getStyle("margin-bottom").toInt(), - 20) + "px"; - } - - contentDiv.style.top = "-" + Math.max(0, contentDiv.offsetTop - contentDiv.getStyle("margin-top").toInt()) + "px"; - - var labelNode = contentDiv.labelNode; - if (labelNode) - { -// labelNode.style.position = "static"; -// labelNode.style.top = "0px"; -// labelNode.style.left = "0px"; -// labelNode.style.position = "relative"; - -// labelNode.style.top = (contentDiv.offsetTop + ((.5 * contentDiv.offsetHeight) - -// (.5 * labelNode.offsetHeight))) + "px"; - } - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _toggleExpanded_clickHandler: function(event) - { - this.setExpanded(!this.isExpanded()); - } -}); - -/** - * Handles xforms widget xf:group. A group renders and manages a set of children - * and provides a header for expanding and collapsing the group. A group header - * is shown for all group that don't have xf:appearance set to 'repeated' and - * that are not the root group. - */ -alfresco.xforms.HGroup = alfresco.xforms.AbstractGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("table")); - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - /** a map of child ids to contentDivs */ - _contentDivs: {}, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - _isIndented: function() - { - return false; - }, - - /** Inserts a child at the specified position. */ - _insertChildAt: function(child, position) - { - var labelCell = new Element("td"); - this.domNode.childContainerNode.appendChild(labelCell); - var labelNode = this._createLabelContainer(child, "div", labelCell); - labelNode.style.minWidth = "40px"; - labelCell.style.width = labelNode.style.width; - - child.parentWidget = this; - - var contentCell = new Element("td"); - this.domNode.childContainerNode.appendChild(contentCell); - child.domContainer = this.parent(child, position, null, contentCell); - child.domContainer.style.position = "static"; - - var contentDiv = new Element("div", { id: child.id + "-content", "class": "xformsGroupItem" }); - child.domContainer.appendChild(contentDiv); - this._contentDivs[child.id] = contentDiv; - - contentDiv.labelNode = labelNode; - child.render(contentDiv); - - var w = child.domNode.style.width; - if (!w || w[w.length - 1] != "%") - { - contentCell.style.width = child.domNode.offsetWidth + "px"; - } - contentDiv.widget = child; - this._childAdded(child); - return child.domContainer; - }, - - render: function(attach_point) - { - this.domNode.widget = this; - this.domNode.style.width = "100%"; - attach_point.appendChild(this.domNode); - - var tbody = new Element("tbody"); - this.domNode.appendChild(tbody); - this.domNode.childContainerNode = new Element("tr"); - tbody.appendChild(this.domNode.childContainerNode); - return this.domNode; - }, - - _updateDisplay: function(recursively) - { - this._children.each(function(child, index) - { - if (recursively) - { - child._updateDisplay(recursively); - } - }.bind(this)); - } -}); - -alfresco.xforms.SwitchGroup = alfresco.xforms.VGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - if (this.getInitialValue()) - { - var initialValueTrigger = this._getCaseToggleTriggerByTypeValue(this.getInitialValue()); - this._selectedCaseId = initialValueTrigger.getActions()["toggle"].properties["case"]; - } - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - _getCaseToggleTriggers: function() - { - var bw = this.xform.getBinding(this.xformsNode).widgets; - var result = []; - for (var i in bw) - { - if (! (bw[i] instanceof alfresco.xforms.Trigger)) - { - continue; - } - - var action = bw[i].getActions()["toggle"]; - if (action) - { - result.push(bw[i]); - } - } - return result; - }, - - _getCaseToggleTriggerByCaseId: function(caseId) - { - var bw = this.xform.getBinding(this.xformsNode).widgets; - for (var i in bw) - { - if (! (bw[i] instanceof alfresco.xforms.Trigger)) - { - continue; - } - - var action = bw[i].getActions()["toggle"]; - if (!action) - { - continue; - } - if (action.properties["case"] == caseId) - { - return bw[i]; - } - } - throw new Error("unable to find trigger " + type + - ", properties " + properties + - " for " + this.id); - - }, - - _getCaseToggleTriggerByTypeValue: function(typeValue) - { - var bw = this.xform.getBinding(this.xformsNode).widgets; - for (var i in bw) - { - if (! (bw[i] instanceof alfresco.xforms.Trigger)) - { - continue; - } - - var action = bw[i].getActions()["setvalue"]; - if (!action) - { - continue; - } - if (action.properties["value"] == typeValue) - { - return bw[i]; - } - } - throw new Error("unable to find toggle trigger for type value " + typeValue + - " for " + this.id); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - /** */ - _insertChildAt: function(child, position) - { - var childDomContainer = this.parent(child, position); - if (child.id == this._selectedCaseId) - { - this._getCaseToggleTriggerByCaseId(this._selectedCaseId).fire(); - } - else - { - childDomContainer.style.display = "none"; - } - return childDomContainer; - }, - - render: function(attach_point) - { - this.parent(attach_point); - var cases = this._getCaseToggleTriggers(); - var caseToggleSelect = new Element("select", - { - "id": this.id + "-toggle-select", - "styles": { "position": "absolute", "right": "0px", "top": "0px" } - }); - this._groupHeaderNode.appendChild(caseToggleSelect); - for (var i = 0; i < cases.length; i++) - { - var option = document.createElement("option"); - caseToggleSelect.appendChild(option); - var caseId = cases[i].getActions()["toggle"].properties["case"]; - option.setAttribute("value", caseId); - option.appendChild(document.createTextNode(cases[i].getLabel())); - if (cases[i].getActions()["toggle"].properties["case"] == this._selectedCaseId) - { - option.selected = true; - } - } - caseToggleSelect.onchange = this._caseToggleSelect_changeHandler.bindAsEventListener(this); - }, - - ///////////////////////////////////////////////////////////////// - // XForms event handlers - ///////////////////////////////////////////////////////////////// - - /** */ - handleSwitchToggled: function(selectedCaseId, deselectedCaseId) - { - alfresco.log(this.id + ".handleSwitchToggled(" + selectedCaseId + - ", " + deselectedCaseId + ")"); - this.selectedCaseId = selectedCaseId; - for (var i = 0; i < this._children.length; i++) - { - if (this._children[i].id == selectedCaseId) - { - this._children[i].domContainer.style.display = "block"; - } - else if (this._children[i].id == deselectedCaseId) - { - this._children[i].domContainer.style.display = "none"; - } - } - this._updateDisplay(true); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _caseToggleSelect_changeHandler: function(event) - { - event.stopPropagation(); - var t = this._getCaseToggleTriggerByCaseId(event.target.value); - t.fire(); - } -}); - -/** */ -alfresco.xforms.CaseGroup = alfresco.xforms.VGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - } -}); - -/** - * Handles xforms widget xf:group for the root group. Does some special rendering - * to present a title rather than a group header. - */ -alfresco.xforms.ViewRoot = alfresco.xforms.VGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - this.focusedRepeat = null; - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - _isIndented: function() - { - return false; - }, - - render: function(attach_point) - { - this.domNode.widget = this; - this.domNode.style.position = "relative"; - this.domNode.style.width = "100%"; - this.domNode.addClass("xformsViewRoot"); - - this._groupHeaderNode = new Element("div", - { - "id": this.id + "-groupHeaderNode", - "class": "xformsViewRootHeader" - }); - this.domNode.appendChild(this._groupHeaderNode); - - var icon = document.createElement("img"); - this._groupHeaderNode.appendChild(icon); - icon.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/file_large.gif"); - icon.align = "absmiddle"; - icon.style.margin = "0px 5px"; - this._groupHeaderNode.appendChild(document.createTextNode(this.getLabel())); - attach_point.appendChild(this.domNode); - - this.domNode.childContainerNode = new Element("div", - { - "id": this.id + "-childContainerNode", - "styles": {"position": "relative", "width": "100%"} - }); - return this.domNode; - }, - - /** */ - getLabel: function() - { - return this.parent() + " " + alfresco.xforms.constants.FORM_INSTANCE_DATA_NAME; - } -}); - -/** A struct for providing repeat index data. */ -alfresco.xforms.RepeatIndexData = function(repeat, index) -{ - this.repeat = repeat; - this.index = index; - this.toString = function() - { - return "{" + this.repeat.id + " = " + this.index + "}"; - }; -} - -/** - * Handles xforms widget xf:repeat. - */ -alfresco.xforms.Repeat = alfresco.xforms.VGroup.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - this._repeatControls = []; - this._selectedIndex = -1; - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - /** - * Indicates whether or not this repeat can insert more children based - * on the alf:maximum restriction. - */ - isInsertRepeatItemEnabled: function() - { - var maximum = this.xform.getBinding(this.xformsNode).maximum; - maximum = isNaN(maximum) ? Number.MAX_VALUE : maximum; - return this._children.length < maximum; - }, - - /** - * Indicates whether or not this repeat can removed children based - * on the alf:minimum restriction. - */ - isRemoveRepeatItemEnabled: function() - { - var minimum = this.xform.getBinding(this.xformsNode).minimum; - minimum = isNaN(minimum) ? this.isRequired() ? 1 : 0 : minimum; - return this._children.length > minimum; - }, - - /** - * Returns the currently selected index or -1 if this repeat has no repeat items. - */ - getSelectedIndex: function() - { - this._selectedIndex = Math.min(this._children.length, this._selectedIndex); - if (this._children.length == 0) - { - this._selectedIndex = -1; - } - return this._selectedIndex; - }, - - /** - * Helper function to locate the appropriate repeat item trigger for this repeat. - * This is done by locating all related widgets via binding, and selecting the - * Trigger who's action type is the type provided and where the properties - * provided are the same for that action. This approach is used rather than simply - * looking up the trigger by id since the id isn't known for nested repeats as - * chiba modifies them. - */ - _getRepeatItemTrigger: function(type, properties) - { - var bw = this.xform.getBinding(this.xformsNode).widgets; - for (var i in bw) - { - if (! (bw[i] instanceof alfresco.xforms.Trigger)) - { - continue; - } - - var action = bw[i].getActions()[type]; - if (!action) - { - continue; - } - - var propertiesEqual = true; - for (var p in properties) - { - if (!(p in action.properties) || - action.properties[p] != properties[p]) - { - propertiesEqual = false; - break; - } - } - if (propertiesEqual) - { - return bw[i]; - } - } - throw new Error("unable to find trigger " + type + - ", properties " + properties + - " for " + this.id); - - }, - - /** - * Sets the currently selected child by calliing XFormsBean.setRepeatIndeces. - * If the child provided is null, the index is set to 0. - */ - setFocusedChild: function(child) - { - var oldFocusedRepeat = this.getViewRoot().focusedRepeat; - this.getViewRoot().focusedRepeat = this; - if (oldFocusedRepeat != null && oldFocusedRepeat != this) - { - if (!oldFocusedRepeat.isAncestorOf(this)) - { - oldFocusedRepeat._selectedIndex = -1; - } - oldFocusedRepeat._updateDisplay(false); - } - - var repeatIndices = this.getRepeatIndices(); - if (!child) - { - repeatIndices.push(new alfresco.xforms.RepeatIndexData(this, 0)); - this.xform.setRepeatIndeces(repeatIndices); - } - else - { - var index = this.getChildIndex(child); - if (index < 0) - { - throw new Error("unable to find child " + child.id + " in " + this.id); - } - - repeatIndices.push(new alfresco.xforms.RepeatIndexData(this, index + 1)); - // xforms repeat indexes are 1-based - this.xform.setRepeatIndeces(repeatIndices); - } - }, - - /** - * Calls swapRepeatItems on the XFormsBean which will produce the event log - * to insert and remove the appropriate repeat items. - */ - _swapChildren: function(fromIndex, toIndex) - { - alfresco.log(this.id + ".swapChildren(" + fromIndex + ", " + toIndex + ")"); - var fromChild = this.getChildAt(fromIndex); - var toChild = this.getChildAt(toIndex); - this.xform.swapRepeatItems(fromChild, toChild); - var anim = dojo.lfx.html.fadeOut(fromChild.domContainer, 500); - anim.onEnd = function() - { - fromChild.domContainer.style.display = "none"; - }; - anim.play(); - }, - - /** - * Updates the repeat controls by changing the opacity on the image based on - * whether or not the action is enabled. - */ - _updateRepeatControls: function() - { - var insertEnabled = this.isInsertRepeatItemEnabled(); - var removeEnabled = this.isRemoveRepeatItemEnabled(); - for (var i = 0; i < this._repeatControls.length; i++) - { - this._repeatControls[i].moveRepeatItemUpImage.setOpacity(i == 0 ? .3 : 1); - this._repeatControls[i].moveRepeatItemDownImage.setOpacity(i == this._repeatControls.length - 1 ? .3 : 1); - this._repeatControls[i].insertRepeatItemImage.setOpacity(insertEnabled ? 1 : .3); - this._repeatControls[i].removeRepeatItemImage.setOpacity(removeEnabled ? 1 : .3); - } - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods & properties - ///////////////////////////////////////////////////////////////// - - /** When debugging, insert the id into the label. */ - getLabel: function() - { - var label = this.parentWidget.getLabel(); - if (alfresco.constants.DEBUG) - { - label += "[" + this.id + "]"; - } - return label; - }, - - /** Overrides _insertChildAt in Group to provide repeater controls. */ - _insertChildAt: function(child, position) - { - this._repeatControls.splice(position, 0, new Element("div")); - var images = - [ - { name: "insertRepeatItemImage", src: "plus", action: this._insertRepeatItemAfter_handler }, - { name: "moveRepeatItemUpImage", src: "arrow_up", action: this._moveRepeatItemUp_handler }, - { name: "moveRepeatItemDownImage", src: "arrow_down", action: this._moveRepeatItemDown_handler }, - { name: "removeRepeatItemImage", src: "minus", action: this._removeRepeatItem_handler } - ]; - var _repeatControlsWidth = 0; - for (var i = 0; i < images.length; i++) - { - var img = new Element("img", - { - "src": (alfresco.constants.WEBAPP_CONTEXT + "/images/icons/" + - images[i].src + ".gif"), - "styles": { "width" : "16px", "height" : "16px" } - }); - this._repeatControls[position][images[i].name] = img; - var imgMargin = [2, 5, 2, (i == 0 ? 5 : 0) ]; - img.style.margin = imgMargin.join("px ") + "px"; - _repeatControlsWidth += (parseInt(img.style.width) + imgMargin[1] + imgMargin[3]); - this._repeatControls[position].appendChild(img); - img.onclick = images[i].action.bindAsEventListener(this); - } - - var result = this.parent(child, position); - child.repeat = this; - result.onclick = function(event) - { - event = new Event(event); - child.repeat.setFocusedChild(child); - event.stopPropagation(); - }; - result.addClass("xformsRepeatItem"); - if (result.nextSibling) - { - result.parentNode.insertBefore(this._repeatControls[position], - result.nextSibling); - } - else - { - result.parentNode.appendChild(this._repeatControls[position]); - } - - this._repeatControls[position].addClass("xformsRepeatControls"); - this._repeatControls[position].style.width = _repeatControlsWidth + "px"; - this._repeatControls[position].style.backgroundColor = result.getStyle("background-color"); - this._repeatControls[position].style.overflow = "hidden"; - - result.style.paddingBottom = (.5 * this._repeatControls[position].offsetHeight) + "px"; - - this._repeatControls[position].style.top = -((.5 * this._repeatControls[position].offsetHeight) + - result.getStyle("margin-bottom").toInt() + - result.getStyle("border-bottom").toInt()) + "px"; - // may need to use this for centering repeat controls in quirks mode on IE - // this._repeatControls[position].style.margin = "0px " + Math.floor(100 * ((result.offsetWidth - - // this._repeatControls[position].offsetWidth) / - // (result.offsetWidth * 2)))+ "%"; - return result; - }, - - /** - * Overrides _removeChildAt in Group to remove the repeat controls associated with - * the repeat item. - */ - _removeChildAt: function(position) - { - this._repeatControls[position].style.display = "none"; - this._repeatControls[position].remove(); - this._repeatControls.splice(position, 1); - return this.parent(position); - }, - - /** Disables insert before. */ - _childAdded: function(child) - { - this.headerInsertRepeatItemImage.setOpacity(.3); - this._updateRepeatControls(); - }, - - /** Reenables insert before if there are no children left. */ - _childRemoved: function(child) - { - if (this._children.length == 0) - { - this.headerInsertRepeatItemImage.setOpacity(1); - } - this._updateRepeatControls(); - }, - - _isIndented: function() - { - return false; - }, - - render: function(attach_point) - { - this.domNode = this.parent(attach_point); - this.domNode.addClass("xformsRepeat"); - - // clear the border bottom for the group header since we'll be getting it - // from the repeat item border - this._groupHeaderNode.style.borderBottomWidth = "0px"; - - this._groupHeaderNode.repeat = this; - this._groupHeaderNode.onclick = function(event) - { - if (event.target == event.currentTarget) - { - event.currentTarget.repeat.setFocusedChild(null); - } - }; - - this.headerInsertRepeatItemImage = - new Element("img", - { - "align": "absmiddle", - "src": alfresco.constants.WEBAPP_CONTEXT + "/images/icons/plus.gif", - "styles": { "margin-left": "5px", "width": "16px", "height": "16px" } - }); - - this.headerInsertRepeatItemImage.repeat = this; - this._groupHeaderNode.appendChild(this.headerInsertRepeatItemImage); - - this.headerInsertRepeatItemImage.onclick = - this._headerInsertRepeatItemBefore_handler.bindAsEventListener(this); - - return this.domNode; - }, - - _updateDisplay: function(recursively) - { - this.parent(recursively); - if (this.getViewRoot().focusedRepeat != null && - (this.getViewRoot().focusedRepeat == this || - this.getViewRoot().focusedRepeat.isAncestorOf(this))) - { - this._groupHeaderNode.addClass("xformsRepeatFocusedHeader"); - } - else - { - this._groupHeaderNode.removeClass("xformsRepeatFocusedHeader"); - } - - for (var i = 0; i < this._children.length; i++) - { - var domContainerClasses = this._children[i].domContainer.getProperty("class").split(" "); - if (i + 1 == this.getSelectedIndex() && this.getViewRoot().focusedRepeat == this) - { - domContainerClasses.remove("xformsRowOdd"); - domContainerClasses.remove("xformsRowEven"); - domContainerClasses.include("xformsRepeatItemSelected"); - } - else - { - domContainerClasses.remove("xformsRepeatItemSelected"); - domContainerClasses.remove("xformsRow" + (i % 2 ? "Odd" : "Even")); - domContainerClasses.include("xformsRow" + (i % 2 ? "Even" : "Odd")); - } - this._children[i].domContainer.setProperty("class", domContainerClasses.join(" ")); - - this._repeatControls[i].style.backgroundColor = - this._children[i].domContainer.getStyle("background-color"); - } - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - /** - * Event handler for insert after. If insert is enabled, causes a setRepeatIndeces - * and an insert. - */ - _insertRepeatItemAfter_handler: function(event) - { - event = new Event(event); - event.stopPropagation(); - if (this.isInsertRepeatItemEnabled()) - { - var index = this._repeatControls.indexOf(event.target.parentNode); - var repeatItem = this.getChildAt(index); - this.setFocusedChild(repeatItem); - var trigger = this._getRepeatItemTrigger("insert", { position: "after" }); - trigger.fire(); - } - }, - - /** - * Event handler for insert before. If insert is enabled, causes a setRepeatIndeces - * and an insert. - */ - _headerInsertRepeatItemBefore_handler: function(event) - { - event = new Event(event); - if (this._children.length == 0) - { - event.stopPropagation(); - if (this.isInsertRepeatItemEnabled()) - { - this.setFocusedChild(null); - var trigger = this._getRepeatItemTrigger("insert", { position: "before" }); - trigger.fire(); - } - } - }, - - /** - * Event handler for remove. If remove is enabled, causes a setRepeatIndeces - * and an delete. - */ - _removeRepeatItem_handler: function(event) - { - event = new Event(event); - event.stopPropagation(); - if (this.isRemoveRepeatItemEnabled()) - { - var index = this._repeatControls.indexOf(event.target.parentNode); - var repeatItem = this.getChildAt(index); - this.setFocusedChild(repeatItem); - var trigger = this._getRepeatItemTrigger("delete", {}); - trigger.fire(); - } - }, - - /** - * Event handler for move up. Calls swap children with the child before - * if the current select child is not the first child. - */ - _moveRepeatItemUp_handler: function(event) - { - event = new Event(event); - event.stopPropagation(); - var index = this._repeatControls.indexOf(event.target.parentNode); - if (index != 0 && this._children.length != 1) - { - var repeatItem = this.getChildAt(index); - this.setFocusedChild(repeatItem); - this._swapChildren(index, index - 1); - } - }, - - /** - * Event handler for move down. Calls swap children with the child after - * if the current select child is not the last child. - */ - _moveRepeatItemDown_handler: function(event) - { - event = new Event(event); - event.stopPropagation(); - var index = this._repeatControls.indexOf(event.target.parentNode); - if (index != this._children.length - 1 && this._children.length != 1) - { - var repeatItem = this.getChildAt(index); - this.setFocusedChild(repeatItem); - this._swapChildren(index, index + 1); - } - }, - - ///////////////////////////////////////////////////////////////// - // XForms event handlers - ///////////////////////////////////////////////////////////////// - - /** Sets the selected index. */ - handleIndexChanged: function(index) - { - alfresco.log(this.id + ".handleIndexChanged(" + index + ")"); - this._selectedIndex = index; - this._updateDisplay(false); - }, - - /** Returns a clone of the specified prototype id. */ - handlePrototypeCloned: function(prototypeId) - { - alfresco.log(this.id + ".handlePrototypeCloned("+ prototypeId +")"); - var chibaData = _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.CHIBA_NS, - alfresco.xforms.constants.CHIBA_PREFIX, - "data"); - chibaData = chibaData[chibaData.length - 1]; - var prototypeToClone = dojo.dom.firstElement(chibaData); - if (prototypeToClone.getAttribute("id") != prototypeId) - { - throw new Error("unable to locate " + prototypeId + - " in " + this.id); - } - return prototypeToClone.cloneNode(true); - }, - - /** Inserts the clonedPrototype at the specified position. */ - handleItemInserted: function(clonedPrototype, position) - { - alfresco.log(this.id + ".handleItemInserted(" + clonedPrototype.nodeName + - ", " + position + ")"); - var w = this.xform.createWidget(clonedPrototype); - this._insertChildAt(w, position); - this.xform.loadWidgets(w.xformsNode, w); - }, - - /** Deletes the item at the specified position. */ - handleItemDeleted: function(position) - { - alfresco.log(this.id + ".handleItemDeleted(" + position + ")"); - this._removeChildAt(position); - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// trigger widgets -//////////////////////////////////////////////////////////////////////////////// - -/** - * Handles xforms widget xf:trigger. - */ -alfresco.xforms.Trigger = alfresco.xforms.Widget.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode, new Element("input", { type: "submit" })); - }, - - ///////////////////////////////////////////////////////////////// - // methods & properties - ///////////////////////////////////////////////////////////////// - - /** TODO: DOCUMENT */ - getActions: function() - { - if (typeof this._actions == "undefined") - { - var actionNode = _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "action")[0]; - this._actions = {}; - for (var i = 0; i < actionNode.childNodes.length; i++) - { - if (actionNode.childNodes[i].nodeType != document.ELEMENT_NODE) - { - continue; - } - - var a = new alfresco.xforms.XFormsAction(this.xform, actionNode.childNodes[i]); - this._actions[a.getType()] = a; - } - } - return this._actions; - }, - - /** fires the xforms action associated with the trigger */ - fire: function(asynchronous) - { - this.xform.fireAction(this.id, asynchronous); - }, - - ///////////////////////////////////////////////////////////////// - // overridden methods - ///////////////////////////////////////////////////////////////// - - isValidForSubmit: function() - { - return true; - }, - - isVisible: function() - { - return false; - }, - - render: function(attach_point) - { - attach_point.appendChild(this.domNode); - this.widget = this.domNode; - this.widget.value = this.getLabel() + " " + this.id; - this.widget.onclick = this._clickHandler.bindAsEventListener(this); - this.domContainer.style.display = "none"; - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - _clickHandler: function(event) - { - this.fire(); - } -}); - -/** - * Handles xforms widget xf:submit. - */ -alfresco.xforms.Submit = alfresco.xforms.Trigger.extend({ - initialize: function(xform, xformsNode) - { - this.parent(xform, xformsNode); - var submit_buttons = (this.id == "submit" - ? _xforms_getSubmitButtons() - : (this.id == "save-draft" - ? _xforms_getSaveDraftButtons() - : null)); - if (submit_buttons == null) - { - throw new Error("unknown submit button " + this.id); - } - submit_buttons.each(function(b) - { - alfresco.log("adding submit handler for " + b.getAttribute('id')); - $(b).onclick = this._submitButton_clickHandler.bindAsEventListener(this); - }.bind(this)); - }, - - ///////////////////////////////////////////////////////////////// - // DOM event handlers - ///////////////////////////////////////////////////////////////// - - _clickHandler: function(event) - { - this.done = false; - _hide_errors(); - this.fire(); - }, - - /** */ - _submitButton_clickHandler: function(event) - { - event = new Event(event); - var result; - if (this.xform.submitWidget && this.xform.submitWidget.done) - { - alfresco.log("done - doing base click on " + this.xform.submitWidget.currentButton.id); - this.xform.submitWidget.currentButton = null; - this.xform.submitWidget = null; - result = true; - } - else - { - alfresco.log("triggering submit from handler " + event.target.id); - event.stopPropagation(); - _hide_errors(); - this.xform.submitWidget = this; - this.xform.submitWidget.currentButton = event.target; - this.xform.submitWidget.fire(true); - result = false; - } - alfresco.log("submit click handler exit " + event.target.id + " with result " + result); - return result; - } -}); - -/** - * A struct describing an xforms action block. - */ -alfresco.xforms.XFormsAction = new Class({ - initialize: function(xform, xformsNode) - { - this.xform = xform; - this.xformsNode = xformsNode; - /** All properties of the action as map of key value pairs */ - this.properties = []; - for (var i = 0; i < this.xformsNode.attributes.length; i++) - { - var attr = this.xformsNode.attributes[i]; - if (attr.nodeName.match(new RegExp("^" + alfresco.xforms.constants.XFORMS_PREFIX + ":"))) - { - this.properties[attr.nodeName.substring((alfresco.xforms.constants.XFORMS_PREFIX + ":").length)] = - attr.nodeValue; - } - } - if (this.getType() == "setvalue" && !this.properties["value"]) - { - this.properties["value"] = this.xformsNode.firstChild.nodeValue; - } - }, - - /** Returns the action type. */ - getType: function() - { - return this.xformsNode.nodeName.substring((alfresco.xforms.constants.XFORMS_PREFIX + ":").length); - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// xforms data model -//////////////////////////////////////////////////////////////////////////////// - -/** - * An xforms event. A log of events is returned by any xforms action and - * is used to update the UI appropriately. - */ -alfresco.xforms.XFormsEvent = new Class({ - initialize: function(node) - { - this.type = node.nodeName; - this.targetId = node.getAttribute("targetId"); - this.targetName = node.getAttribute("targetName"); - this.properties = {}; - for (var i = 0; i < node.childNodes.length; i++) - { - if (node.childNodes[i].nodeType == document.ELEMENT_NODE) - { - this.properties[node.childNodes[i].getAttribute("name")] = - node.childNodes[i].getAttribute("value"); - } - } - }, - - /** Returns the widget managing the specified target id. */ - getTarget: function() - { - var targetDomNode = document.getElementById(this.targetId + "-content"); - if (!targetDomNode) - { - throw new Error("unable to find node " + this.targetId + "-content"); - } - return targetDomNode.widget; - } -}); - -/** - * A parsed xf:bind. - */ -alfresco.xforms.Binding = new Class({ - initialize: function(xformsNode, parentBinding) - { - this.xformsNode = xformsNode; - this.id = this.xformsNode.getAttribute("id"); - this.nodeset = this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":nodeset"); - this._readonly = - (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":readonly") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":readonly") == "true()" - : null); - this._required = - (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":required") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":required") == "true()" - : null); - this._type = - (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":type") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":type") - : null); - this._builtInType = - (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":builtInType") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":builtInType") - : null); - this.constraint = - (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":constraint") - ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":constraint") - : null); - this.maximum = this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":maxOccurs"); - this.maximum = this.maximum == "unbounded" ? Number.MAX_VALUE : parseInt(this.maximum); - this.minimum = parseInt(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":minOccurs")); - this.parentBinding = parentBinding; - this.widgets = {}; - }, - - /** Returns the expected schema type for this binding. */ - getType: function() - { - return (this._type != null - ? this._type - : (this.parentBinding != null ? this.parentBinding.getType() : null)); - }, - - /** Returns the expected built in schema type for this binding. */ - getBuiltInType: function() - { - return (this._builtInType != null - ? this._builtInType - : (this.parentBinding != null ? this.parentBinding.getBuiltInType() : null)); - }, - - /** Returns true if a node bound by this binding has a readonly value */ - isReadonly: function() - { - return (this._readonly != null ? this._readonly : - (this.parentBinding != null ? this.parentBinding.isReadonly() : false)); - }, - - /** Returns true if a node bound by this binding has a required value */ - isRequired: function() - { - return (this._required != null ? this._required : - (this.parentBinding != null ? this.parentBinding.isRequired() : false)); - }, - - toString: function() - { - return ("{id: " + this.id + - ",type: " + this.getType() + - ",builtInType: " + this.getBuiltInType() + - ",required: " + this.isRequired() + - ",readonly: " + this.isReadonly() + - ",nodeset: " + this.nodeset + "}"); - } -}); - -/** - * Manages the xforms document. - */ -alfresco.xforms.XForm = new Class({ - - /** Makes a request to the XFormsBean to load the xforms document. */ - initialize: function() - { - alfresco.AjaxHelper.sendRequest("XFormsBean.getXForm", - null, - true, - this._loadHandler.bindAsEventListener(this)); - }, - - ///////////////////////////////////////////////////////////////// - // Initialization - ///////////////////////////////////////////////////////////////// - - /** Parses the xforms document and produces the widget tree. */ - _loadHandler: function(xformDocument) - { - this.xformDocument = xformDocument; - this.xformsNode = xformDocument.documentElement; - this._bindings = this._loadBindings(this.getModel()); - - var bindings = this.getBindings(); - var alfUI = document.getElementById(alfresco.xforms.constants.XFORMS_UI_DIV_ID); - alfUI.style.width = "100%"; - var rootGroup = _getElementsByTagNameNS(this.getBody(), - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "group")[0]; - - this.rootWidget = new alfresco.xforms.ViewRoot(this, rootGroup); - this.rootWidget.render(alfUI); - - this.loadWidgets(rootGroup, this.rootWidget); - }, - - /** Creates the widget for the provided xforms node. */ - createWidget: function(xformsNode) - { - var appearance = (xformsNode.getAttribute("appearance") || - xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance")); - appearance = appearance == null || appearance.length == 0 ? null : appearance; - - var xformsType = xformsNode.nodeName.toLowerCase(); - var binding = this.getBinding(xformsNode); - var schemaType = binding ? binding.getType() : null; - var builtInSchemaType = binding ? binding.getBuiltInType() : null; - - alfresco.log("creating widget for xforms type " + xformsType + - " schema type " + schemaType + - " built in schema type " + builtInSchemaType + - " with appearance " + appearance); - var x = alfresco.xforms.widgetConfig[xformsType]; - if (!x) - { - throw new Error("unknown type " + xformsNode.nodeName); - } - x = schemaType in x ? x[schemaType] : builtInSchemaType in x ? x[builtInSchemaType] : x["*"]; - x = appearance in x ? x[appearance] : x["*"]; - // alfresco.log(xformsType + ":" + schemaType + ":" + appearance + " =>" + x); - if (x === undefined) - { - throw new Error("unable to find widget for xforms type " + xformsType + - " schemaType " + schemaType + - " appearance " + appearance); - } - if (x == null || typeof x.className == "undefined") - { - return null; - } - var cstr = eval(x.className); - if (!cstr) - { - throw new Error("unable to load constructor " + x.className + - " for xforms type " + xformsType + - " schemaType " + schemaType + - " appearance " + appearance); - } - var result = new cstr(this, xformsNode, $merge({}, x.params)); - if (result instanceof alfresco.xforms.Widget) - { - return result; - } - else - { - throw new Error("constructor for widget " + x + - " for xforms type " + xformsType + - " schemaType " + schemaType + - " appearance " + appearance + - " is not an alfresco.xforms.Widget"); - } - }, - - /** Loads all widgets for the provided xforms node's children. */ - loadWidgets: function(xformsNode, parentWidget) - { - for (var i = 0; i < xformsNode.childNodes.length; i++) - { - if (xformsNode.childNodes[i].nodeType != document.ELEMENT_NODE) - { - continue; - } - alfresco.log("loading " + xformsNode.childNodes[i].nodeName + - " into " + parentWidget.id); - if (xformsNode.childNodes[i].getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + - ":prototype") == "true") - { - alfresco.log(xformsNode.childNodes[i].getAttribute("id") + - " is a prototype, ignoring"); - continue; - } - var w = this.createWidget(xformsNode.childNodes[i]); - if (w != null) - { - alfresco.log("created " + w.id + " for " + xformsNode.childNodes[i].nodeName); - parentWidget.addChild(w); - if (w instanceof alfresco.xforms.AbstractGroup) - { - this.loadWidgets(xformsNode.childNodes[i], w); - } - } - } - }, - - /** Loads all bindings from the xforms document. */ - _loadBindings: function(bind, parentBinding, result) - { - result = result || []; - for (var i = 0; i < bind.childNodes.length; i++) - { - if (bind.childNodes[i].nodeName.toLowerCase() == - alfresco.xforms.constants.XFORMS_PREFIX + ":bind") - { - var b = new alfresco.xforms.Binding(bind.childNodes[i], parentBinding); - result[b.id] = b; - alfresco.log("loaded binding " + b); - this._loadBindings(bind.childNodes[i], result[b.id], result); - } - } - return result; - }, - - ///////////////////////////////////////////////////////////////// - // XForms model properties & methods - ///////////////////////////////////////////////////////////////// - - /** Returns the model section of the xforms document. */ - getModel: function() - { - return _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "model")[0]; - }, - - /** Returns the instance section of the xforms document. */ - getInstance: function() - { - return _getElementsByTagNameNS(this.getModel(), - alfresco.xforms.constants.XFORMS_NS, - alfresco.xforms.constants.XFORMS_PREFIX, - "instance")[0]; - }, - - /** Returns the body section of the xforms document. */ - getBody: function() - { - var b = _getElementsByTagNameNS(this.xformsNode, - alfresco.xforms.constants.XHTML_NS, - alfresco.xforms.constants.XHTML_PREFIX, - "body"); - return b[b.length - 1]; - }, - - /** Returns the binding corresponding to the provided xforms node. */ - getBinding: function(xformsNode) - { - return this._bindings[xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":bind")]; - }, - - /** Returns all parsed bindings. */ - getBindings: function() - { - return this._bindings; - }, - - ///////////////////////////////////////////////////////////////// - // XFormsBean interaction - ///////////////////////////////////////////////////////////////// - - /** swaps the specified repeat items by calling XFormsBean.swapRepeatItems. */ - swapRepeatItems: function(fromChild, toChild) - { - var params = - { - fromItemId: fromChild.xformsNode.getAttribute("id"), - toItemId: toChild.xformsNode.getAttribute("id"), - instanceId: this.getInstance().getAttribute("id") - }; - - alfresco.AjaxHelper.sendRequest("XFormsBean.swapRepeatItems", - params, - false, - this._handleEventLog.bindAsEventListener(this)); - }, - - /** sets the repeat indexes by calling XFormsBean.setRepeatIndeces. */ - setRepeatIndeces: function(repeatIndeces) - { - alfresco.log("setting repeat indeces [" + repeatIndeces.join(", ") + "]"); - var params = { }; - params["repeatIds"] = []; - for (var i = 0; i < repeatIndeces.length; i++) - { - params.repeatIds.push(repeatIndeces[i].repeat.id); - params[repeatIndeces[i].repeat.id] = repeatIndeces[i].index; - } - params.repeatIds = params.repeatIds.join(","); - alfresco.AjaxHelper.sendRequest("XFormsBean.setRepeatIndeces", - params, - false, - this._handleEventLog.bindAsEventListener(this)); - }, - - /** Fires an action specified by the id by calling XFormsBean.fireAction. */ - fireAction: function(id, asynchronous) - { - alfresco.log("fireAction(" + id + ")"); - alfresco.AjaxHelper.sendRequest("XFormsBean.fireAction", - { id: id }, - $pick(asynchronous, false), - this._handleEventLog.bindAsEventListener(this)); - }, - - /** Sets the value of the specified control id by calling XFormsBean.setXFormsValue. */ - setXFormsValue: function(id, value) - { - value = value == null ? "" : value.toString(); - alfresco.log("setting value " + id + " = " + value); - alfresco.AjaxHelper.sendRequest("XFormsBean.setXFormsValue", - { id: id, value: value }, - true, - this._handleEventLog.bindAsEventListener(this)); - }, - - /** Handles the xforms event log resulting from a call to the XFormsBean. */ - _handleEventLog: function(events) - { - events = events.documentElement; - var prototypeClones = []; - var generatedIds = null; - for (var i = 0; i < events.childNodes.length; i++) - { - if (events.childNodes[i].nodeType != document.ELEMENT_NODE) - { - continue; - } - var xfe = new alfresco.xforms.XFormsEvent(events.childNodes[i]); - alfresco.log("parsing " + xfe.type + - "(" + xfe.targetId + ", " + xfe.targetName + ")"); - switch (xfe.type) - { - case "chiba-index-changed": - { - var index = Number(xfe.properties["index"]); - try - { - xfe.getTarget().handleIndexChanged(index); - } - catch (e) - { - alfresco.log(e); - } - break; - } - case "chiba-state-changed": - { - alfresco.log("handleStateChanged(" + xfe.targetId + ")"); - xfe.getTarget().setModified(true); - if ("valid" in xfe.properties) - { - xfe.getTarget().setValid(xfe.properties["valid"] == "true"); - } - if ("required" in xfe.properties) - { - xfe.getTarget().setRequired(xfe.properties["required"] == "true"); - } - if ("readonly" in xfe.properties) - { - xfe.getTarget().setReadonly(xfe.properties["readonly"] == "true"); - } - if ("enabled" in xfe.properties) - { - xfe.getTarget().setEnabled(xfe.properties["enabled"] == "true"); - } - if ("value" in xfe.properties) - { - alfresco.log("setting " + xfe.getTarget().id + " = " + xfe.properties["value"]); - xfe.getTarget().setValue(xfe.properties["value"]); - } - break; - } - case "chiba-prototype-cloned": - { - var prototypeId = xfe.properties["prototypeId"]; - var originalId = xfe.properties["originalId"]; - alfresco.log("handlePrototypeCloned(" + xfe.targetId + - ", " + originalId + - ", " + prototypeId + ")"); - var clone = null; - var prototypeNode = _findElementById(this.xformsNode, prototypeId); - if (prototypeNode) - { - alfresco.log("cloning prototype " + prototypeNode.getAttribute("id")); - clone = prototypeNode.cloneNode(true); - } - else - { - alfresco.log("cloning prototype " + originalId); - var prototypeNode = _findElementById(this.xformsNode, originalId); - //clone = prototypeNode.cloneNode(true); - clone = prototypeNode.ownerDocument.createElement(alfresco.xforms.constants.XFORMS_PREFIX + ":group"); - clone.setAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance", "repeated"); - for (var j = 0; j < prototypeNode.childNodes.length; j++) - { - clone.appendChild(prototypeNode.childNodes[j].cloneNode(true)); - } - clone.setAttribute("id", prototypeId); - } - - if (clone == null) - { - throw new Error("unable to clone prototype " + prototypeId); - } - - alfresco.log("created clone " + clone.getAttribute("id") + - " nodeName " + clone.nodeName + - " parentClone " + (prototypeClones.length != 0 - ? prototypeClones.peek().getAttribute("id") - : null)); - prototypeClones.push(clone); - break; - } - case "chiba-id-generated": - { - var originalId = xfe.properties["originalId"]; - - alfresco.log("handleIdGenerated(" + xfe.targetId + ", " + originalId + ")"); - var node = _findElementById(prototypeClones.peek(), originalId); - if (!node) - { - throw new Error("unable to find " + originalId + - " in clone " + dojo.dom.innerXML(clone)); - } - alfresco.log("applying id " + xfe.targetId + - " to " + node.nodeName + "(" + originalId + ")"); - node.setAttribute("id", xfe.targetId); - generatedIds = generatedIds || new Object(); - generatedIds[xfe.targetId] = originalId; - if (prototypeClones.length != 1) - { - var e = _findElementById(prototypeClones[prototypeClones.length - 2], originalId); - if (e) - { - e.setAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":prototype", "true"); - } - } - break; - } - case "chiba-item-inserted": - { - var position = Number(xfe.properties["position"]) - 1; - var originalId = xfe.properties["originalId"]; - var clone = prototypeClones.pop(); - // walk all nodes of the clone and ensure that they have generated ids. - // those that do not are nested repeats that should not be added - assert(clone.getAttribute("id") in generatedIds, - "expected clone id " + clone.getAttribute("id") + - " to be a generated id"); - function _removeNonGeneratedChildNodes(node, ids) - { - var child = node.firstChild; - while (child) - { - var next = child.nextSibling; - if (child.nodeType == document.ELEMENT_NODE) - { - if (child.getAttribute("id") in ids) - { - _removeNonGeneratedChildNodes(child, ids); - } - else - { - node.removeChild(child); - } - } - child = next; - } - }; - _removeNonGeneratedChildNodes(clone, generatedIds); - - if (prototypeClones.length != 0) - { - alfresco.log("using parentClone " + prototypeClones.peek().getAttribute("id") + - " of " + clone.getAttribute("id")); - var parentRepeat = _findElementById(prototypeClones.peek(), xfe.targetId); - parentRepeat.appendChild(clone); - } - else - { - alfresco.log("no parentClone found, directly insert " + clone.getAttribute("id") + - " on " + xfe.targetId); - xfe.getTarget().handleItemInserted(clone, position); - } - break; - } - case "chiba-item-deleted": - { - var position = Number(xfe.properties["position"]) - 1; - xfe.getTarget().handleItemDeleted(position); - break; - } - case "chiba-replace-all": - { - if (this.submitWidget) - { - this.submitWidget.done = true; - this.submitWidget.currentButton.click(); - } - break; - } - case "chiba-switch-toggled": - { - var switchElement = xfe.getTarget(); - switchElement.handleSwitchToggled(xfe.properties["selected"], - xfe.properties["deselected"]); - } - case "xforms-valid": - { - xfe.getTarget().setValid(true); - xfe.getTarget().setModified(true); - break; - } - case "xforms-invalid": - { - xfe.getTarget().setValid(false); - xfe.getTarget().setModified(true); - break; - } - case "xforms-required": - { - xfe.getTarget().setRequired(true); - break; - } - case "xforms-optional": - { - xfe.getTarget().setRequired(false); - break; - } - case "xforms-submit-error": - { - this.submitWidget = null; - var invalid_widgets = this.rootWidget.getWidgetsInvalidForSubmit(); - _show_error(document.createTextNode(alfresco.resources["validation_provide_values_for_required_fields"])); - var error_list = document.createElement("ul"); - invalid_widgets.each(function(invalid) - { - var error_item = document.createElement("li"); - error_item.appendChild(document.createTextNode(invalid.getAlert())); - error_list.appendChild(error_item); - invalid.showAlert(); - }); - _show_error(error_list); - break; - } - case "xforms-readonly": - { - xfe.getTarget().setReadonly(true); - break; - } - case "xforms-readwrite": - { - xfe.getTarget().setReadonly(false); - break; - } - case "xforms-submit": - case "xforms-submit-done": - case "xforms-enabled": - case "xforms-disabled": - break; - default: - { - alfresco.log("unhandled event " + events.childNodes[i].nodeName); - } - } - } - } -}); - -//////////////////////////////////////////////////////////////////////////////// -// error message display management -//////////////////////////////////////////////////////////////////////////////// - -/** hides the error message display. */ -function _hide_errors() -{ - var errorDiv = $(alfresco.xforms.constants.XFORMS_ERROR_DIV_ID); - if (errorDiv) - { - errorDiv.empty(); - errorDiv.style.display = "none"; - } -} - -/** shows the error message display. */ -function _show_error(msg) -{ - var errorDiv = $(alfresco.xforms.constants.XFORMS_ERROR_DIV_ID); - if (!errorDiv) - { - errorDiv = new Element("div", { "id": alfresco.xforms.constants.XFORMS_ERROR_DIV_ID }); - errorDiv.addClass("infoText statusErrorText xformsError"); - errorDiv.injectBefore($(alfresco.xforms.constants.XFORMS_UI_DIV_ID)); - } - - if (errorDiv.style.display == "block") - { - errorDiv.appendChild(document.createElement("br")); - } - else - { - errorDiv.style.display = "block"; - } - errorDiv.appendChild(msg); -} - -//////////////////////////////////////////////////////////////////////////////// -// DOM utilities - XXXarielb should be merged into common.js -//////////////////////////////////////////////////////////////////////////////// - -function _findElementById(node, id) -{ -// alfresco.log("looking for " + 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 == document.ELEMENT_NODE) - { - var n = _findElementById(node.childNodes[i], id); - if (n) - { - return n; - } - } - } - return null; -} - -function _hasAttribute(node, name) -{ - return (node == null - ? false - : (node.hasAttribute - ? node.hasAttribute(name) - : node.getAttribute(name) != null)); -} - -function _getElementsByTagNameNS(parentNode, ns, nsPrefix, tagName) -{ - return (parentNode.getElementsByTagNameNS - ? parentNode.getElementsByTagNameNS(ns, tagName) - : parentNode.getElementsByTagName(nsPrefix + ":" + tagName)); -} - -//////////////////////////////////////////////////////////////////////////////// -// XPath wrapper -//////////////////////////////////////////////////////////////////////////////// - -function _evaluateXPath(xpath, contextNode, result_type) -{ - var xmlDocument = contextNode.ownerDocument; - if (alfresco.constants.DEBUG) - { - alfresco.log("evaluating xpath " + xpath + - " on node " + contextNode.nodeName + - " in document " + xmlDocument); - } - var result = null; - if (xmlDocument.evaluate) - { - var nsResolver = (xmlDocument.createNSResolver - ? xmlDocument.createNSResolver(xmlDocument.documentElement) - : null); - result = xmlDocument.evaluate(xpath, - contextNode, - nsResolver, - result_type, - null); - if (result) - { - switch (result_type) - { - case XPathResult.FIRST_ORDERED_NODE_TYPE: - result = result.singleNodeValue; - break; - case XPathResult.BOOLEAN_TYPE: - result = result.booleanValue; - break; - case XPathResult.STRING_TYPE: - result = result.stringValue; - break; - } - } - } - else - { - xmlDocument.setProperty("SelectionLanguage", "XPath"); - var namespaces = []; - for (var i = 0; i < xmlDocument.documentElement.attributes.length; i++) - { - var attr = xmlDocument.documentElement.attributes[i]; - if (attr.nodeName.match(/^xmlns:/)) - { - namespaces.push(attr.nodeName + "=\'" + attr.nodeValue + "\'"); - } - } - - if (alfresco.constants.DEBUG) - { - alfresco.log("using namespaces " + namespaces.join(",")); - } - xmlDocument.setProperty("SelectionNamespaces", namespaces.join(' ')); - if (result_type == XPathResult.FIRST_ORDERED_NODE_TYPE) - { - result = xmlDocument.selectSingleNode(xpath); - } - else if (result_type == XPathResult.BOOLEAN_TYPE) - { - result = true; - } - } - alfresco.log("resolved xpath " + xpath + " to " + result); - return result; -} - -if (!XPathResult) -{ - var XPathResult = - { - ANY_TYPE: 0, - NUMBER_TYPE: 1, - STRING_TYPE: 2, - BOOEAN_TYPE: 3, - FIRST_ORDERED_NODE_TYPE: 9 - }; -} - -dojo.html.toCamelCase = function(str) -{ - return str.replace(/-./, function(str) { return str.charAt(1).toUpperCase(); }); -} - -//////////////////////////////////////////////////////////////////////////////// -// tiny mce integration -//////////////////////////////////////////////////////////////////////////////// - -alfresco.constants.TINY_MCE_DEFAULT_PLUGINS = - alfresco.xforms.RichTextEditor.determineNecessaryTinyMCEPlugins(alfresco.xforms.widgetConfig); - -alfresco.constants.TINY_MCE_DEFAULT_SETTINGS = -{ - theme: "advanced", - mode: "exact", - plugins: alfresco.constants.TINY_MCE_DEFAULT_PLUGINS, - width: -1, - height: -1, - auto_resize: false, - force_p_newlines: false, - encoding: "UTF-8", - entity_encoding: "raw", - add_unload_trigger: false, - add_form_submit_trigger: false, - theme_advanced_toolbar_location: "top", - theme_advanced_toolbar_align: "left", - theme_advanced_buttons1: "", - theme_advanced_buttons2: "", - theme_advanced_buttons3: "", - urlconverter_callback: "alfresco_TinyMCE_urlconverter_callback", - file_browser_callback: "alfresco_TinyMCE_file_browser_callback" -}; - -tinyMCE.init($extend({}, alfresco.constants.TINY_MCE_DEFAULT_SETTINGS)); - -window.addEvent("domready", - function() - { - document.xform = new alfresco.xforms.XForm(); - }); +/* + * 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. + */ +//////////////////////////////////////////////////////////////////////////////// +// XForms user interface +// +// This script communicates with the XFormBean to produce and manage an xform. +// +// This script requires mootools.js, dojo.js, tiny_mce.js, +// tiny_mce_wcm_extensions.js, and upload_helper.js to be loaded in advance. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// initialization +// +// Initiliaze dojo requirements, tinymce, and add a hook to load the xform. +//////////////////////////////////////////////////////////////////////////////// + +djConfig.parseWidgets = false; +dojo.require("dojo.lfx.html"); +alfresco.log = alfresco.constants.DEBUG ? log : Class.empty; + +//////////////////////////////////////////////////////////////////////////////// +// constants +// +// These are the client side declared constants. Others relating to namespaces +// and the webapp context path are expected to be provided by the jsp including +// this script. +//////////////////////////////////////////////////////////////////////////////// +alfresco.xforms.constants.XFORMS_ERROR_DIV_ID = "alfresco-xforms-error"; + +alfresco.xforms.constants.EXPANDED_IMAGE = new Image(); +alfresco.xforms.constants.EXPANDED_IMAGE.src = + alfresco.constants.WEBAPP_CONTEXT + "/images/icons/expanded.gif"; + +alfresco.xforms.constants.COLLAPSED_IMAGE = new Image(); +alfresco.xforms.constants.COLLAPSED_IMAGE.src = + alfresco.constants.WEBAPP_CONTEXT + "/images/icons/collapsed.gif"; + +//////////////////////////////////////////////////////////////////////////////// +// widgets +//////////////////////////////////////////////////////////////////////////////// + +/** + * Base class for all xforms widgets. Each widget has a set of common properties, + * particularly a corresponding xforms node, a node within the browser DOM, + * a parent widget, and state variables. + */ +alfresco.xforms.Widget = new Class({ + initialize: function(xform, xformsNode, domNode) + { + this.xform = xform; + this.xformsNode = xformsNode; + this.id = this.xformsNode.getAttribute("id"); + this._modified = false; + this._valid = true; + var b = this.xform.getBinding(this.xformsNode); + if (b) + { + alfresco.log("adding " + this.id + " to binding " + b.id); + b.widgets[this.id] = this; + } + else + { + alfresco.log("no binding found for " + this.id); + } + this.domNode = domNode || new Element("div"); + this.domNode.setAttribute("id", this.id + "-domNode"); + this.domNode.widget = this; + this.domNode.addClass("xformsItem"); + }, + + ///////////////////////////////////////////////////////////////// + // properties + ///////////////////////////////////////////////////////////////// + + /** A reference to the xform. */ + xform: null, + + /** The xformsNode managed by this widget. */ + xformsNode: null, + + /** The dom node containing the label for this widget. */ + labelNode: null, + + /** The parent widget, or null if this is the root widget. */ + parentWidget: null, + + /** The dom node for this widget. */ + domNode: null, + + /** The dom node containing this widget. */ + domContainer: null, + + /** The parent widget which is using this as a composite. */ + _compositeParent: null, + + ///////////////////////////////////////////////////////////////// + // + ///////////////////////////////////////////////////////////////// + + /** Sets the widget's modified state, as indicated by an XFormsEvent. */ + setModified: function(b) + { + if (this._modified != b) + { + this._modified = b; + this._updateDisplay(false); + if (this.isValidForSubmit()) + { + this.hideAlert(); + } + } + }, + + /** Sets the widget's valid state, as indicated by an XFormsEvent */ + setValid: function(b) + { + if (this._valid != b) + { + this._valid = b; + this._updateDisplay(false); + if (this.isValidForSubmit()) + { + this.hideAlert(); + } + else + { + this.showAlert(); + } + } + }, + + /** + * Heuristic approach to determine if the widget is valid for submit or + * if it's causing an xforms-error. + */ + isValidForSubmit: function() + { + if (typeof this._valid != "undefined" && !this._valid) + { + alfresco.log(this.id + " is invalid"); + return false; + } + if (!this._modified && + this.isRequired() && + this.getInitialValue() == null) + { + alfresco.log(this.id + " is unmodified and required and empty"); + return false; + } + if (this.isRequired() && this.getValue() == null) + { + alfresco.log(this.id + " is required and empty"); + return false; + } + alfresco.log(this.id + " is valid: {" + + "modified: " + this._modified + + ", required: " + this.isRequired() + + ", initial_value: " + this.getInitialValue() + + ", value: " + this.getValue() + "}"); + return true; + }, + + /** Returns the depth of the widget within the widget heirarchy. */ + getDepth: function() + { + var result = 1; + var p = this.parentWidget; + while (p) + { + result++; + p = p.parentWidget; + } + return result; + }, + + /** Returns the root group element */ + getViewRoot: function() + { + var p = this; + while (p.parentWidget) + { + p = p.parentWidget; + } + if (! (p instanceof alfresco.xforms.ViewRoot)) + { + throw new Error("expected root widget " + p + " to be a view root"); + } + return p; + }, + + /** Returns true if the parent is an ancestor of the given parent */ + isAncestorOf: function(parentWidget) + { + var p = this; + while (p.parentWidget) + { + if (p.parentWidget == parentWidget) + { + return true; + } + p = p.parentWidget; + } + return false; + }, + + /** Sets the widget's enabled state, as indicated by an XFormsEvent */ + setEnabled: function(enabled) + { + }, + + /** Returns the widget's enabled state */ + isEnabled: function() + { + return true; + }, + + /** Sets the widget's required state, as indicated by an XFormsEvent */ + setRequired: function(b) + { + if (this._required != b) + { + this._required = b; + this._updateDisplay(false); + } + }, + + /** Indicates if a value is required for the widget. */ + isRequired: function() + { + if (typeof this._required != "undefined") + { + return this._required; + } + var binding = this.xform.getBinding(this.xformsNode); + return binding && binding.isRequired(); + }, + + /** Sets the widget's readonly state, as indicated by an XFormsEvent */ + setReadonly: function(readonly) + { + this._readonly = readonly; + }, + + /** Indicates if the widget's value is readonly. */ + isReadonly: function() + { + if (typeof this._readonly != "undefined") + { + return this._readonly; + } + var binding = this.xform.getBinding(this.xformsNode); + return binding && binding.isReadonly(); + }, + + isVisible: function() + { + return true; + }, + + /** Commits the changed value to the server */ + _commitValueChange: function(value) + { + if (this._compositeParent) + { + this._compositeParent._commitValueChange(value); + } + else + { + this.xform.setXFormsValue(this.id, value || this.getValue()); + } + }, + + /** Sets the value contained by the widget */ + setValue: function(value, forceCommit) + { + if (forceCommit) + { + this.xform.setXFormsValue(this.id, value); + } + }, + + /** Returns the value contained by the widget, or null if none is set */ + getValue: function() + { + return null; + }, + + /** Sets the widget's initial value. */ + setInitialValue: function(value, forceCommit) + { + this._initialValue = + (typeof value == "string" && value.length == 0 ? null : value); + if (forceCommit) + { + this.xform.setXFormsValue(this.id, value); + } + }, + + /** + * Returns the widget's local value, either with a local variable, or by + * looking it up within the model section. + */ + getInitialValue: function() + { + if (typeof this._initialValue != "undefined") + { + return this._initialValue; + } + + var xpath = this._getXPathInInstanceDocument(); + var d = this.xformsNode.ownerDocument; + var contextNode = this.xform.getInstance(); + alfresco.log("locating " + xpath + " in " + contextNode.nodeName); + this._initialValue = _evaluateXPath("/" + xpath, + this.xform.getInstance(), + XPathResult.FIRST_ORDERED_NODE_TYPE); + if (!this._initialValue) + { + alfresco.log("unable to resolve xpath /" + xpath + " for " + this.id); + this._initialValue = null; + } + else + { + this._initialValue = (this._initialValue.nodeType == document.ELEMENT_NODE + ? (this._initialValue.firstChild + ? this._initialValue.firstChild.nodeValue + : null) + : this._initialValue.nodeValue); + if (typeof this._initialValue == "string" && this._initialValue.length == 0) + { + this._initialValue = null; + } + alfresco.log("resolved xpath " + xpath + " to " + this._initialValue); + } + return this._initialValue; + }, + + /** Produces an xpath to the model node within the instance data document. */ + _getXPathInInstanceDocument: function() + { + var binding = this.xform.getBinding(this.xformsNode); + var xpath = ''; + var repeatIndices = this.getRepeatIndices(); + do + { + var s = binding.nodeset; + if (binding.nodeset == '.') + { + binding = binding.parentBinding; + } + if (binding.nodeset.match(/.+\[.+\]/)) + { + s = binding.nodeset.replace(/([^\[]+)\[.*/, "$1"); + s += '[' + (repeatIndices.shift().index) + ']'; + } + xpath = s + (xpath.length != 0 ? '/' + xpath : ""); + binding = binding.parentBinding; + } + while (binding); + return xpath; + }, + + /** Returns a child node by name within the xform. */ + _getChildXFormsNode: function(nodeName) + { + var x = _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + nodeName); + for (var i = 0; i < x.length; i++) + { + if (x[i].parentNode == this.xformsNode) + { + return x[i]; + } + } + return null; + }, + + /** Returns the widget's label. */ + getLabel: function() + { + var node = this._getChildXFormsNode("label"); + var result = node ? node.firstChild.nodeValue : ""; + if (alfresco.constants.DEBUG) + { + result += " [" + this.id + "]"; + } + return result; + }, + + /** Returns the widget's alert text. */ + getAlert: function() + { + var node = this._getChildXFormsNode("alert"); + return node ? node.firstChild.nodeValue : ""; + }, + + /** Returns the widget's alert text. */ + getHint: function() + { + var node = this._getChildXFormsNode("hint"); + return node ? node.firstChild.nodeValue : null; + }, + + /** Makes the label red. */ + showAlert: function() + { + if (!this.labelNode.hasClass("xformsItemLabelSubmitError")) + { + this.labelNode.addClass("xformsItemLabelSubmitError"); + } + }, + + /** Restores the label to its original color. */ + hideAlert: function() + { + if (this.labelNode.hasClass("xformsItemLabelSubmitError")) + { + this.labelNode.removeClass("xformsItemLabelSubmitError"); + } + }, + + /** Returns the value of the appearance attribute for widget */ + getAppearance: function() + { + var result = (this.xformsNode.getAttribute("appearance") || + this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance")); + return result == null || result.length == 0 ? null : result; + }, + + /** Updates the display of the widget. This is intended to be overridden. */ + _updateDisplay: function(recursively) + { + }, + + /** Destroy the widget and any resources no longer needed. */ + _destroy: function() + { + alfresco.log("destroying " + this.id); + }, + + /** + * Returns an array of RepeatIndexDatas corresponding to all enclosing repeats. + * The closest repeat will be at index 0. + */ + getRepeatIndices: function() + { + var result = []; + var w = this; + while (w.parentWidget) + { + if (w.parentWidget instanceof alfresco.xforms.Repeat) + { + result.push(new alfresco.xforms.RepeatIndexData(w.parentWidget, + w.parentWidget.getChildIndex(w) + 1)); + } + w = w.parentWidget; + } + return result; + }, + + /** + */ + getParentGroups: function(appearance) + { + var result = []; + var w = this; + while (w.parentWidget) + { + if (w.parentWidget instanceof alfresco.xforms.AbstractGroup) + { + if (appearance && w.parentWidget.getAppearance() == appearance) + { + result.push(w.parentWidget); + } + } + w = w.parentWidget; + } + return result; + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// widgets for atomic types +//////////////////////////////////////////////////////////////////////////////// + +/** The file picker widget which handles xforms widget xf:upload. */ +alfresco.xforms.FilePicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode, params) + { + this.parent(xform, xformsNode); + this._selectableTypes = "selectable_types" in params ? params["selectable_types"].split(",") : null; + this._filterMimetypes = "filter_mimetypes" in params ? params["filter_mimetypes"].split(",") : []; + this._folderRestriction = "folder_restriction" in params ? params["folder_restriction"] : null; + this._configSearchName = "config_search_name" in params ? params["config_search_name"] : null; }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + this.domNode.addClass("xformsFilePicker"); + attach_point.appendChild(this.domNode); + //XXXarielb support readonly and disabled + this.widget = new alfresco.FilePickerWidget(this.id, + this.domNode, + this.getInitialValue(), + false, + this._filePicker_changeHandler.bindAsEventListener(this), + null /* cancel is ignored */, + this._filePicker_resizeHandler.bindAsEventListener(this), + this._selectableTypes, + this._filterMimetypes, + this._folderRestriction, + this._configSearchName); + this.widget.render(); + }, + + getValue: function() + { + return this.widget.getValue(); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this.widget.setValue(value); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _filePicker_changeHandler: function(fpw) + { + this._commitValueChange(); + }, + + _filePicker_resizeHandler: function(fpw) + { + this.domContainer.style.height = + Math.max(fpw.node.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + } +}); + +/** The textfield widget which handle xforms widget xf:input with any string or numerical type */ +alfresco.xforms.TextField = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("input", { type: "text" })); + this._maxLength = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxLength") + ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxLength")) + : -1); + this._length = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":length") + ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":length")) + : -1); + + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue() || ""; + attach_point.appendChild(this.domNode); + + this.widget = this.domNode; + this.widget.setAttribute("value", initial_value); + if (this._maxLength >= 0 || this._length >= 0) + { + this.widget.setAttribute("maxlength", this._maxLength >= 0 ? this._maxLength : this._length); + this.widget.style.maxWidth = "100%"; + this.widget.setAttribute("size", this._maxLength >= 0 ? this._maxLength : this._length); + } + else if (this.getAppearance() == "full") + { + var borderWidth = (this.widget.offsetWidth - this.widget.clientWidth); + var marginRight = 2; + this.widget.style.marginRight = marginRight + "px"; + this.widget.style.width = (((attach_point.offsetWidth - borderWidth - marginRight) / attach_point.offsetWidth) * 100) + "%"; + this.widget.style.minWidth = "50px"; + } + + if (this.isReadonly()) + { + this.widget.setAttribute("readonly", this.isReadonly()); + this.widget.setAttribute("disabled", this.isReadonly()); + } + else + { + this.widget.onblur = this._widget_changeHandler.bindAsEventListener(this); + } + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this.widget.value = value; + } + }, + + getValue: function() + { + return (this.widget.value != null && this.widget.value.length == 0 + ? null + : this.widget.value); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _widget_changeHandler: function(event) + { + this._commitValueChange(); + } +}); + +/** The number range widget which handle xforms widget xf:range with any numerical type */ +alfresco.xforms.NumericalRange = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + dojo.require("dojo.widget.Slider"); + this._fractionDigits = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":fractionDigits") + ? Number(this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":fractionDigits")) + : -1); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue() || ""; + attach_point.appendChild(this.domNode); + var sliderDiv = document.createElement("div"); + sliderDiv.style.fontWeight = "bold"; + sliderDiv.style.marginBottom = "5px"; + this.domNode.appendChild(sliderDiv); + + var minimum = Number(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":start")); + var maximum = Number(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":end")); + var snapValues = 0; + if (this._fractionDigits == 0) + { + snapValues = maximum - minimum + 1; + } + sliderDiv.appendChild(document.createTextNode(minimum)); + + var sliderWidgetDiv = document.createElement("div"); + sliderDiv.appendChild(sliderWidgetDiv); + this.widget = dojo.widget.createWidget("SliderHorizontal", + { + initialValue: initial_value, + minimumX: minimum, + maximumX: maximum, + showButtons: false, + activeDrag: false, + snapValues: snapValues + }, + sliderWidgetDiv); + sliderDiv.appendChild(document.createTextNode(maximum)); + + this.currentValueDiv = document.createElement("div"); + this.domNode.appendChild(this.currentValueDiv); + this.currentValueDiv.appendChild(document.createTextNode("Value: " + initial_value)); + + dojo.event.connect(this.widget, + "onValueChanged", + this, + this._hSlider_valueChangedHandler); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this.widget.setValue(value); + } + }, + + getValue: function() + { + return this.widget.getValue(); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _hSlider_valueChangedHandler: function(value) + { + if (this._fractionDigits >= 0) + { + value = Math.round(value * Math.pow(10, this._fractionDigits)) / Math.pow(10, this._fractionDigits); + } + this.currentValueDiv.replaceChild(document.createTextNode("Value: " + value), + this.currentValueDiv.firstChild); + if (!this.widget._isDragInProgress) + { + this._commitValueChange(); + } + } +}); + +/** The text area widget handles xforms widget xf:textarea with appearance minimal */ +alfresco.xforms.PlainTextEditor = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("textarea")); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + attach_point.appendChild(this.domNode); + this.domNode.addClass("xformsTextArea"); + var initialValue = this.getInitialValue() || ""; + this.widget = this.domNode; + this.widget.appendChild(document.createTextNode(initialValue)); + if (this.isReadonly()) + { + this.widget.setAttribute("readonly", this.isReadonly()); + } + var borderWidth = (this.widget.offsetWidth - this.widget.clientWidth); + var marginRight = 2; + this.widget.style.marginRight = marginRight + "px"; + this.widget.style.width = (((attach_point.offsetWidth - borderWidth - marginRight) / attach_point.offsetWidth) * 100) + "%"; + this.widget.onchange =this._textarea_changeHandler.bindAsEventListener(this); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this.widget.value = value; + } + }, + + getValue: function() + { + return this.widget.value; + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _textarea_changeHandler: function(event) + { + this._commitValueChange(); + } +}); + +/** The textfield widget which handle xforms widget xf:textarea. with appearance full or compact */ +alfresco.xforms.RichTextEditor = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode, params) + { + this.parent(xform, xformsNode); + this._focused = false; + this._params = params; + this._oldValue = null; + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + _removeTinyMCE: function() + { + var value = this.getValue(); //tinyMCE.getContent(this.id); + if (value != this._oldValue) + { + alfresco.log("commitValueChange from _removeTinyMCE [" + value + "]"); + this._commitValueChange(value); + this._oldValue = value; + } + tinyMCE.removeMCEControl(this.id); + this._focused = false; + }, + + _createTinyMCE:function() + { + if (alfresco.xforms.RichTextEditor.currentInstance && + alfresco.xforms.RichTextEditor.currentInstance != this) + { + alfresco.xforms.RichTextEditor.currentInstance._removeTinyMCE(); + } + + alfresco.xforms.RichTextEditor.currentInstance = this; + + for (var i in alfresco.constants.TINY_MCE_DEFAULT_SETTINGS) + { + if (!(i in this._params)) + { + this._params[i] = alfresco.constants.TINY_MCE_DEFAULT_SETTINGS[i]; + } + } + for (var i in this._params) + { + if (i in tinyMCE.settings) + { + alfresco.log("setting tinyMCE.settings[" + i + "] = " + this._params[i]); + tinyMCE.settings[i] = this._params[i]; + } + } + tinyMCE.settings.height = this._params["height"] ? parseInt(this._params["height"]) : -1; + tinyMCE.settings.auto_focus = this.id; + tinyMCE.addMCEControl(this.widget, this.id); + + tinyMCE.getInstanceById(this.id).getWin().focus(); + var editorDocument = tinyMCE.getInstanceById(this.id).getDoc(); + editorDocument.widget = this; + + tinyMCE.addEvent(editorDocument, + window.ie ? "beforedeactivate" : "blur", + this._tinyMCE_blurHandler); + tinyMCE.addEvent(editorDocument, "focus", this._tinyMCE_focusHandler); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + attach_point.appendChild(this.domNode); + this.domNode.addClass("xformsTextArea"); + if (this._params.height) + { + this.domNode.setStyle("height", parseInt(this._params["height"]) + "px"); + } + this.widget = new Element("div"); + this.domNode.appendChild(this.widget); + this.widget.addClass("xformsTextArea"); + if (this._params["height"]) + { + this.widget.setStyle("height", parseInt(this._params["height"]) + "px"); + } + this.widget.style.border = "2px inset #f0f0f0"; + this.widget.style.marginRight = "2px"; + this.widget.style.overflow = "auto"; + this._oldValue = this.getInitialValue() || ""; + this.widget.innerHTML = this._oldValue; + + $each(this.widget.getElementsByTagName("img"), + function(img, index) + { + if (img.getAttribute("src") && img.getAttribute("src").match("^/")) + { + img.setAttribute("src", alfresco.constants.AVM_WEBAPP_URL + img.getAttribute("src")); + } + }); + if (!this.isReadonly()) + { + this.widget.onmouseover = this._div_mouseoverHandler.bindAsEventListener(this); + } + }, + + setValue: function(value, forceCommit) + { + if (value != this._oldValue || forceCommit) + { + if (alfresco.xforms.RichTextEditor.currentInstance == this) + { + tinyMCE.selectedInstance = tinyMCE.getInstanceById(this.id); + try + { + tinyMCE.setContent(value); + } + catch (e) + { + //XXXarielb figure this out - getting intermittent errors in IE. + alfresco.log(e); + } + } + else + { + this.widget.innerHTML = value; + } + } + this.parent(value, forceCommit); + }, + + getValue: function() + { + var result = (alfresco.xforms.RichTextEditor.currentInstance == this + ? tinyMCE.getContent(this.id) + : this.widget.innerHTML); + result = result.replace(new RegExp(alfresco.constants.AVM_WEBAPP_URL, "g"), ""); + return result; + }, + + setReadonly: function(readonly) + { + this.parent(readonly); + if (readonly && alfresco.xforms.RichTextEditor.currentInstance == this) + { + this._removeTinyMCE(); + } + }, + + _destroy: function() + { + this.parent(); + if (!this.isReadonly()) + { + alfresco.log("removing mce control " + this.id); + tinyMCE.removeMCEControl(this.id); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _tinyMCE_blurHandler: function(event) + { + if (event.type == "beforedeactivate") + { + event.target = event.srcElement.ownerDocument; + } + var widget = event.target.widget; + var value = widget.getValue(); + if (value != widget._oldValue) + { + alfresco.log("commitValueChange from _tinyMCE_blurHandler [" + value + "]"); + widget._commitValueChange(value); + widget._oldValue = value; + } + widget._focused = false; + }, + + _tinyMCE_focusHandler: function(event) + { + var widget = event.target.widget; + var repeatIndices = widget.getRepeatIndices(); + if (repeatIndices.length != 0 && !widget._focused) + { + var r = repeatIndices[repeatIndices.length - 1].repeat; + var p = widget; + while (p && p.parentWidget != r) + { + if (p.parentWidget instanceof alfresco.xforms.Repeat) + { + throw new Error("unexpected parent repeat " + p.parentWidget.id); + } + p = p.parentWidget; + } + if (!p) + { + throw new Error("unable to find parent repeat " + r.id + + " of " + widget.id); + } + repeatIndices[repeatIndices.length - 1].repeat.setFocusedChild(p); + } + widget._focused = true; + }, + + _div_mouseoverHandler: function(event) + { + if (!this.hoverLayer) + { + this.hoverLayer = new Element("div"); + this.hoverLayer.addClass("xformsRichTextEditorHoverLayer"); + this.hoverLayer.setText(alfresco.resources["click_to_edit"]); + } + if (this.hoverLayer.parentNode != this.widget) + { + this.widget.appendChild(this.hoverLayer); + + this.hoverLayer.style.lineHeight = this.hoverLayer.offsetHeight + "px"; + this.hoverLayer.setOpacity(.8); + this.hoverLayer.onmouseout = this._hoverLayer_mouseoutHandler.bindAsEventListener(this); + this.hoverLayer.onclick = this._hoverLayer_clickHandler.bindAsEventListener(this); + } + }, + + _hoverLayer_mouseoutHandler: function(event) + { + if (this.hoverLayer.parentNode == this.widget) + { + this.hoverLayer.setOpacity(1); + this.widget.removeChild(this.hoverLayer); + } + }, + + _hoverLayer_clickHandler: function(event) + { + if (this.hoverLayer.parentNode == this.widget) + { + this.hoverLayer.setOpacity(1); + this.widget.removeChild(this.hoverLayer); + this._createTinyMCE(); + } + } +}); + +/** The currently rendered rich text editor instance */ +alfresco.xforms.RichTextEditor.currentInstance = null; + +/** + * Reads the widget configuration to determine which plugins will + * be needed by tinymce. All plugins must be loaded into tinymce at + * startup so they must be accumulated in advance. + */ +alfresco.xforms.RichTextEditor.determineNecessaryTinyMCEPlugins = function(config) +{ + var result = []; + for (var widget in config) + { + for (var schemaType in config[widget]) + { + for (var appearance in config[widget][schemaType]) + { + if (config[widget][schemaType][appearance].className == "alfresco.xforms.RichTextEditor" && + config[widget][schemaType][appearance].params && + config[widget][schemaType][appearance].params.plugins) + { + alfresco.log("found plugins definition " + config[widget][schemaType][appearance].params.plugins + + " for text editor at config[" + widget + "][" + schemaType + "][" + appearance + "]"); + var plugins = config[widget][schemaType][appearance].params.plugins.split(","); + for (var p = 0; p < plugins.length; p++) + { + if (result.indexOf(plugins[p]) < 0) + { + result.push(plugins[p]); + } + } + } + } + } + } + return result.join(","); +} + +/** Base class for all select widgets. */ +alfresco.xforms.AbstractSelectWidget = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode, domNode) + { + this.parent(xform, xformsNode, domNode); + }, + + ///////////////////////////////////////////////////////////////// + // methods + ///////////////////////////////////////////////////////////////// + + /** + * Returns the possible item values for the select control as an array + * of anonymous objects with properties id, label, value, and valid. + */ + _getItemValues: function() + { + var binding = this.xform.getBinding(this.xformsNode); + var values = _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "item"); + var result = []; + for (var i = 0; i < values.length; i++) + { + var label = _getElementsByTagNameNS(values[i], + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "label")[0]; + label = label.firstChild.nodeValue; + var value = _getElementsByTagNameNS(values[i], + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "value")[0]; + var valueText = value.firstChild.nodeValue; + var itemId = value.getAttribute("id"); + var valid = true; + if (binding.constraint) + { + if (!window.ie) + { + valid = _evaluateXPath(binding.constraint, value, XPathResult.BOOLEAN_TYPE); + if (alfresco.constants.DEBUG) + { + alfresco.log("evaludated constraint " + binding.constraint + + " on " + value + " to " + valid); + } + } + else + { + valid = !(valueText == label && valueText.match(/^\[.+\]$/)); + } + } + result.push({ + id: itemId, + label: valid ? label : "", + value: valid ? valueText : "_invalid_value_", + valid: valid + }); + + if (alfresco.constants.DEBUG) + { + alfresco.log("values["+ i + "] = {id: " + result[i].id + + ",label: " + result[i].label + ",value: " + result[i].value + + ",valid: " + result[i].valid + "}"); + } + } + return result; + } +}); + +/** + * Handles xforms widget xf:select. Produces either a multiselect list or a set of + * checkboxes depending on the number of inputs. + */ +alfresco.xforms.CheckboxSelect = alfresco.xforms.AbstractSelectWidget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var values = this._getItemValues(); + var initial_value = this.getInitialValue(); + initial_value = initial_value ? initial_value.split(' ') : []; + this._selectedValues = []; + this.widget = this.domNode; + attach_point.appendChild(this.domNode); + for (var i = 0; i < values.length; i++) + { + var checkboxDiv = document.createElement("div"); + checkboxDiv.style.lineHeight = "16px"; + this.widget.appendChild(checkboxDiv); + + var checkbox = new Element("input"); + checkbox.setAttribute("id", this.id + "_" + i + "-widget"); + checkbox.setAttribute("name", this.id + "_" + i + "-widget"); + checkbox.setAttribute("type", "checkbox"); + checkbox.setAttribute("value", values[i].value); + if (initial_value.indexOf(values[i].value) != -1) + { + this._selectedValues.push(values[i].value); + checkbox.checked = true; + } + checkboxDiv.appendChild(checkbox); + checkboxDiv.appendChild(document.createTextNode(values[i].label)); + checkbox.onclick = this._checkbox_clickHandler.bindAsEventListener(this); + } + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this._selectedValues = value.split(' '); + var checkboxes = this.widgets.getElementsByTagName("input"); + for (var i = 0; i < checkboxes.length; i++) + { + checkboxes[i].checked = + this._selectedValues.indexOf(checkboxes[i].getAttribute("value")) != -1; + } + } + }, + + getValue: function() + { + return this._selectedValues.length == 0 ? null : this._selectedValues.join(" "); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _checkbox_clickHandler: function(event) + { + this._selectedValues = []; + var all_checkboxes = this.widget.getElementsByTagName("input"); + for (var i = 0; i < all_checkboxes.length; i++) + { + if (all_checkboxes[i] && all_checkboxes[i].checked) + { + this._selectedValues.push(all_checkboxes[i].getAttribute("value")); + } + } + this._commitValueChange(); + } +}); + +/** + * Handles xforms widget xf:select. Produces either a multiselect list or a set of + * checkboxes depending on the number of inputs. + */ +alfresco.xforms.ListSelect = alfresco.xforms.AbstractSelectWidget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("select")); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var values = this._getItemValues(); + var initial_value = this.getInitialValue(); + initial_value = initial_value ? initial_value.split(' ') : []; + this._selectedValues = []; + attach_point.appendChild(this.domNode); + this.widget = this.domNode; + this.widget.setAttribute("multiple", true); + attach_point.appendChild(this.widget); + for (var i = 0; i < values.length; i++) + { + var option = document.createElement("option"); + option.appendChild(document.createTextNode(values[i].label)); + option.setAttribute("value", values[i].value); + if (initial_value.indexOf(values[i].value) != -1) + { + this._selectedValues.push(values[i].value); + option.selected = true; + } + this.widget.appendChild(option); + } + this.widget.onblur = this._list_changeHandler.bindAsEventListener(this); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this._selectedValues = value.split(' '); + var options = this.widgets.getElementsByTagName("option"); + for (var i = 0; i < options.length; i++) + { + options[i].selected = + this._selectedValues.indexOf(options[i].getAttribute("value")) != -1; + } + } + }, + + getValue: function() + { + return this._selectedValues.length == 0 ? null : this._selectedValues.join(" "); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _list_changeHandler: function(event) + { + this._selectedValues = []; + for (var i = 0; i < event.target.options.length; i++) + { + if (event.target.options[i].selected) + { + this._selectedValues.push(event.target.options[i].getAttribute("value")); + } + } + this._commitValueChange(); + } +}); + +/** + * Handles xforms widget xf:select1. Produces either a combobox or a set of + * radios depending on the number of inputs. + */ +alfresco.xforms.RadioSelect1 = alfresco.xforms.AbstractSelectWidget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var values = this._getItemValues(); + var initial_value = this.getInitialValue(); + this.widget = this.domNode; + attach_point.appendChild(this.domNode); + for (var i = 0; i < values.length; i++) + { + if (!values[i].valid) + { + // always skip the invalid values for radios + continue; + } + + var radio_div = document.createElement("div"); + radio_div.style.lineHeight = "16px"; + this.widget.appendChild(radio_div); + var radio = new Element("input"); + radio.setAttribute("id", this.id + "-widget"); + radio.setAttribute("name", this.id + "-widget"); + radio.setAttribute("type", "radio"); + radio_div.appendChild(radio); + radio_div.appendChild(document.createTextNode(values[i].label)); + + radio.setAttribute("value", values[i].value); + if (values[i].value == initial_value) + { + this._selectedValue = initial_value; + radio.checked = true; + } + if (this.isReadonly()) + { + radio.setAttribute("disabled", true); + } + radio.onclick = this._radio_clickHandler.bindAsEventListener(this); + } + this.widget.style.height = this.widget.offsetHeight + "px"; + }, + + /** */ + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this._selectedValue = value; + var radios = this.widget.getElementsByTagName("input"); + for (var i = 0; i < radios.length; i++) + { + radios[i].checked = radios[i].getAttribute("value") == this._selectedValue; + } + } + }, + + getValue: function() + { + return this._selectedValue; + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _radio_clickHandler: function(event) + { + if (!event.target.checked) + { + var all_radios = this.widget.getElementsByTagName("input"); + for (var i = 0; i < all_radios.length; i++) + { + if (all_radios[i].name == event.target.name) + { + all_radios[i].checked = event.target == all_radios[i]; + } + } + } + this._selectedValue = event.target.value; + this._commitValueChange(); + } +}); + +/** + * Handles xforms widget xf:select1. Produces either a combobox or a set of + * radios depending on the number of inputs. + */ +alfresco.xforms.ComboboxSelect1 = alfresco.xforms.AbstractSelectWidget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("select")); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var values = this._getItemValues(); + var initial_value = this.getInitialValue(); + this.domNode = new Element("select"); + attach_point.appendChild(this.domNode); + this.widget = this.domNode; + for (var i = 0; i < values.length; i++) + { + if (initial_value && !values[i].valid) + { + // skip the invalid value if we have a default value + continue; + } + var option = new Element("option"); + this.widget.appendChild(option); + option.appendChild(document.createTextNode(values[i].label)); + option.setAttribute("value", values[i].value); + if (values[i].value == initial_value) + { + this._selectedValue = initial_value; + option.selected = true; + } + + if (this.isReadonly()) + { + this.widget.setAttribute("disabled", true); + } + } + this.widget.onchange = this._combobox_changeHandler.bindAsEventListener(this); + }, + + /** */ + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this._selectedValue = value; + var options = this.widget.getElementsByTagName("option"); + for (var i = 0; i < options.length; i++) + { + options[i].selected = options[i].getAttribute("value") == this._selectedValue; + } + } + }, + + getValue: function() + { + return this._selectedValue; + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _combobox_changeHandler: function(event) + { + this._selectedValue = event.target.options[event.target.selectedIndex].value; + this._commitValueChange(); + } +}); + +/** + * Handles xforms widget xf:select1 with a type of boolean. + */ +alfresco.xforms.Checkbox = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, + xformsNode, + new Element("input", { type: "checkbox" })); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue() == "true"; + attach_point.appendChild(this.domNode); + this.widget = this.domNode; + + if (initial_value) + { + this.widget.setAttribute("checked", true); + } + if (this.isReadonly()) + { + this.widget.setAttribute("disabled", true); + } + this.widget.onclick = this._checkbox_clickHandler.bindAsEventListener(this); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + this.widget.checked = value == "true"; + } + }, + + getValue: function() + { + return this.widget.checked; + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _checkbox_clickHandler: function(event) + { + this._commitValueChange(); + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// widgets for date types +//////////////////////////////////////////////////////////////////////////////// + +/** The date picker widget which handles xforms widget xf:input with type xf:date */ +alfresco.xforms.DatePicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + this._minInclusive = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":minInclusive") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":minInclusive") + : null); + this._maxInclusive = (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxInclusive") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":maxInclusive") + : null); + + dojo.require("dojo.date.format"); + // XXXarielb - change to a static + this._noValueSet = (alfresco.resources["eg"] + " " + + dojo.date.format(new Date(), + {datePattern: alfresco.xforms.constants.DATE_FORMAT, + selector: 'dateOnly'})); + }, + + _createPicker: function() + { + dojo.require("dojo.widget.DatePicker"); + var datePickerDiv = document.createElement("div"); + this.domNode.parentNode.appendChild(datePickerDiv); + + var dp_initial_value = this.getValue() || null; //dojo.date.toRfc3339(new Date()); + var datePickerProperties = { value: dp_initial_value }; + if (this._minInclusive) + { + datePickerProperties.startDate = this._minInclusive; + } + if (this._maxInclusive) + { + datePickerProperties.endDate = this._maxInclusive; + } + + this.widget.picker = dojo.widget.createWidget("DatePicker", + datePickerProperties, + datePickerDiv); + this.domContainer.style.height = + Math.max(this.widget.picker.domNode.offsetHeight + + this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + + dojo.event.connect(this.widget.picker, + "onValueChanged", + this, + this._datePicker_valueChangedHandler); + }, + + _destroyPicker: function() + { + if (this.widget.picker) + { + this.domNode.parentNode.removeChild(this.widget.picker.domNode); + this.widget.picker = null; + this.domContainer.style.height = + Math.max(this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-bottom").toInt() + + this.domNode.parentNode.getStyle("margin-top").toInt(), + 20) + "px"; + } + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue(); + attach_point.appendChild(this.domNode); + this.widget = new Element("input", { "id": this.id + "-widget", "type": "text"}); + if (initial_value) + { + var jsDate = dojo.date.fromRfc3339(initial_value); + this.widget.setAttribute("value", + dojo.date.format(jsDate, + {datePattern: alfresco.xforms.constants.DATE_FORMAT, + selector: 'dateOnly'})); + } + else + { + this.widget.setAttribute("value", this._noValueSet); + this.widget.addClass("xformsGhostText"); + } + if (this.isReadonly()) + { + this.widget.setAttribute("disabled", true); + } + this.domNode.appendChild(this.widget); + + var expandoImage = new Element("img"); + expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); + expandoImage.align = "absmiddle"; + expandoImage.style.margin = "0px 5px"; + + this.domNode.appendChild(expandoImage); + + if (!this.isReadonly()) + { + expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); + this.widget.onfocus = this._dateTextBox_focusHandler.bindAsEventListener(this); + this.widget.onchange = this._dateTextBox_changeHandler.bindAsEventListener(this); + } + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + var jsDate = dojo.date.fromRfc3339(value); + this.widget.value = dojo.date.format(jsDate, + {datePattern: alfresco.xforms.constants.DATE_FORMAT, + selector: 'dateOnly'}); + this.widget.removeClass("xformsGhostText"); + } + }, + + getValue: function() + { + if (this.widget.value == null || + this.widget.value.length == 0 || + this.widget.value == this._noValueSet) + { + return null; + } + else + { + var jsDate = dojo.date.parse(this.widget.value, + {datePattern: alfresco.xforms.constants.DATE_FORMAT, + selector: 'dateOnly'}); + return dojo.date.toRfc3339(jsDate, "dateOnly"); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _dateTextBox_focusHandler: function(event) + { + this._destroyPicker(); + }, + + _dateTextBox_changeHandler: function(event) + { + this._commitValueChange(); + }, + + _datePicker_valueChangedHandler: function(date) + { + var rfcDate = dojo.date.toRfc3339(date, "dateOnly"); + this._destroyPicker(); + this.setValue(rfcDate); + this._commitValueChange(); + }, + + _expando_clickHandler: function() + { + if (this.widget.picker) + { + this._destroyPicker(); + } + else + { + this._createPicker(); + } + } +}); + +/** The date picker widget which handles xforms widget xf:input with type xf:date */ +alfresco.xforms.TimePicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + dojo.require("dojo.date.format"); + this._noValueSet = (alfresco.resources["eg"] + " " + + dojo.date.format(new Date(), + {timePattern: alfresco.xforms.constants.TIME_FORMAT, + selector: "timeOnly"})); + this._xformsFormat = "HH:mm:ss.S"; + }, + /** */ + _createPicker: function() + { + dojo.require("dojo.widget.TimePicker"); + var timePickerDiv = document.createElement("div"); + this.domNode.appendChild(timePickerDiv); + var jsDate = (this.getValue() + ? dojo.date.parse(this.getValue(), + {timePattern: this._xformsFormat, + selector: "timeOnly"}) + : new Date()); + this.widget.picker = dojo.widget.createWidget("TimePicker", + { + value: jsDate + }, + timePickerDiv); + this.widget.picker.anyTimeContainerNode.innerHTML = ""; + + // don't let it float - it screws up layout somehow + this.widget.picker.domNode.style.cssFloat = "none"; + this.domContainer.style.height = + Math.max(this.widget.picker.domNode.offsetHeight + + this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + dojo.event.connect(this.widget.picker, + "onValueChanged", + this, + this._timePicker_valueChangedHandler); + }, + + _destroyPicker: function() + { + if (this.widget.picker) + { + this.domNode.removeChild(this.widget.picker.domNode); + this.widget.picker = null; + this.domContainer.style.height = + Math.max(this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + } + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue(); + + attach_point.appendChild(this.domNode); + this.widget = new Element("input", { "id": this.id + "-widget", "type": "text" }); + if (initial_value) + { + var jsDate = dojo.date.parse(initial_value, {timePattern: this._xformsFormat, selector: "timeOnly"}); + this.widget.setAttribute("value", + dojo.date.format(jsDate, + {timePattern: alfresco.xforms.constants.TIME_FORMAT, + selector: "timeOnly"})); + } + else + { + this.widget.setAttribute("value", this._noValueSet); + this.widget.addClass("xformsGhostText"); + } + if (this.isReadonly()) + { + this.widget.setAttribute("disabled", true); + } + this.domNode.appendChild(this.widget); + + var expandoImage = new Element("img"); + expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); + expandoImage.align = "absmiddle"; + expandoImage.style.margin = "0px 5px"; + + this.domNode.appendChild(expandoImage); + + if (!this.isReadonly()) + { + expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); + this.widget.onfocus = this._timeTextBox_focusHandler.bindAsEventListener(this); + this.widget.onchange = this._timeTextBox_changeHandler.bindAsEventListener(this); + } + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + var jsDate = dojo.date.parse(value, {timePattern: this._xformsFormat, selector: "timeOnly"}); + this.widget.value = dojo.date.format(jsDate, + {timePattern: alfresco.xforms.constants.TIME_FORMAT, + selector: "timeOnly"}); + this.widget.removeClass("xformsghosttext"); + } + }, + + getValue: function() + { + if (this.widget.value == null || + this.widget.value.length == 0 || + this.widget.value == this._noValueSet) + { + return null; + } + else + { + var jsDate = dojo.date.parse(this.widget.value, + {timePattern: alfresco.xforms.constants.TIME_FORMAT, + selector: "timeOnly"}); + return dojo.date.format(jsDate, {timePattern: this._xformsFormat, selector: "timeOnly"}); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _timeTextBox_focusHandler: function(event) + { + this._destroyPicker(); + }, + + _timeTextBox_changeHandler: function(event) + { + this._commitValueChange(); + }, + + _timePicker_valueChangedHandler: function(date) + { + var xfDate = dojo.date.format(date, {timePattern: this._xformsFormat, selector: "timeOnly"}); + this.setValue(xfDate); + this._commitValueChange(); + }, + + _expando_clickHandler: function() + { + if (this.widget.picker) + { + this._destroyPicker(); + } + else + { + this._createPicker(); + } + } +}); + +/** The date time picker widget which handles xforms widget xf:input with type xf:datetime */ +alfresco.xforms.DateTimePicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + dojo.require("dojo.date.format"); + this._noValueSet = (alfresco.resources["eg"] + " " + + dojo.date.format(new Date(), + {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, + selector: "dateOnly"})); + }, + + /** */ + _createPicker: function() + { + dojo.require("dojo.widget.DatePicker"); + dojo.require("dojo.widget.TimePicker"); + + this._pickerDiv = document.createElement("div"); + this._pickerDiv.style.position = "relative"; + this._pickerDiv.style.width = this.widget.offsetWidth + "px"; + this.domNode.appendChild(this._pickerDiv); + + var datePickerDiv = document.createElement("div"); + datePickerDiv.style.position = "absolute"; + datePickerDiv.style.left = "0px"; + datePickerDiv.style.top = "0px"; + this._pickerDiv.appendChild(datePickerDiv); + + var dp_initial_value = this.getValue() || dojo.date.toRfc3339(new Date()); + this.widget.datePicker = dojo.widget.createWidget("DatePicker", + { + value: dp_initial_value + }, + datePickerDiv); + var timePickerDiv = document.createElement("div"); + timePickerDiv.style.position = "absolute"; + timePickerDiv.style.right = "0px"; + timePickerDiv.style.top = "0px"; + this._pickerDiv.appendChild(timePickerDiv); + + var jsDate = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); + this.widget.timePicker = dojo.widget.createWidget("TimePicker", + { + value: jsDate + }, + timePickerDiv); + this.widget.timePicker.anyTimeContainerNode.innerHTML = ""; + + // don't let it float - it screws up layout somehow + this.widget.timePicker.domNode.style.cssFloat = "none"; + this._pickerDiv.style.height = Math.max(this.widget.timePicker.domNode.offsetHeight, + this.widget.datePicker.domNode.offsetHeight); + this.domContainer.style.height = + Math.max(this._pickerDiv.offsetHeight + + this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + dojo.event.connect(this.widget.datePicker, + "onValueChanged", + this, + this._datePicker_valueChangedHandler); + dojo.event.connect(this.widget.timePicker, + "onValueChanged", + this, + this._timePicker_valueChangedHandler); + }, + + _destroyPicker: function() + { + if (this._pickerDiv) + { + this.domNode.removeChild(this._pickerDiv); + this.widget.datePicker = null; + this.widget.timePicker = null; + this._pickerDiv = null; + this.domContainer.style.height = + Math.max(this.widget.offsetHeight + + this.domNode.parentNode.getStyle("margin-top").toInt() + + this.domNode.parentNode.getStyle("margin-bottom").toInt(), + 20) + "px"; + } + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + var initial_value = this.getInitialValue(); + + attach_point.appendChild(this.domNode); + this.widget = new Element("input", { "id": this.id + "-widget", "type": "text" }); + if (initial_value) + { + var jsDate = dojo.date.fromRfc3339(initial_value); + this.widget.setAttribute("value", + dojo.date.format(jsDate, + {timePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, + selector: "timeOnly"})); + } + else + { + this.widget.setAttribute("value", this._noValueSet); + this.widget.addClass("xformsGhostText"); + } + this.domNode.appendChild(this.widget); + this.widget.style.width = (3 * this.widget.offsetWidth) + "px"; + + var expandoImage = new Element("img"); + expandoImage.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/action.gif"); + expandoImage.align = "absmiddle"; + expandoImage.style.margin = "0px 5px"; + + this.domNode.appendChild(expandoImage); + + expandoImage.onclick = this._expando_clickHandler.bindAsEventListener(this); + this.widget.onfocus = this._dateTimeTextBox_focusHandler.bindAsEventListener(this); + this.widget.onchange = this._dateTimeTextBox_changeHandler.bindAsEventListener(this); + }, + + setValue: function(value, forceCommit) + { + if (!this.widget) + { + this.setInitialValue(value, forceCommit); + } + else + { + this.parent(value, forceCommit); + var jsDate = dojo.date.fromRfc3339(value); + this.widget.value = dojo.date.format(jsDate, + {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, + selector: "dateOnly"}); + this.widget.removeClass("xformsGhostText"); + } + }, + + getValue: function() + { + if (this.widget.value == null || + this.widget.value.length == 0 || + this.widget.value == this._noValueSet) + { + return null; + } + else + { + var jsDate = dojo.date.parse(this.widget.value, + {datePattern: alfresco.xforms.constants.DATE_TIME_FORMAT, + selector: "dateOnly"}); + return dojo.date.toRfc3339(jsDate); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _dateTimeTextBox_focusHandler: function(event) + { + this._destroyPicker(); + }, + + _dateTimeTextBox_changeHandler: function(event) + { + this._commitValueChange(); + }, + + _timePicker_valueChangedHandler: function(date) + { + var value = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); + value.setHours(date.getHours()); + value.setMinutes(date.getMinutes()); + value = dojo.date.toRfc3339(value); + this.setValue(value); + this._commitValueChange(); + }, + + _datePicker_valueChangedHandler: function(date) + { + var value = this.getValue() ? dojo.date.fromRfc3339(this.getValue()) : new Date(); + value.setYear(date.getYear()); + value.setMonth(date.getMonth()); + value.setDate(date.getDate()); + value = dojo.date.toRfc3339(value); + this.setValue(value); + this._commitValueChange(); + }, + + _expando_clickHandler: function() + { + if (this._pickerDiv) + { + this._destroyPicker(); + } + else + { + this._createPicker(); + } + } +}); + +/** The year picker handles xforms widget xf:input with a gYear type */ +alfresco.xforms.YearPicker = alfresco.xforms.TextField.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + render: function(attach_point) + { + this.parent(attach_point); + this.widget.size = "4"; + this.widget.setAttribute("maxlength", "4"); + }, + + getInitialValue: function() + { + var result = this.parent(); + return result ? result.replace(/^0*([^0]+)$/, "$1") : result; + }, + + setValue: function(value, forceCommit) + { + this.parent((value + ? value.replace(/^0*([^0]+)$/, "$1") + : null), + forceCommit); + }, + + getValue: function() + { + var result = this.parent(); + return result ? dojo.string.padLeft(result, 4, "0") : null; + } +}); + +/** The day picker widget which handles xforms widget xf:input with type xf:gDay */ +alfresco.xforms.DayPicker = alfresco.xforms.ComboboxSelect1.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + _getItemValues: function() + { + var result = []; + result.push({id: "day_empty", label: "", value: "", valid: false}); + for (var i = 1; i <= 31; i++) + { + result.push({ + id: "day_" + i, + label: i, + value: "---" + (i < 10 ? "0" + i : i), + valid: true + }); + } + return result; + } +}); + +/** The month picker widget which handles xforms widget xf:input with type xf:gMonth */ +alfresco.xforms.MonthPicker = alfresco.xforms.ComboboxSelect1.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + _getItemValues: function() + { + var result = []; + result.push({id: "month_empty", label: "", value: "", valid: false}); + for (var i = 0; i < 12; i++) + { + var d = new Date(); + d.setMonth(i); + result.push({ + id: "month_" + i, + label: dojo.date.getMonthName(d), + value: "--" + (i + 1 < 10 ? "0" + (i + 1) : i + 1), + valid: true + }); + } + return result; + } +}); + +/** The month day picker widget which handles xforms widget xf:input with type xf:gMonthDay */ +alfresco.xforms.MonthDayPicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + this.monthPicker = new alfresco.xforms.MonthPicker(xform, xformsNode); + this.monthPicker._compositeParent = this; + + this.dayPicker = new alfresco.xforms.DayPicker(xform, xformsNode); + this.dayPicker._compositeParent = this; + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + render: function(attach_point) + { + this.setValue(this.getInitialValue()); + attach_point.appendChild(this.domNode); + this.dayPicker.render(this.domNode); + this.dayPicker.widget.style.marginRight = "10px"; + this.monthPicker.render(this.domNode); + this.domNode.style.width = this.monthPicker.domNode.offsetWidth + this.dayPicker.domNode.offsetWidth + 10 + "px"; + }, + + setValue: function(value) + { + this.monthPicker.setValue(value ? value.match(/^--[^-]+/)[0] : null); + this.dayPicker.setValue(value ? "---" + value.replace(/^--[^-]+-/, "") : null); + }, + + getValue: function() + { + // format is --MM-DD + var day = this.dayPicker.getValue(); + var month = this.monthPicker.getValue(); + return month && day ? day.replace(/^--/, month) : null; + } +}); + +/** The year month picker widget which handles xforms widget xf:input with type xf:gYearMonth */ +alfresco.xforms.YearMonthPicker = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + this.yearPicker = new alfresco.xforms.YearPicker(xform, xformsNode); + this.yearPicker._compositeParent = this; + + this.monthPicker = new alfresco.xforms.MonthPicker(xform, xformsNode); + this.monthPicker._compositeParent = this; + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + render: function(attach_point) + { + this.setValue(this.getInitialValue()); + attach_point.appendChild(this.domNode); + this.monthPicker.render(this.domNode); + this.monthPicker.widget.style.marginRight = "10px"; + this.yearPicker.domNode.style.display = "inline"; + this.yearPicker.render(this.domNode); + this.domNode.style.width = this.yearPicker.domNode.offsetWidth + this.monthPicker.domNode.offsetWidth + 10 + "px"; + }, + + setValue: function(value) + { + this.monthPicker.setValue(value ? value.replace(/^[^-]+-/, "--") : null); + this.yearPicker.setValue(value ? value.match(/^[^-]+/)[0] : null); + }, + + getValue: function() + { + // format is CCYY-MM + var year = this.yearPicker.getValue(); + var month = this.monthPicker.getValue(); + return year && month ? month.replace(/^-/, year) : null; + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// widgets for group types +//////////////////////////////////////////////////////////////////////////////// + +/** + * Handles xforms widget xf:group. A group renders and manages a set of children + * and provides a header for expanding and collapsing the group. A group header + * is shown for all group that don't have xf:appearance set to 'repeated' and + * that are not the root group. + */ +alfresco.xforms.AbstractGroup = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode, domNode) + { + this.parent(xform, xformsNode, domNode); + this._children = []; + this.domNode.removeClass("xformsItem"); + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + /** Returns the child at the specified index or null if the index is out of range. */ + getChildAt: function(index) + { + return index < this._children.length ? this._children[index] : null; + }, + + /** Returns the index of a particular child or -1 if the child was not found. */ + getChildIndex: function(child) + { + for (var i = 0; i < this._children.length; i++) + { + if (alfresco.constants.DEBUG) + { + alfresco.log(this.id + "[" + i + "]: " + + " is " + this._children[i].id + + " the same as " + child.id + "?"); + } + if (this._children[i] == child) + { + return i; + } + } + return -1; + }, + + /** Adds the child to end of the list of children. */ + addChild: function(child) + { + return this._insertChildAt(child, this._children.length); + }, + + _isIndented: function() + { + return !(this.parentWidget instanceof alfresco.xforms.ViewRoot) && this._children.length > 1; + }, + + /** Inserts a child at the specified position. */ + _insertChildAt: function(child, position, nodeName, attach_point) + { + alfresco.log(this.id + "._insertChildAt(" + child.id + ", " + position + ")"); + child.parentWidget = this; + + child.domContainer = new Element($pick(nodeName, "div")); + child.domContainer.setAttribute("id", child.id + "-domContainer"); + child.domContainer.addClass("xformsItemDOMContainer"); + + if (position == this._children.length) + { + $pick(attach_point, this.domNode.childContainerNode).appendChild(child.domContainer); + this._children.push(child); + } + else + { + $pick(attach_point, this.domNode.childContainerNode).insertBefore(child.domContainer, + this.getChildAt(position).domContainer); + this._children.splice(position, 0, child); + } + return child.domContainer; + }, + + /** Removes the child at the specified position. */ + _removeChildAt: function(position) + { + var child = this.getChildAt(position); + if (!child) + { + throw new Error("unable to find child at " + position); + } + + this._children.splice(position, 1); + child.domContainer.group = this; + var anim = dojo.lfx.html.fadeOut(child.domContainer, 500); + anim.onEnd = function() + { + child.domContainer.style.display = "none"; + child._destroy(); + + child.domContainer.remove(); + + child.domContainer.group._updateDisplay(false); + }; + anim.play(); + + this._childRemoved(child); + + return child; + }, + + /** Event handler for when a child has been added. */ + _childAdded: function(child) { }, + + /** Event handler for when a child has been removed. */ + _childRemoved: function(child) { }, + + /** Utility function to create the a label container */ + _createLabelContainer: function(child, nodeName, attach_point) + { + var labelNode = new Element($pick(nodeName, "div"), + { + id: child.id + "-label", + "class": "xformsItemLabelContainer" + }); + var requiredImage = new Element("img", { "class": "xformsItemRequiredImage" }); + requiredImage.src = alfresco.xforms.AbstractGroup._requiredImage.src; + + labelNode.appendChild(requiredImage); + + if (!child.isRequired()) + { + requiredImage.style.visibility = "hidden"; + } + var label = child.getLabel(); + if (label) + { + child.labelNode = labelNode; + child.labelNode.appendChild(document.createTextNode(label)); + } + var hint = child.getHint(); + if (hint) + { + labelNode.setAttribute("title", hint); + requiredImage.setAttribute("alt", hint); + } + labelNode.style.width = "0px"; + $pick(attach_point, child.domContainer).appendChild(labelNode); + labelNode.style.width = labelNode.scrollWidth + "px"; + return labelNode; + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + isValidForSubmit: function() + { + return true; + }, + + /** Iterates all children a produces an array of widgets which are invalid for submit. */ + getWidgetsInvalidForSubmit: function() + { + var result = []; + for (var i = 0; i < this._children.length; i++) + { + if (this._children[i] instanceof alfresco.xforms.AbstractGroup) + { + result = result.concat(this._children[i].getWidgetsInvalidForSubmit()); + } + else if (!this._children[i].isValidForSubmit()) + { + result.push(this._children[i]); + } + } + return result; + }, + + /** Recusively destroys all children. */ + _destroy: function() + { + this.parent(); + this._children.each(function(c) { c._destroy() }); + }, + + setReadonly: function(readonly) + { + this.parent(readonly); + this._children.each(function(c) { c.setReadonly(readonly); }); + }, + + render: function(attach_point) + { + this.domNode.widget = this; + return this.domNode; + }, + + _updateDisplay: function(recursively) + { + if (recursively) + { + this._children.each(function(c) { c._updateDisplay(recursively); }); + } + }, + + showAlert: function() + { + this._children.each(function(c) { c.showAlert(); }); + }, + + hideAlert: function() + { + this._children.each(function(c) { c.hideAlert(); }); + } +}); + +alfresco.xforms.AbstractGroup._requiredImage = new Image(); +alfresco.xforms.AbstractGroup._requiredImage.src = alfresco.constants.WEBAPP_CONTEXT + "/images/icons/required_field.gif"; + +/** + * Handles xforms widget xf:group. A group renders and manages a set of children + * and provides a header for expanding and collapsing the group. A group header + * is shown for all group that don't have xf:appearance set to 'repeated' and + * that are not the root group. + */ +alfresco.xforms.VGroup = alfresco.xforms.AbstractGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + _groupHeaderNode: null, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + /** Inserts a child at the specified position. */ + _insertChildAt: function(child, position) + { + if (!this.domNode.childContainerNode.parentNode) + { + // only add this to the dom once we're adding a child + this.domNode.appendChild(this.domNode.childContainerNode); + this._contentDivs = {}; + } + + child.domContainer = this.parent(child, position); + + if (this.parentWidget && this.parentWidget.domNode) + { + child.domContainer.style.top = this.parentWidget.domNode.style.bottom; + } + + function shouldInsertDivider(group, child, position) + { + if (group.getAppearance() != "full") + { + return false; + } + if (group instanceof alfresco.xforms.Repeat) + { + return false; + } + + if (!child.isVisible()) + { + return false; + } + if (group._children[position - 1] instanceof alfresco.xforms.AbstractGroup) + { + return true; + } + if (child instanceof alfresco.xforms.AbstractGroup) + { + for (var i = position - 1; i > 0; i--) + { + if (group._children[i].isVisible()) + { + return true; + } + } + } + return false; + } + + if (shouldInsertDivider(this, child, position)) + { + var divider = new Element("div", { "class": "xformsGroupDivider"}); + this.domNode.childContainerNode.insertBefore(divider, + child.domContainer); + } + + var contentDiv = new Element("div", { "id": child.id + "-content", "class": "xformsGroupItem"}); + this._contentDivs[child.id] = contentDiv; + if (!(child instanceof alfresco.xforms.AbstractGroup)) + { + contentDiv.labelNode = this._createLabelContainer(child); + child.domContainer.appendChild(contentDiv.labelNode); + } + + child.domContainer.appendChild(contentDiv); + contentDiv.style.left = (child instanceof alfresco.xforms.AbstractGroup + ? "0px" + : "30%"); + + contentDiv.style.width = (child instanceof alfresco.xforms.AbstractGroup + ? "100%" + : (1 - (contentDiv.offsetLeft / + child.domContainer.offsetWidth)) * 100 + "%"); + child.render(contentDiv); + if (!(child instanceof alfresco.xforms.AbstractGroup)) + { + child.domContainer.style.height = + Math.max(contentDiv.offsetHeight + + contentDiv.getStyle("margin-top").toInt() + + contentDiv.getStyle("margin-bottom").toInt(), + 20) + "px"; + } + + alfresco.log(contentDiv.getAttribute("id") + " offsetTop is " + contentDiv.offsetTop); + contentDiv.style.top = "-" + Math.max(0, contentDiv.offsetTop - contentDiv.getStyle("margin-top").toInt()) + "px"; + if (contentDiv.labelNode) + { +// contentDiv.labelNode.style.top = (contentDiv.offsetTop + ((.5 * contentDiv.offsetHeight) - +// (.5 * contentDiv.labelNode.offsetHeight))) + "px"; + contentDiv.labelNode.style.position = "relative"; + contentDiv.labelNode.style.top = contentDiv.offsetTop + "px"; + contentDiv.labelNode.style.height = contentDiv.offsetHeight + "px"; + contentDiv.labelNode.style.lineHeight = contentDiv.labelNode.style.height; + + } + contentDiv.widget = child; + + this._updateDisplay(false); + this._childAdded(child); + return child.domContainer; + }, + + render: function(attach_point) + { + this.domNode.widget = this; + + if (this.getAppearance() == "full") + { + this.domNode.addClass("xformsGroup"); + this.domNode.style.position = "relative"; + this.domNode.style.marginRight = (this.domNode.getStyle("margin-left").toInt() / 3) + "px"; + if (window.ie) + { + this.domNode.style.width = "100%"; + } + else + { + var x = ((this.domNode.offsetWidth - this.domNode.clientWidth) + + this.domNode.getStyle("margin-left").toFloat() + + this.domNode.getStyle("margin-right").toFloat()); + this.domNode.style.width = (1 - (x / attach_point.offsetWidth)) * 100 + "%"; + } + + this._groupHeaderNode = new Element("div", + { + "id": this.id + "-groupHeaderNode", + "class": "xformsGroupHeader" + }); + this.domNode.appendChild(this._groupHeaderNode); + + this.toggleExpandedImage = new Element("img", + { + "align": "absmiddle", + "styles": { "margin": "0px 5px" }, + "src": alfresco.xforms.constants.EXPANDED_IMAGE.src + }); + this._groupHeaderNode.appendChild(this.toggleExpandedImage); + this.toggleExpandedImage.onclick = this._toggleExpanded_clickHandler.bindAsEventListener(this); + + this._groupHeaderNode.appendChild(document.createTextNode(this.getLabel())); + } + attach_point.appendChild(this.domNode); + this.domNode.childContainerNode = new Element("div", + { + "id": this.id + "-childContainerNode", + "styles": { "position": "relative", "width": "100%" } + }); + return this.domNode; + }, + + /** Indicates if the group is expanded. */ + isExpanded: function() + { + return (this.toggleExpandedImage.getAttribute("src") == + alfresco.xforms.constants.EXPANDED_IMAGE.src); + }, + + /** + * Sets the expanded state of the widget. If collapsed, everything but the header + * will be hidden. + */ + setExpanded: function(expanded) + { + if (expanded != this.isExpanded()) + { + this.toggleExpandedImage.src = + (expanded + ? alfresco.xforms.constants.EXPANDED_IMAGE.src + : alfresco.xforms.constants.COLLAPSED_IMAGE.src); + this.domNode.childContainerNode.style.display = expanded ? "block" : "none"; + } + }, + + _updateDisplay: function(recursively) + { + if (this._isIndented()) + { + this.domNode.style.marginLeft = 10 + "px"; + this.domNode.style.marginRight = 5 + "px"; + // XXXarielb can this be moved to render or insertChild? + this.domNode.style.width = (((this.domNode.offsetWidth - 15) / this.domNode.offsetWidth) * 100) + "%"; + } + if (window.ie) + { + this.domNode.style.width = "100%"; + } + else + { +// var x = ((this.domNode.offsetWidth - this.domNode.clientWidth) + +// this.domNode.getStyle("margin-left").toFloat() + +// this.domNode.getStyle("margin-right").toFloat()); +// this.domNode.style.width = (1 - (x / this.domNode.parentNode.offsetWidth)) * 100 + "%"; + } + + for (var i = 0; i < this._children.length; i++) + { + if (!this._children[i].isVisible()) + { + continue; + } + var contentDiv = this._contentDivs[this._children[i].id]; + + contentDiv.style.position = "static"; + contentDiv.style.top = "0px"; + contentDiv.style.left = "0px"; + + contentDiv.style.position = "relative"; + contentDiv.style.left = (this._children[i] instanceof alfresco.xforms.AbstractGroup + ? "0px" + : "30%"); + contentDiv.style.width = (this._children[i] instanceof alfresco.xforms.AbstractGroup + ? "100%" + : (1 - (contentDiv.offsetLeft / + this._children[i].domContainer.parentNode.offsetWidth)) * 100 + "%"); + + if (recursively) + { + this._children[i]._updateDisplay(recursively); + } + + if (!(this._children[i] instanceof alfresco.xforms.AbstractGroup)) + { + this._children[i].domContainer.style.height = + Math.max(contentDiv.offsetHeight + + contentDiv.getStyle("margin-top").toInt() + + contentDiv.getStyle("margin-bottom").toInt(), + 20) + "px"; + } + + contentDiv.style.top = "-" + Math.max(0, contentDiv.offsetTop - contentDiv.getStyle("margin-top").toInt()) + "px"; + + var labelNode = contentDiv.labelNode; + if (labelNode) + { +// labelNode.style.position = "static"; +// labelNode.style.top = "0px"; +// labelNode.style.left = "0px"; +// labelNode.style.position = "relative"; + +// labelNode.style.top = (contentDiv.offsetTop + ((.5 * contentDiv.offsetHeight) - +// (.5 * labelNode.offsetHeight))) + "px"; + } + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _toggleExpanded_clickHandler: function(event) + { + this.setExpanded(!this.isExpanded()); + } +}); + +/** + * Handles xforms widget xf:group. A group renders and manages a set of children + * and provides a header for expanding and collapsing the group. A group header + * is shown for all group that don't have xf:appearance set to 'repeated' and + * that are not the root group. + */ +alfresco.xforms.HGroup = alfresco.xforms.AbstractGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("table")); + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + /** a map of child ids to contentDivs */ + _contentDivs: {}, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + _isIndented: function() + { + return false; + }, + + /** Inserts a child at the specified position. */ + _insertChildAt: function(child, position) + { + var labelCell = new Element("td"); + this.domNode.childContainerNode.appendChild(labelCell); + var labelNode = this._createLabelContainer(child, "div", labelCell); + labelNode.style.minWidth = "40px"; + labelCell.style.width = labelNode.style.width; + + child.parentWidget = this; + + var contentCell = new Element("td"); + this.domNode.childContainerNode.appendChild(contentCell); + child.domContainer = this.parent(child, position, null, contentCell); + child.domContainer.style.position = "static"; + + var contentDiv = new Element("div", { id: child.id + "-content", "class": "xformsGroupItem" }); + child.domContainer.appendChild(contentDiv); + this._contentDivs[child.id] = contentDiv; + + contentDiv.labelNode = labelNode; + child.render(contentDiv); + + var w = child.domNode.style.width; + if (!w || w[w.length - 1] != "%") + { + contentCell.style.width = child.domNode.offsetWidth + "px"; + } + contentDiv.widget = child; + this._childAdded(child); + return child.domContainer; + }, + + render: function(attach_point) + { + this.domNode.widget = this; + this.domNode.style.width = "100%"; + attach_point.appendChild(this.domNode); + + var tbody = new Element("tbody"); + this.domNode.appendChild(tbody); + this.domNode.childContainerNode = new Element("tr"); + tbody.appendChild(this.domNode.childContainerNode); + return this.domNode; + }, + + _updateDisplay: function(recursively) + { + this._children.each(function(child, index) + { + if (recursively) + { + child._updateDisplay(recursively); + } + }.bind(this)); + } +}); + +alfresco.xforms.SwitchGroup = alfresco.xforms.VGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + if (this.getInitialValue()) + { + var initialValueTrigger = this._getCaseToggleTriggerByTypeValue(this.getInitialValue()); + this._selectedCaseId = initialValueTrigger.getActions()["toggle"].properties["case"]; + } + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + _getCaseToggleTriggers: function() + { + var bw = this.xform.getBinding(this.xformsNode).widgets; + var result = []; + for (var i in bw) + { + if (! (bw[i] instanceof alfresco.xforms.Trigger)) + { + continue; + } + + var action = bw[i].getActions()["toggle"]; + if (action) + { + result.push(bw[i]); + } + } + return result; + }, + + _getCaseToggleTriggerByCaseId: function(caseId) + { + var bw = this.xform.getBinding(this.xformsNode).widgets; + for (var i in bw) + { + if (! (bw[i] instanceof alfresco.xforms.Trigger)) + { + continue; + } + + var action = bw[i].getActions()["toggle"]; + if (!action) + { + continue; + } + if (action.properties["case"] == caseId) + { + return bw[i]; + } + } + throw new Error("unable to find trigger " + type + + ", properties " + properties + + " for " + this.id); + + }, + + _getCaseToggleTriggerByTypeValue: function(typeValue) + { + var bw = this.xform.getBinding(this.xformsNode).widgets; + for (var i in bw) + { + if (! (bw[i] instanceof alfresco.xforms.Trigger)) + { + continue; + } + + var action = bw[i].getActions()["setvalue"]; + if (!action) + { + continue; + } + if (action.properties["value"] == typeValue) + { + return bw[i]; + } + } + throw new Error("unable to find toggle trigger for type value " + typeValue + + " for " + this.id); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + /** */ + _insertChildAt: function(child, position) + { + var childDomContainer = this.parent(child, position); + if (child.id == this._selectedCaseId) + { + this._getCaseToggleTriggerByCaseId(this._selectedCaseId).fire(); + } + else + { + childDomContainer.style.display = "none"; + } + return childDomContainer; + }, + + render: function(attach_point) + { + this.parent(attach_point); + var cases = this._getCaseToggleTriggers(); + var caseToggleSelect = new Element("select", + { + "id": this.id + "-toggle-select", + "styles": { "position": "absolute", "right": "0px", "top": "0px" } + }); + this._groupHeaderNode.appendChild(caseToggleSelect); + for (var i = 0; i < cases.length; i++) + { + var option = document.createElement("option"); + caseToggleSelect.appendChild(option); + var caseId = cases[i].getActions()["toggle"].properties["case"]; + option.setAttribute("value", caseId); + option.appendChild(document.createTextNode(cases[i].getLabel())); + if (cases[i].getActions()["toggle"].properties["case"] == this._selectedCaseId) + { + option.selected = true; + } + } + caseToggleSelect.onchange = this._caseToggleSelect_changeHandler.bindAsEventListener(this); + }, + + ///////////////////////////////////////////////////////////////// + // XForms event handlers + ///////////////////////////////////////////////////////////////// + + /** */ + handleSwitchToggled: function(selectedCaseId, deselectedCaseId) + { + alfresco.log(this.id + ".handleSwitchToggled(" + selectedCaseId + + ", " + deselectedCaseId + ")"); + this.selectedCaseId = selectedCaseId; + for (var i = 0; i < this._children.length; i++) + { + if (this._children[i].id == selectedCaseId) + { + this._children[i].domContainer.style.display = "block"; + } + else if (this._children[i].id == deselectedCaseId) + { + this._children[i].domContainer.style.display = "none"; + } + } + this._updateDisplay(true); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _caseToggleSelect_changeHandler: function(event) + { + event.stopPropagation(); + var t = this._getCaseToggleTriggerByCaseId(event.target.value); + t.fire(); + } +}); + +/** */ +alfresco.xforms.CaseGroup = alfresco.xforms.VGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + } +}); + +/** + * Handles xforms widget xf:group for the root group. Does some special rendering + * to present a title rather than a group header. + */ +alfresco.xforms.ViewRoot = alfresco.xforms.VGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + this.focusedRepeat = null; + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + _isIndented: function() + { + return false; + }, + + render: function(attach_point) + { + this.domNode.widget = this; + this.domNode.style.position = "relative"; + this.domNode.style.width = "100%"; + this.domNode.addClass("xformsViewRoot"); + + this._groupHeaderNode = new Element("div", + { + "id": this.id + "-groupHeaderNode", + "class": "xformsViewRootHeader" + }); + this.domNode.appendChild(this._groupHeaderNode); + + var icon = document.createElement("img"); + this._groupHeaderNode.appendChild(icon); + icon.setAttribute("src", alfresco.constants.WEBAPP_CONTEXT + "/images/icons/file_large.gif"); + icon.align = "absmiddle"; + icon.style.margin = "0px 5px"; + this._groupHeaderNode.appendChild(document.createTextNode(this.getLabel())); + attach_point.appendChild(this.domNode); + + this.domNode.childContainerNode = new Element("div", + { + "id": this.id + "-childContainerNode", + "styles": {"position": "relative", "width": "100%"} + }); + return this.domNode; + }, + + /** */ + getLabel: function() + { + return this.parent() + " " + alfresco.xforms.constants.FORM_INSTANCE_DATA_NAME; + } +}); + +/** A struct for providing repeat index data. */ +alfresco.xforms.RepeatIndexData = function(repeat, index) +{ + this.repeat = repeat; + this.index = index; + this.toString = function() + { + return "{" + this.repeat.id + " = " + this.index + "}"; + }; +} + +/** + * Handles xforms widget xf:repeat. + */ +alfresco.xforms.Repeat = alfresco.xforms.VGroup.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + this._repeatControls = []; + this._selectedIndex = -1; + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + /** + * Indicates whether or not this repeat can insert more children based + * on the alf:maximum restriction. + */ + isInsertRepeatItemEnabled: function() + { + var maximum = this.xform.getBinding(this.xformsNode).maximum; + maximum = isNaN(maximum) ? Number.MAX_VALUE : maximum; + return this._children.length < maximum; + }, + + /** + * Indicates whether or not this repeat can removed children based + * on the alf:minimum restriction. + */ + isRemoveRepeatItemEnabled: function() + { + var minimum = this.xform.getBinding(this.xformsNode).minimum; + minimum = isNaN(minimum) ? this.isRequired() ? 1 : 0 : minimum; + return this._children.length > minimum; + }, + + /** + * Returns the currently selected index or -1 if this repeat has no repeat items. + */ + getSelectedIndex: function() + { + this._selectedIndex = Math.min(this._children.length, this._selectedIndex); + if (this._children.length == 0) + { + this._selectedIndex = -1; + } + return this._selectedIndex; + }, + + /** + * Helper function to locate the appropriate repeat item trigger for this repeat. + * This is done by locating all related widgets via binding, and selecting the + * Trigger who's action type is the type provided and where the properties + * provided are the same for that action. This approach is used rather than simply + * looking up the trigger by id since the id isn't known for nested repeats as + * chiba modifies them. + */ + _getRepeatItemTrigger: function(type, properties) + { + var bw = this.xform.getBinding(this.xformsNode).widgets; + for (var i in bw) + { + if (! (bw[i] instanceof alfresco.xforms.Trigger)) + { + continue; + } + + var action = bw[i].getActions()[type]; + if (!action) + { + continue; + } + + var propertiesEqual = true; + for (var p in properties) + { + if (!(p in action.properties) || + action.properties[p] != properties[p]) + { + propertiesEqual = false; + break; + } + } + if (propertiesEqual) + { + return bw[i]; + } + } + throw new Error("unable to find trigger " + type + + ", properties " + properties + + " for " + this.id); + + }, + + /** + * Sets the currently selected child by calliing XFormsBean.setRepeatIndeces. + * If the child provided is null, the index is set to 0. + */ + setFocusedChild: function(child) + { + var oldFocusedRepeat = this.getViewRoot().focusedRepeat; + this.getViewRoot().focusedRepeat = this; + if (oldFocusedRepeat != null && oldFocusedRepeat != this) + { + if (!oldFocusedRepeat.isAncestorOf(this)) + { + oldFocusedRepeat._selectedIndex = -1; + } + oldFocusedRepeat._updateDisplay(false); + } + + var repeatIndices = this.getRepeatIndices(); + if (!child) + { + repeatIndices.push(new alfresco.xforms.RepeatIndexData(this, 0)); + this.xform.setRepeatIndeces(repeatIndices); + } + else + { + var index = this.getChildIndex(child); + if (index < 0) + { + throw new Error("unable to find child " + child.id + " in " + this.id); + } + + repeatIndices.push(new alfresco.xforms.RepeatIndexData(this, index + 1)); + // xforms repeat indexes are 1-based + this.xform.setRepeatIndeces(repeatIndices); + } + }, + + /** + * Calls swapRepeatItems on the XFormsBean which will produce the event log + * to insert and remove the appropriate repeat items. + */ + _swapChildren: function(fromIndex, toIndex) + { + alfresco.log(this.id + ".swapChildren(" + fromIndex + ", " + toIndex + ")"); + var fromChild = this.getChildAt(fromIndex); + var toChild = this.getChildAt(toIndex); + this.xform.swapRepeatItems(fromChild, toChild); + var anim = dojo.lfx.html.fadeOut(fromChild.domContainer, 500); + anim.onEnd = function() + { + fromChild.domContainer.style.display = "none"; + }; + anim.play(); + }, + + /** + * Updates the repeat controls by changing the opacity on the image based on + * whether or not the action is enabled. + */ + _updateRepeatControls: function() + { + var insertEnabled = this.isInsertRepeatItemEnabled(); + var removeEnabled = this.isRemoveRepeatItemEnabled(); + for (var i = 0; i < this._repeatControls.length; i++) + { + this._repeatControls[i].moveRepeatItemUpImage.setOpacity(i == 0 ? .3 : 1); + this._repeatControls[i].moveRepeatItemDownImage.setOpacity(i == this._repeatControls.length - 1 ? .3 : 1); + this._repeatControls[i].insertRepeatItemImage.setOpacity(insertEnabled ? 1 : .3); + this._repeatControls[i].removeRepeatItemImage.setOpacity(removeEnabled ? 1 : .3); + } + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods & properties + ///////////////////////////////////////////////////////////////// + + /** When debugging, insert the id into the label. */ + getLabel: function() + { + var label = this.parentWidget.getLabel(); + if (alfresco.constants.DEBUG) + { + label += "[" + this.id + "]"; + } + return label; + }, + + /** Overrides _insertChildAt in Group to provide repeater controls. */ + _insertChildAt: function(child, position) + { + this._repeatControls.splice(position, 0, new Element("div")); + var images = + [ + { name: "insertRepeatItemImage", src: "plus", action: this._insertRepeatItemAfter_handler }, + { name: "moveRepeatItemUpImage", src: "arrow_up", action: this._moveRepeatItemUp_handler }, + { name: "moveRepeatItemDownImage", src: "arrow_down", action: this._moveRepeatItemDown_handler }, + { name: "removeRepeatItemImage", src: "minus", action: this._removeRepeatItem_handler } + ]; + var _repeatControlsWidth = 0; + for (var i = 0; i < images.length; i++) + { + var img = new Element("img", + { + "src": (alfresco.constants.WEBAPP_CONTEXT + "/images/icons/" + + images[i].src + ".gif"), + "styles": { "width" : "16px", "height" : "16px" } + }); + this._repeatControls[position][images[i].name] = img; + var imgMargin = [2, 5, 2, (i == 0 ? 5 : 0) ]; + img.style.margin = imgMargin.join("px ") + "px"; + _repeatControlsWidth += (parseInt(img.style.width) + imgMargin[1] + imgMargin[3]); + this._repeatControls[position].appendChild(img); + img.onclick = images[i].action.bindAsEventListener(this); + } + + var result = this.parent(child, position); + child.repeat = this; + result.onclick = function(event) + { + event = new Event(event); + child.repeat.setFocusedChild(child); + event.stopPropagation(); + }; + result.addClass("xformsRepeatItem"); + if (result.nextSibling) + { + result.parentNode.insertBefore(this._repeatControls[position], + result.nextSibling); + } + else + { + result.parentNode.appendChild(this._repeatControls[position]); + } + + this._repeatControls[position].addClass("xformsRepeatControls"); + this._repeatControls[position].style.width = _repeatControlsWidth + "px"; + this._repeatControls[position].style.backgroundColor = result.getStyle("background-color"); + this._repeatControls[position].style.overflow = "hidden"; + + result.style.paddingBottom = (.5 * this._repeatControls[position].offsetHeight) + "px"; + + this._repeatControls[position].style.top = -((.5 * this._repeatControls[position].offsetHeight) + + result.getStyle("margin-bottom").toInt() + + result.getStyle("border-bottom").toInt()) + "px"; + // may need to use this for centering repeat controls in quirks mode on IE + // this._repeatControls[position].style.margin = "0px " + Math.floor(100 * ((result.offsetWidth - + // this._repeatControls[position].offsetWidth) / + // (result.offsetWidth * 2)))+ "%"; + return result; + }, + + /** + * Overrides _removeChildAt in Group to remove the repeat controls associated with + * the repeat item. + */ + _removeChildAt: function(position) + { + this._repeatControls[position].style.display = "none"; + this._repeatControls[position].remove(); + this._repeatControls.splice(position, 1); + return this.parent(position); + }, + + /** Disables insert before. */ + _childAdded: function(child) + { + this.headerInsertRepeatItemImage.setOpacity(.3); + this._updateRepeatControls(); + }, + + /** Reenables insert before if there are no children left. */ + _childRemoved: function(child) + { + if (this._children.length == 0) + { + this.headerInsertRepeatItemImage.setOpacity(1); + } + this._updateRepeatControls(); + }, + + _isIndented: function() + { + return false; + }, + + render: function(attach_point) + { + this.domNode = this.parent(attach_point); + this.domNode.addClass("xformsRepeat"); + + // clear the border bottom for the group header since we'll be getting it + // from the repeat item border + this._groupHeaderNode.style.borderBottomWidth = "0px"; + + this._groupHeaderNode.repeat = this; + this._groupHeaderNode.onclick = function(event) + { + if (event.target == event.currentTarget) + { + event.currentTarget.repeat.setFocusedChild(null); + } + }; + + this.headerInsertRepeatItemImage = + new Element("img", + { + "align": "absmiddle", + "src": alfresco.constants.WEBAPP_CONTEXT + "/images/icons/plus.gif", + "styles": { "margin-left": "5px", "width": "16px", "height": "16px" } + }); + + this.headerInsertRepeatItemImage.repeat = this; + this._groupHeaderNode.appendChild(this.headerInsertRepeatItemImage); + + this.headerInsertRepeatItemImage.onclick = + this._headerInsertRepeatItemBefore_handler.bindAsEventListener(this); + + return this.domNode; + }, + + _updateDisplay: function(recursively) + { + this.parent(recursively); + if (this.getViewRoot().focusedRepeat != null && + (this.getViewRoot().focusedRepeat == this || + this.getViewRoot().focusedRepeat.isAncestorOf(this))) + { + this._groupHeaderNode.addClass("xformsRepeatFocusedHeader"); + } + else + { + this._groupHeaderNode.removeClass("xformsRepeatFocusedHeader"); + } + + for (var i = 0; i < this._children.length; i++) + { + var domContainerClasses = this._children[i].domContainer.getProperty("class").split(" "); + if (i + 1 == this.getSelectedIndex() && this.getViewRoot().focusedRepeat == this) + { + domContainerClasses.remove("xformsRowOdd"); + domContainerClasses.remove("xformsRowEven"); + domContainerClasses.include("xformsRepeatItemSelected"); + } + else + { + domContainerClasses.remove("xformsRepeatItemSelected"); + domContainerClasses.remove("xformsRow" + (i % 2 ? "Odd" : "Even")); + domContainerClasses.include("xformsRow" + (i % 2 ? "Even" : "Odd")); + } + this._children[i].domContainer.setProperty("class", domContainerClasses.join(" ")); + + this._repeatControls[i].style.backgroundColor = + this._children[i].domContainer.getStyle("background-color"); + } + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + /** + * Event handler for insert after. If insert is enabled, causes a setRepeatIndeces + * and an insert. + */ + _insertRepeatItemAfter_handler: function(event) + { + event = new Event(event); + event.stopPropagation(); + if (this.isInsertRepeatItemEnabled()) + { + var index = this._repeatControls.indexOf(event.target.parentNode); + var repeatItem = this.getChildAt(index); + this.setFocusedChild(repeatItem); + var trigger = this._getRepeatItemTrigger("insert", { position: "after" }); + trigger.fire(); + } + }, + + /** + * Event handler for insert before. If insert is enabled, causes a setRepeatIndeces + * and an insert. + */ + _headerInsertRepeatItemBefore_handler: function(event) + { + event = new Event(event); + if (this._children.length == 0) + { + event.stopPropagation(); + if (this.isInsertRepeatItemEnabled()) + { + this.setFocusedChild(null); + var trigger = this._getRepeatItemTrigger("insert", { position: "before" }); + trigger.fire(); + } + } + }, + + /** + * Event handler for remove. If remove is enabled, causes a setRepeatIndeces + * and an delete. + */ + _removeRepeatItem_handler: function(event) + { + event = new Event(event); + event.stopPropagation(); + if (this.isRemoveRepeatItemEnabled()) + { + var index = this._repeatControls.indexOf(event.target.parentNode); + var repeatItem = this.getChildAt(index); + this.setFocusedChild(repeatItem); + var trigger = this._getRepeatItemTrigger("delete", {}); + trigger.fire(); + } + }, + + /** + * Event handler for move up. Calls swap children with the child before + * if the current select child is not the first child. + */ + _moveRepeatItemUp_handler: function(event) + { + event = new Event(event); + event.stopPropagation(); + var index = this._repeatControls.indexOf(event.target.parentNode); + if (index != 0 && this._children.length != 1) + { + var repeatItem = this.getChildAt(index); + this.setFocusedChild(repeatItem); + this._swapChildren(index, index - 1); + } + }, + + /** + * Event handler for move down. Calls swap children with the child after + * if the current select child is not the last child. + */ + _moveRepeatItemDown_handler: function(event) + { + event = new Event(event); + event.stopPropagation(); + var index = this._repeatControls.indexOf(event.target.parentNode); + if (index != this._children.length - 1 && this._children.length != 1) + { + var repeatItem = this.getChildAt(index); + this.setFocusedChild(repeatItem); + this._swapChildren(index, index + 1); + } + }, + + ///////////////////////////////////////////////////////////////// + // XForms event handlers + ///////////////////////////////////////////////////////////////// + + /** Sets the selected index. */ + handleIndexChanged: function(index) + { + alfresco.log(this.id + ".handleIndexChanged(" + index + ")"); + this._selectedIndex = index; + this._updateDisplay(false); + }, + + /** Returns a clone of the specified prototype id. */ + handlePrototypeCloned: function(prototypeId) + { + alfresco.log(this.id + ".handlePrototypeCloned("+ prototypeId +")"); + var chibaData = _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.CHIBA_NS, + alfresco.xforms.constants.CHIBA_PREFIX, + "data"); + chibaData = chibaData[chibaData.length - 1]; + var prototypeToClone = dojo.dom.firstElement(chibaData); + if (prototypeToClone.getAttribute("id") != prototypeId) + { + throw new Error("unable to locate " + prototypeId + + " in " + this.id); + } + return prototypeToClone.cloneNode(true); + }, + + /** Inserts the clonedPrototype at the specified position. */ + handleItemInserted: function(clonedPrototype, position) + { + alfresco.log(this.id + ".handleItemInserted(" + clonedPrototype.nodeName + + ", " + position + ")"); + var w = this.xform.createWidget(clonedPrototype); + this._insertChildAt(w, position); + this.xform.loadWidgets(w.xformsNode, w); + }, + + /** Deletes the item at the specified position. */ + handleItemDeleted: function(position) + { + alfresco.log(this.id + ".handleItemDeleted(" + position + ")"); + this._removeChildAt(position); + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// trigger widgets +//////////////////////////////////////////////////////////////////////////////// + +/** + * Handles xforms widget xf:trigger. + */ +alfresco.xforms.Trigger = alfresco.xforms.Widget.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode, new Element("input", { type: "submit" })); + }, + + ///////////////////////////////////////////////////////////////// + // methods & properties + ///////////////////////////////////////////////////////////////// + + /** TODO: DOCUMENT */ + getActions: function() + { + if (typeof this._actions == "undefined") + { + var actionNode = _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "action")[0]; + this._actions = {}; + for (var i = 0; i < actionNode.childNodes.length; i++) + { + if (actionNode.childNodes[i].nodeType != document.ELEMENT_NODE) + { + continue; + } + + var a = new alfresco.xforms.XFormsAction(this.xform, actionNode.childNodes[i]); + this._actions[a.getType()] = a; + } + } + return this._actions; + }, + + /** fires the xforms action associated with the trigger */ + fire: function(asynchronous) + { + this.xform.fireAction(this.id, asynchronous); + }, + + ///////////////////////////////////////////////////////////////// + // overridden methods + ///////////////////////////////////////////////////////////////// + + isValidForSubmit: function() + { + return true; + }, + + isVisible: function() + { + return false; + }, + + render: function(attach_point) + { + attach_point.appendChild(this.domNode); + this.widget = this.domNode; + this.widget.value = this.getLabel() + " " + this.id; + this.widget.onclick = this._clickHandler.bindAsEventListener(this); + this.domContainer.style.display = "none"; + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + _clickHandler: function(event) + { + this.fire(); + } +}); + +/** + * Handles xforms widget xf:submit. + */ +alfresco.xforms.Submit = alfresco.xforms.Trigger.extend({ + initialize: function(xform, xformsNode) + { + this.parent(xform, xformsNode); + var submit_buttons = (this.id == "submit" + ? _xforms_getSubmitButtons() + : (this.id == "save-draft" + ? _xforms_getSaveDraftButtons() + : null)); + if (submit_buttons == null) + { + throw new Error("unknown submit button " + this.id); + } + submit_buttons.each(function(b) + { + alfresco.log("adding submit handler for " + b.getAttribute('id')); + $(b).onclick = this._submitButton_clickHandler.bindAsEventListener(this); + }.bind(this)); + }, + + ///////////////////////////////////////////////////////////////// + // DOM event handlers + ///////////////////////////////////////////////////////////////// + + _clickHandler: function(event) + { + this.done = false; + _hide_errors(); + this.fire(); + }, + + /** */ + _submitButton_clickHandler: function(event) + { + event = new Event(event); + var result; + if (this.xform.submitWidget && this.xform.submitWidget.done) + { + alfresco.log("done - doing base click on " + this.xform.submitWidget.currentButton.id); + this.xform.submitWidget.currentButton = null; + this.xform.submitWidget = null; + result = true; + } + else + { + alfresco.log("triggering submit from handler " + event.target.id); + event.stopPropagation(); + _hide_errors(); + this.xform.submitWidget = this; + this.xform.submitWidget.currentButton = event.target; + this.xform.submitWidget.fire(true); + result = false; + } + alfresco.log("submit click handler exit " + event.target.id + " with result " + result); + return result; + } +}); + +/** + * A struct describing an xforms action block. + */ +alfresco.xforms.XFormsAction = new Class({ + initialize: function(xform, xformsNode) + { + this.xform = xform; + this.xformsNode = xformsNode; + /** All properties of the action as map of key value pairs */ + this.properties = []; + for (var i = 0; i < this.xformsNode.attributes.length; i++) + { + var attr = this.xformsNode.attributes[i]; + if (attr.nodeName.match(new RegExp("^" + alfresco.xforms.constants.XFORMS_PREFIX + ":"))) + { + this.properties[attr.nodeName.substring((alfresco.xforms.constants.XFORMS_PREFIX + ":").length)] = + attr.nodeValue; + } + } + if (this.getType() == "setvalue" && !this.properties["value"]) + { + this.properties["value"] = this.xformsNode.firstChild.nodeValue; + } + }, + + /** Returns the action type. */ + getType: function() + { + return this.xformsNode.nodeName.substring((alfresco.xforms.constants.XFORMS_PREFIX + ":").length); + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// xforms data model +//////////////////////////////////////////////////////////////////////////////// + +/** + * An xforms event. A log of events is returned by any xforms action and + * is used to update the UI appropriately. + */ +alfresco.xforms.XFormsEvent = new Class({ + initialize: function(node) + { + this.type = node.nodeName; + this.targetId = node.getAttribute("targetId"); + this.targetName = node.getAttribute("targetName"); + this.properties = {}; + for (var i = 0; i < node.childNodes.length; i++) + { + if (node.childNodes[i].nodeType == document.ELEMENT_NODE) + { + this.properties[node.childNodes[i].getAttribute("name")] = + node.childNodes[i].getAttribute("value"); + } + } + }, + + /** Returns the widget managing the specified target id. */ + getTarget: function() + { + var targetDomNode = document.getElementById(this.targetId + "-content"); + if (!targetDomNode) + { + throw new Error("unable to find node " + this.targetId + "-content"); + } + return targetDomNode.widget; + } +}); + +/** + * A parsed xf:bind. + */ +alfresco.xforms.Binding = new Class({ + initialize: function(xformsNode, parentBinding) + { + this.xformsNode = xformsNode; + this.id = this.xformsNode.getAttribute("id"); + this.nodeset = this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":nodeset"); + this._readonly = + (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":readonly") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":readonly") == "true()" + : null); + this._required = + (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":required") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":required") == "true()" + : null); + this._type = + (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":type") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":type") + : null); + this._builtInType = + (_hasAttribute(this.xformsNode, alfresco.xforms.constants.ALFRESCO_PREFIX + ":builtInType") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":builtInType") + : null); + this.constraint = + (_hasAttribute(this.xformsNode, alfresco.xforms.constants.XFORMS_PREFIX + ":constraint") + ? this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":constraint") + : null); + this.maximum = this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":maxOccurs"); + this.maximum = this.maximum == "unbounded" ? Number.MAX_VALUE : parseInt(this.maximum); + this.minimum = parseInt(this.xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":minOccurs")); + this.parentBinding = parentBinding; + this.widgets = {}; + }, + + /** Returns the expected schema type for this binding. */ + getType: function() + { + return (this._type != null + ? this._type + : (this.parentBinding != null ? this.parentBinding.getType() : null)); + }, + + /** Returns the expected built in schema type for this binding. */ + getBuiltInType: function() + { + return (this._builtInType != null + ? this._builtInType + : (this.parentBinding != null ? this.parentBinding.getBuiltInType() : null)); + }, + + /** Returns true if a node bound by this binding has a readonly value */ + isReadonly: function() + { + return (this._readonly != null ? this._readonly : + (this.parentBinding != null ? this.parentBinding.isReadonly() : false)); + }, + + /** Returns true if a node bound by this binding has a required value */ + isRequired: function() + { + return (this._required != null ? this._required : + (this.parentBinding != null ? this.parentBinding.isRequired() : false)); + }, + + toString: function() + { + return ("{id: " + this.id + + ",type: " + this.getType() + + ",builtInType: " + this.getBuiltInType() + + ",required: " + this.isRequired() + + ",readonly: " + this.isReadonly() + + ",nodeset: " + this.nodeset + "}"); + } +}); + +/** + * Manages the xforms document. + */ +alfresco.xforms.XForm = new Class({ + + /** Makes a request to the XFormsBean to load the xforms document. */ + initialize: function() + { + alfresco.AjaxHelper.sendRequest("XFormsBean.getXForm", + null, + true, + this._loadHandler.bindAsEventListener(this)); + }, + + ///////////////////////////////////////////////////////////////// + // Initialization + ///////////////////////////////////////////////////////////////// + + /** Parses the xforms document and produces the widget tree. */ + _loadHandler: function(xformDocument) + { + this.xformDocument = xformDocument; + this.xformsNode = xformDocument.documentElement; + this._bindings = this._loadBindings(this.getModel()); + + var bindings = this.getBindings(); + var alfUI = document.getElementById(alfresco.xforms.constants.XFORMS_UI_DIV_ID); + alfUI.style.width = "100%"; + var rootGroup = _getElementsByTagNameNS(this.getBody(), + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "group")[0]; + + this.rootWidget = new alfresco.xforms.ViewRoot(this, rootGroup); + this.rootWidget.render(alfUI); + + this.loadWidgets(rootGroup, this.rootWidget); + }, + + /** Creates the widget for the provided xforms node. */ + createWidget: function(xformsNode) + { + var appearance = (xformsNode.getAttribute("appearance") || + xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance")); + appearance = appearance == null || appearance.length == 0 ? null : appearance; + + var xformsType = xformsNode.nodeName.toLowerCase(); + var binding = this.getBinding(xformsNode); + var schemaType = binding ? binding.getType() : null; + var builtInSchemaType = binding ? binding.getBuiltInType() : null; + + alfresco.log("creating widget for xforms type " + xformsType + + " schema type " + schemaType + + " built in schema type " + builtInSchemaType + + " with appearance " + appearance); + var x = alfresco.xforms.widgetConfig[xformsType]; + if (!x) + { + throw new Error("unknown type " + xformsNode.nodeName); + } + x = schemaType in x ? x[schemaType] : builtInSchemaType in x ? x[builtInSchemaType] : x["*"]; + x = appearance in x ? x[appearance] : x["*"]; + // alfresco.log(xformsType + ":" + schemaType + ":" + appearance + " =>" + x); + if (x === undefined) + { + throw new Error("unable to find widget for xforms type " + xformsType + + " schemaType " + schemaType + + " appearance " + appearance); + } + if (x == null || typeof x.className == "undefined") + { + return null; + } + var cstr = eval(x.className); + if (!cstr) + { + throw new Error("unable to load constructor " + x.className + + " for xforms type " + xformsType + + " schemaType " + schemaType + + " appearance " + appearance); + } + var result = new cstr(this, xformsNode, $merge({}, x.params)); + if (result instanceof alfresco.xforms.Widget) + { + return result; + } + else + { + throw new Error("constructor for widget " + x + + " for xforms type " + xformsType + + " schemaType " + schemaType + + " appearance " + appearance + + " is not an alfresco.xforms.Widget"); + } + }, + + /** Loads all widgets for the provided xforms node's children. */ + loadWidgets: function(xformsNode, parentWidget) + { + for (var i = 0; i < xformsNode.childNodes.length; i++) + { + if (xformsNode.childNodes[i].nodeType != document.ELEMENT_NODE) + { + continue; + } + alfresco.log("loading " + xformsNode.childNodes[i].nodeName + + " into " + parentWidget.id); + if (xformsNode.childNodes[i].getAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + + ":prototype") == "true") + { + alfresco.log(xformsNode.childNodes[i].getAttribute("id") + + " is a prototype, ignoring"); + continue; + } + var w = this.createWidget(xformsNode.childNodes[i]); + if (w != null) + { + alfresco.log("created " + w.id + " for " + xformsNode.childNodes[i].nodeName); + parentWidget.addChild(w); + if (w instanceof alfresco.xforms.AbstractGroup) + { + this.loadWidgets(xformsNode.childNodes[i], w); + } + } + } + }, + + /** Loads all bindings from the xforms document. */ + _loadBindings: function(bind, parentBinding, result) + { + result = result || []; + for (var i = 0; i < bind.childNodes.length; i++) + { + if (bind.childNodes[i].nodeName.toLowerCase() == + alfresco.xforms.constants.XFORMS_PREFIX + ":bind") + { + var b = new alfresco.xforms.Binding(bind.childNodes[i], parentBinding); + result[b.id] = b; + alfresco.log("loaded binding " + b); + this._loadBindings(bind.childNodes[i], result[b.id], result); + } + } + return result; + }, + + ///////////////////////////////////////////////////////////////// + // XForms model properties & methods + ///////////////////////////////////////////////////////////////// + + /** Returns the model section of the xforms document. */ + getModel: function() + { + return _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "model")[0]; + }, + + /** Returns the instance section of the xforms document. */ + getInstance: function() + { + return _getElementsByTagNameNS(this.getModel(), + alfresco.xforms.constants.XFORMS_NS, + alfresco.xforms.constants.XFORMS_PREFIX, + "instance")[0]; + }, + + /** Returns the body section of the xforms document. */ + getBody: function() + { + var b = _getElementsByTagNameNS(this.xformsNode, + alfresco.xforms.constants.XHTML_NS, + alfresco.xforms.constants.XHTML_PREFIX, + "body"); + return b[b.length - 1]; + }, + + /** Returns the binding corresponding to the provided xforms node. */ + getBinding: function(xformsNode) + { + return this._bindings[xformsNode.getAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":bind")]; + }, + + /** Returns all parsed bindings. */ + getBindings: function() + { + return this._bindings; + }, + + ///////////////////////////////////////////////////////////////// + // XFormsBean interaction + ///////////////////////////////////////////////////////////////// + + /** swaps the specified repeat items by calling XFormsBean.swapRepeatItems. */ + swapRepeatItems: function(fromChild, toChild) + { + var params = + { + fromItemId: fromChild.xformsNode.getAttribute("id"), + toItemId: toChild.xformsNode.getAttribute("id"), + instanceId: this.getInstance().getAttribute("id") + }; + + alfresco.AjaxHelper.sendRequest("XFormsBean.swapRepeatItems", + params, + false, + this._handleEventLog.bindAsEventListener(this)); + }, + + /** sets the repeat indexes by calling XFormsBean.setRepeatIndeces. */ + setRepeatIndeces: function(repeatIndeces) + { + alfresco.log("setting repeat indeces [" + repeatIndeces.join(", ") + "]"); + var params = { }; + params["repeatIds"] = []; + for (var i = 0; i < repeatIndeces.length; i++) + { + params.repeatIds.push(repeatIndeces[i].repeat.id); + params[repeatIndeces[i].repeat.id] = repeatIndeces[i].index; + } + params.repeatIds = params.repeatIds.join(","); + alfresco.AjaxHelper.sendRequest("XFormsBean.setRepeatIndeces", + params, + false, + this._handleEventLog.bindAsEventListener(this)); + }, + + /** Fires an action specified by the id by calling XFormsBean.fireAction. */ + fireAction: function(id, asynchronous) + { + alfresco.log("fireAction(" + id + ")"); + alfresco.AjaxHelper.sendRequest("XFormsBean.fireAction", + { id: id }, + $pick(asynchronous, false), + this._handleEventLog.bindAsEventListener(this)); + }, + + /** Sets the value of the specified control id by calling XFormsBean.setXFormsValue. */ + setXFormsValue: function(id, value) + { + value = value == null ? "" : value.toString(); + alfresco.log("setting value " + id + " = " + value); + alfresco.AjaxHelper.sendRequest("XFormsBean.setXFormsValue", + { id: id, value: value }, + true, + this._handleEventLog.bindAsEventListener(this)); + }, + + /** Handles the xforms event log resulting from a call to the XFormsBean. */ + _handleEventLog: function(events) + { + events = events.documentElement; + var prototypeClones = []; + var generatedIds = null; + for (var i = 0; i < events.childNodes.length; i++) + { + if (events.childNodes[i].nodeType != document.ELEMENT_NODE) + { + continue; + } + var xfe = new alfresco.xforms.XFormsEvent(events.childNodes[i]); + alfresco.log("parsing " + xfe.type + + "(" + xfe.targetId + ", " + xfe.targetName + ")"); + switch (xfe.type) + { + case "chiba-index-changed": + { + var index = Number(xfe.properties["index"]); + try + { + xfe.getTarget().handleIndexChanged(index); + } + catch (e) + { + alfresco.log(e); + } + break; + } + case "chiba-state-changed": + { + alfresco.log("handleStateChanged(" + xfe.targetId + ")"); + xfe.getTarget().setModified(true); + if ("valid" in xfe.properties) + { + xfe.getTarget().setValid(xfe.properties["valid"] == "true"); + } + if ("required" in xfe.properties) + { + xfe.getTarget().setRequired(xfe.properties["required"] == "true"); + } + if ("readonly" in xfe.properties) + { + xfe.getTarget().setReadonly(xfe.properties["readonly"] == "true"); + } + if ("enabled" in xfe.properties) + { + xfe.getTarget().setEnabled(xfe.properties["enabled"] == "true"); + } + if ("value" in xfe.properties) + { + alfresco.log("setting " + xfe.getTarget().id + " = " + xfe.properties["value"]); + xfe.getTarget().setValue(xfe.properties["value"]); + } + break; + } + case "chiba-prototype-cloned": + { + var prototypeId = xfe.properties["prototypeId"]; + var originalId = xfe.properties["originalId"]; + alfresco.log("handlePrototypeCloned(" + xfe.targetId + + ", " + originalId + + ", " + prototypeId + ")"); + var clone = null; + var prototypeNode = _findElementById(this.xformsNode, prototypeId); + if (prototypeNode) + { + alfresco.log("cloning prototype " + prototypeNode.getAttribute("id")); + clone = prototypeNode.cloneNode(true); + } + else + { + alfresco.log("cloning prototype " + originalId); + var prototypeNode = _findElementById(this.xformsNode, originalId); + //clone = prototypeNode.cloneNode(true); + clone = prototypeNode.ownerDocument.createElement(alfresco.xforms.constants.XFORMS_PREFIX + ":group"); + clone.setAttribute(alfresco.xforms.constants.XFORMS_PREFIX + ":appearance", "repeated"); + for (var j = 0; j < prototypeNode.childNodes.length; j++) + { + clone.appendChild(prototypeNode.childNodes[j].cloneNode(true)); + } + clone.setAttribute("id", prototypeId); + } + + if (clone == null) + { + throw new Error("unable to clone prototype " + prototypeId); + } + + alfresco.log("created clone " + clone.getAttribute("id") + + " nodeName " + clone.nodeName + + " parentClone " + (prototypeClones.length != 0 + ? prototypeClones.peek().getAttribute("id") + : null)); + prototypeClones.push(clone); + break; + } + case "chiba-id-generated": + { + var originalId = xfe.properties["originalId"]; + + alfresco.log("handleIdGenerated(" + xfe.targetId + ", " + originalId + ")"); + var node = _findElementById(prototypeClones.peek(), originalId); + if (!node) + { + throw new Error("unable to find " + originalId + + " in clone " + dojo.dom.innerXML(clone)); + } + alfresco.log("applying id " + xfe.targetId + + " to " + node.nodeName + "(" + originalId + ")"); + node.setAttribute("id", xfe.targetId); + generatedIds = generatedIds || new Object(); + generatedIds[xfe.targetId] = originalId; + if (prototypeClones.length != 1) + { + var e = _findElementById(prototypeClones[prototypeClones.length - 2], originalId); + if (e) + { + e.setAttribute(alfresco.xforms.constants.ALFRESCO_PREFIX + ":prototype", "true"); + } + } + break; + } + case "chiba-item-inserted": + { + var position = Number(xfe.properties["position"]) - 1; + var originalId = xfe.properties["originalId"]; + var clone = prototypeClones.pop(); + // walk all nodes of the clone and ensure that they have generated ids. + // those that do not are nested repeats that should not be added + assert(clone.getAttribute("id") in generatedIds, + "expected clone id " + clone.getAttribute("id") + + " to be a generated id"); + function _removeNonGeneratedChildNodes(node, ids) + { + var child = node.firstChild; + while (child) + { + var next = child.nextSibling; + if (child.nodeType == document.ELEMENT_NODE) + { + if (child.getAttribute("id") in ids) + { + _removeNonGeneratedChildNodes(child, ids); + } + else + { + node.removeChild(child); + } + } + child = next; + } + }; + _removeNonGeneratedChildNodes(clone, generatedIds); + + if (prototypeClones.length != 0) + { + alfresco.log("using parentClone " + prototypeClones.peek().getAttribute("id") + + " of " + clone.getAttribute("id")); + var parentRepeat = _findElementById(prototypeClones.peek(), xfe.targetId); + parentRepeat.appendChild(clone); + } + else + { + alfresco.log("no parentClone found, directly insert " + clone.getAttribute("id") + + " on " + xfe.targetId); + xfe.getTarget().handleItemInserted(clone, position); + } + break; + } + case "chiba-item-deleted": + { + var position = Number(xfe.properties["position"]) - 1; + xfe.getTarget().handleItemDeleted(position); + break; + } + case "chiba-replace-all": + { + if (this.submitWidget) + { + this.submitWidget.done = true; + this.submitWidget.currentButton.click(); + } + break; + } + case "chiba-switch-toggled": + { + var switchElement = xfe.getTarget(); + switchElement.handleSwitchToggled(xfe.properties["selected"], + xfe.properties["deselected"]); + } + case "xforms-valid": + { + xfe.getTarget().setValid(true); + xfe.getTarget().setModified(true); + break; + } + case "xforms-invalid": + { + xfe.getTarget().setValid(false); + xfe.getTarget().setModified(true); + break; + } + case "xforms-required": + { + xfe.getTarget().setRequired(true); + break; + } + case "xforms-optional": + { + xfe.getTarget().setRequired(false); + break; + } + case "xforms-submit-error": + { + this.submitWidget = null; + var invalid_widgets = this.rootWidget.getWidgetsInvalidForSubmit(); + _show_error(document.createTextNode(alfresco.resources["validation_provide_values_for_required_fields"])); + var error_list = document.createElement("ul"); + invalid_widgets.each(function(invalid) + { + var error_item = document.createElement("li"); + error_item.appendChild(document.createTextNode(invalid.getAlert())); + error_list.appendChild(error_item); + invalid.showAlert(); + }); + _show_error(error_list); + break; + } + case "xforms-readonly": + { + xfe.getTarget().setReadonly(true); + break; + } + case "xforms-readwrite": + { + xfe.getTarget().setReadonly(false); + break; + } + case "xforms-submit": + case "xforms-submit-done": + case "xforms-enabled": + case "xforms-disabled": + break; + default: + { + alfresco.log("unhandled event " + events.childNodes[i].nodeName); + } + } + } + } +}); + +//////////////////////////////////////////////////////////////////////////////// +// error message display management +//////////////////////////////////////////////////////////////////////////////// + +/** hides the error message display. */ +function _hide_errors() +{ + var errorDiv = $(alfresco.xforms.constants.XFORMS_ERROR_DIV_ID); + if (errorDiv) + { + errorDiv.empty(); + errorDiv.style.display = "none"; + } +} + +/** shows the error message display. */ +function _show_error(msg) +{ + var errorDiv = $(alfresco.xforms.constants.XFORMS_ERROR_DIV_ID); + if (!errorDiv) + { + errorDiv = new Element("div", { "id": alfresco.xforms.constants.XFORMS_ERROR_DIV_ID }); + errorDiv.addClass("infoText statusErrorText xformsError"); + errorDiv.injectBefore($(alfresco.xforms.constants.XFORMS_UI_DIV_ID)); + } + + if (errorDiv.style.display == "block") + { + errorDiv.appendChild(document.createElement("br")); + } + else + { + errorDiv.style.display = "block"; + } + errorDiv.appendChild(msg); +} + +//////////////////////////////////////////////////////////////////////////////// +// DOM utilities - XXXarielb should be merged into common.js +//////////////////////////////////////////////////////////////////////////////// + +function _findElementById(node, id) +{ +// alfresco.log("looking for " + 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 == document.ELEMENT_NODE) + { + var n = _findElementById(node.childNodes[i], id); + if (n) + { + return n; + } + } + } + return null; +} + +function _hasAttribute(node, name) +{ + return (node == null + ? false + : (node.hasAttribute + ? node.hasAttribute(name) + : node.getAttribute(name) != null)); +} + +function _getElementsByTagNameNS(parentNode, ns, nsPrefix, tagName) +{ + return (parentNode.getElementsByTagNameNS + ? parentNode.getElementsByTagNameNS(ns, tagName) + : parentNode.getElementsByTagName(nsPrefix + ":" + tagName)); +} + +//////////////////////////////////////////////////////////////////////////////// +// XPath wrapper +//////////////////////////////////////////////////////////////////////////////// + +function _evaluateXPath(xpath, contextNode, result_type) +{ + var xmlDocument = contextNode.ownerDocument; + if (alfresco.constants.DEBUG) + { + alfresco.log("evaluating xpath " + xpath + + " on node " + contextNode.nodeName + + " in document " + xmlDocument); + } + var result = null; + if (xmlDocument.evaluate) + { + var nsResolver = (xmlDocument.createNSResolver + ? xmlDocument.createNSResolver(xmlDocument.documentElement) + : null); + result = xmlDocument.evaluate(xpath, + contextNode, + nsResolver, + result_type, + null); + if (result) + { + switch (result_type) + { + case XPathResult.FIRST_ORDERED_NODE_TYPE: + result = result.singleNodeValue; + break; + case XPathResult.BOOLEAN_TYPE: + result = result.booleanValue; + break; + case XPathResult.STRING_TYPE: + result = result.stringValue; + break; + } + } + } + else + { + xmlDocument.setProperty("SelectionLanguage", "XPath"); + var namespaces = []; + for (var i = 0; i < xmlDocument.documentElement.attributes.length; i++) + { + var attr = xmlDocument.documentElement.attributes[i]; + if (attr.nodeName.match(/^xmlns:/)) + { + namespaces.push(attr.nodeName + "=\'" + attr.nodeValue + "\'"); + } + } + + if (alfresco.constants.DEBUG) + { + alfresco.log("using namespaces " + namespaces.join(",")); + } + xmlDocument.setProperty("SelectionNamespaces", namespaces.join(' ')); + if (result_type == XPathResult.FIRST_ORDERED_NODE_TYPE) + { + result = xmlDocument.selectSingleNode(xpath); + } + else if (result_type == XPathResult.BOOLEAN_TYPE) + { + result = true; + } + } + alfresco.log("resolved xpath " + xpath + " to " + result); + return result; +} + +if (!XPathResult) +{ + var XPathResult = + { + ANY_TYPE: 0, + NUMBER_TYPE: 1, + STRING_TYPE: 2, + BOOEAN_TYPE: 3, + FIRST_ORDERED_NODE_TYPE: 9 + }; +} + +dojo.html.toCamelCase = function(str) +{ + return str.replace(/-./, function(str) { return str.charAt(1).toUpperCase(); }); +} + +//////////////////////////////////////////////////////////////////////////////// +// tiny mce integration +//////////////////////////////////////////////////////////////////////////////// + +alfresco.constants.TINY_MCE_DEFAULT_PLUGINS = + alfresco.xforms.RichTextEditor.determineNecessaryTinyMCEPlugins(alfresco.xforms.widgetConfig); + +alfresco.constants.TINY_MCE_DEFAULT_SETTINGS = +{ + theme: "advanced", + mode: "exact", + plugins: alfresco.constants.TINY_MCE_DEFAULT_PLUGINS, + width: -1, + height: -1, + auto_resize: false, + force_p_newlines: false, + encoding: "UTF-8", + entity_encoding: "raw", + add_unload_trigger: false, + add_form_submit_trigger: false, + theme_advanced_toolbar_location: "top", + theme_advanced_toolbar_align: "left", + theme_advanced_buttons1: "", + theme_advanced_buttons2: "", + theme_advanced_buttons3: "", + urlconverter_callback: "alfresco_TinyMCE_urlconverter_callback", + file_browser_callback: "alfresco_TinyMCE_file_browser_callback" +}; + +tinyMCE.init($extend({}, alfresco.constants.TINY_MCE_DEFAULT_SETTINGS)); + +window.addEvent("domready", + function() + { + document.xform = new alfresco.xforms.XForm(); + });