cancel
Showing results for 
Search instead for 
Did you mean: 

File upload on create content

oakman
Champ in-the-making
Champ in-the-making
Hi,

I have a problem regarding file uploads. Today, I have a form which creates content (via "create content") based on my custom content type. This custom content type has some mandatory properties. Now, I want to do the same when uploading a file. I know how to set the correct document type when uploading a file (via flash-upload.get.js), but the problem is that the content cannot be created due to my mandatory properties. Now I have two options:

1. Modify the "Upload" action to somehow include my mandatory properties.
2. Somehow configure the form to understand file upload.

Can the form service understand file upload today? Is there any way to modify the flash-upload to render the correct mandatory fields?

Thanks,

Niklas
16 REPLIES 16

calle
Champ in-the-making
Champ in-the-making
Anybody with an answer to this? I guess you must develop some kind of upload-control in the form for your association… if you figure out how to do this please post the solution!
//Calle

iblanco
Confirmed Champ
Confirmed Champ
For some comments I found in the Source Code of Alfresco I think that file uploading is not supported yet. So based on the  file upload API that Share uses for uploading I made my own API for uploading and aware of my custom types.

Then I configured in share-config-custom.xml a config section with "model-type" evaluator that includes the "cm:content" field:


<config evaluator="model-type" condition="custom:myType">
        <forms>
            <form submission-url="/api/custom/upload">
                <field-visibility>
                    <show id="cm:content" force="true" />
                    <show id="cm:title" force="true" />
                    <show id="cm:description" force="true" />
                    <show id="cm:author" force="true" />
                    <show id="size" for-mode="view" />
                    <show id="cm:creator" for-mode="view" />
                    <show id="cm:created" for-mode="view" />
                    <show id="cm:modifier" for-mode="view" />
                    <show id="cm:modified" for-mode="view" />

                    <!– Custom –>                   
                    <show id="custom:myField" />
                   
                    <!– tags and categories –>
                    <show id="cm:taggable" for-mode="edit" force="true" />
                    <show id="cm:categories" />

                    <!– cm:dublincore aspect –>
                    <show id="cm:publisher" />
                    <show id="cm:contributor" />
                    <show id="cm:type" />
                    <show id="cm:identifier" />
                    <show id="cm:dcsource" />
                    <show id="cm:coverage" />
                    <show id="cm:rights" />
                    <show id="cm:subject" />

                    <!– cm:complianceable aspect –>
                    <show id="cm:removeAfter" />

                    <!– cm:effectivity aspect –>
                    <show id="cm:from" />
                    <show id="cm:to" />

                    <!–  cm:summarizable aspect –>
                    <show id="cm:summary" />

                    <!– cm:translatable aspect –>
                    <show id="cm:translations" />

                    <!– cm:localizable aspect –>
                    <show id="cm:locale" />

                    <!– cm:ownable aspect –>
                    <show id="cm:owner" />

                    <!– cm:attachable aspect –>
                    <show id="cm:attachments" />

                    <!– cm:emailed aspect –>
                    <show id="cm:originator" />
                    <show id="cm:addressee" />
                    <show id="cm:addressees" />
                    <show id="cm:sentdate" />
                    <show id="cm:subjectline" />
                </field-visibility>
                <appearance>
                    <field id="cm:content">
                        <control template="/org/alfresco/components/form/controls/fileUpload.ftl" />
                    </field>
                    <set id="" appearance="panel" label-id="set.label.documentoCondicionesComerciales" />
                    <field id="cm:title">
                        <control template="/org/alfresco/components/form/controls/textfield.ftl" />
                    </field>
(…)
    </config>

The control I include for the content "fileUpload.ftl" is just a control that has a standard "file" typed input field:


<div class="form-field">
   <label for="${fieldHtmlId}">${field.label?html}:<#if field.mandatory><span class="mandatory-indicator">${msg("form.required.fields.marker")}</span></#if></label>
   <input type="file" id="${fieldHtmlId}" name="${field.name}" />
</div>

With all this in place I can go to the form console, request a form of kind "type",id  "custom:myType", mode "create" and "multipart" and use it to upload one file with my custom model and fields applied and set. I suppose in a future I would be able to get rid of the custom API, but for now it does the trick.

Now the problem is that I don't manage to make the "create-content" toolbar to show my type. I suppose I will have to make my own page in Share or something like this, but I'm a bit stuck. Any clue ?

orichaud
Champ in-the-making
Champ in-the-making
I have read your post with interest.
First of all, have you manage to hook your create for with Alfresco share so that you can start creating new custom type?
Is your custom type also deriving from cm:content?

Regards.

Olivier

iblanco
Confirmed Champ
Confirmed Champ
Hi Olivier,

Yes I managed to hook my create with Share. I copied the standard create-content component in share and made some changes so that it would invoke the form UI component with itemKind "type" and the corresponding custom type.

Yes, my custom type derives from cm:content, but the same approach should be reasonable for other types. But keep in mind that deriving from cm:content for all custom contents and deriving from cm:folder for all custom folder types seems to be considered a best practice in Alfresco reference books.

orichaud
Champ in-the-making
Champ in-the-making
Hi,

Thank you for your reply.

Your pointer is useful, but I am sorry. I don't have your expertise and I need more information to go on. Would you mind detailing how you proceeded and the files you modified?

Regards.

Olivier

iblanco
Confirmed Champ
Confirmed Champ
Sorry not much time for explanation, but those are the changes, you'll have to study them. Keep in mind that those are quite dirty hacks.

I created '<tomcat>/shared/classes/alfresco/web-extension/site-data/pages/custom-create-content.xml', with this content:


<?xml version='1.0' encoding='UTF-8'?>
<page>
   <title>Create Custom Content</title>
   <title-id>page.customCreateContent.title</title-id>
   <description>Create text-based content</description>
   <description-id>page.eunCreateContent.description</description-id>
   <template-instance>custom-create-content</template-instance>
   <authentication>user</authentication>
</page>

I created '<tomcat>/shared/classes/alfresco/web-extension/site-data/template-instances/custom-create-content.xml', with this content:

<?xml version='1.0' encoding='UTF-8'?>
<template-instance>
   <template-type>org/alfresco/create-content</template-type>
   <properties>
      <pageFamily>documentlibrary</pageFamily>
      <container>documentLibrary</container>
   </properties>
   <components>
      <!– title: normal, portlet, repository & portlet+repository –>
      <component>
         <region-id>title</region-id>
         <url>/components/title/collaboration-title</url>
      </component>
      <component>
         <region-id>portlet-title</region-id>
         <url>/components/title/portlet-collaboration-title</url>
      </component>
      <component>
         <region-id>repo-title</region-id>
         <url>/components/title/simple-title</url>
         <properties>
            <title>title.repository</title>
            <subtitle>title.browser</subtitle>
         </properties>
      </component>
      <component>
         <region-id>portlet-repo-title</region-id>
         <url>/components/title/simple-title</url>
         <properties>
            <title>title.repository</title>
            <subtitle>title.browser</subtitle>
         </properties>
      </component>
      <component>
         <region-id>navigation</region-id>
         <url>/components/navigation/collaboration-navigation</url>
      </component>
      <component>
         <region-id>create-content-mgr</region-id>
         <url>/components/create-content/create-content-mgr</url>
      </component>
      <component>
         <region-id>create-content</region-id>
         <url>/components/form?type={type}&amp;destination={destination}</url>
         <properties>
            <itemKind>type</itemKind>
            <itemId>{type}</itemId>
            <mode>create</mode>
            <submitType>multipart</submitType>
            <showCaption>true</showCaption>
            <showCancelButton>true</showCancelButton>
         </properties>
      </component>
   </components>
</template-instance>

'<tomcat>/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/toolbar.get.config.xml' :

<toolbar>
    <createContent>
        <content mimetype="text/plain" icon="plain-text" label="menu.create-content.text" />
        <content mimetype="text/html" icon="html" label="menu.create-content.html" />
        <content mimetype="text/xml" icon="xml" label="menu.create-content.xml" />
    </createContent>
    <uploadCustomContent>
        <content type="custom:customType1" icon="plain-text" label="menu.upload-custom-content.customType1" />
        <content type="custom:customType2" icon="plain-text" label="menu.upload-custom-content.customType2" />
    </uploadCustomContent>
    <actionSets>
        <actionSet id="default">
            <action type="action-link" id="onActionCopyTo" label="menu.selected-items.copy" />
            <action type="action-link" id="onActionMoveTo" permission="delete" label="menu.selected-items.move" />
            <action type="action-link" id="onActionDelete" permission="delete" label="menu.selected-items.delete" />
            <action type="action-link" id="onActionAssignWorkflow" asset="document" label="menu.selected-items.assign-workflow" />
            <action type="action-link" id="onActionManagePermissions" permission="permissions" label="menu.selected-items.manage-permissions" />
        </actionSet>
    </actionSets>
</toolbar>

'<tomcat>/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/toolbar.get.js'  :

<import resource="classpath:alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/include/toolbar.lib.js">

Similar for repo-toolbar.get.config.xml and repo-toolbar.get.js

'<tomcat>/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/include/toolbar.lib.js' :

const PREFERENCES_ROOT = "org.alfresco.share.documentList";

function getPreferences()
{
   var preferences = {};
  
   // Request the current user's preferences
   var result = remote.call("/api/people/" + stringUtils.urlEncode(user.name) + "/preferences?pf=" + PREFERENCES_ROOT);
   if (result.status == 200 && result != "{}")
   {
      var prefs = eval('(' + result + ')');
      try
      {
         // Populate the preferences object literal for easy look-up later
         preferences = eval('(prefs.' + PREFERENCES_ROOT + ')');
         if (typeof preferences != "object")
         {
            preferences = {};
         }
      }
      catch (e)
      {
      }
   }
  
   model.preferences = preferences;
}

function getActionSet(myConfig)
{
   // Actions
   var xmlActionSet = myConfig..actionSet.(@id == "default"),
      actionSet = [];
  
   // Found match?
   if (xmlActionSet.@id == "default")
   {
      for each(var xmlAction in xmlActionSet.action)
      {
         actionSet.push(
         {
            id: xmlAction.@id.toString(),
            type: xmlAction.@type.toString(),
            permission: xmlAction.@permission.toString(),
            asset: xmlAction.@asset.toString(),
            href: xmlAction.@href.toString(),
            label: xmlAction.@label.toString()
         });
      }
   }
  
   model.actionSet = actionSet;
}

function getCreateContent(myConfig)
{
   // New Content
   var xmlCreateContent = myConfig.createContent,
        xmlUploadCustomContent = myConfig.uploadCustomContent,
      createContent = [],
      uploadCustomContent = [];
  
   if (xmlCreateContent != null)
   {
      for each (var xmlContent in xmlCreateContent.content)
      {
         createContent.push(
         {
            mimetype: xmlContent.@mimetype.toString(),
            icon: xmlContent.@icon.toString(),
            permission: xmlContent.@permission.toString(),
            formid: xmlContent.@formid.toString(),
            label: xmlContent.@label.toString()
         });
      }
   }

   if (xmlUploadCustomContent != null)
   {
      for each (var xmlContent in xmlUploadCustomContent.content)
      {
         uploadCustomContent.push(
         {
            type: xmlContent.@type.toString(),
            icon: xmlContent.@icon.toString(),
            permission: xmlContent.@permission.toString(),
            formid: xmlContent.@formid.toString(),
            label: xmlContent.@label.toString()
         });
      }
   }
  
   // Google Docs enabled?
   var googleDocsEnabled = false,
      googleDocsConfig = config.scoped["DocumentLibrary"]["google-docs"];

   if (googleDocsConfig !== null)
   {
      googleDocsEnabled = (googleDocsConfig.getChildValue("enabled").toString() == "true");
     
      var configs = googleDocsConfig.getChildren("creatable-types"),
         creatableConfig,
         configItem,
         creatableType,
         mimetype;

      if (configs)
      {
         for (var i = 0; i < configs.size(); i++)
         {
            creatableConfig = configs.get(i).childrenMap["creatable"];
            if (creatableConfig)
            {
               for (var j = 0; j < creatableConfig.size(); j++)
               {
                  configItem = creatableConfig.get(j);
                  // Get type and mimetype from each config item
                  creatableType = configItem.attributes["type"].toString();
                  mimetype = configItem.value.toString();
                  if (creatableType && mimetype)
                  {
                     createContent.push(
                     {
                        mimetype: mimetype,
                        icon: creatableType,
                        permission: "create-google-doc",
                        formid: "doclib-create-googledoc",
                        label: "google-docs." + creatableType
                     });
                  }
               }
            }
         }
      }
   }
  
   model.googleDocsEnabled = googleDocsEnabled;
   model.createContent = createContent;
   model.uploadCustomContent = uploadCustomContent;
}

/**
* Main entrypoint for component webscript logic
*
* @method main
*/
function main()
{
   var myConfig = new XML(config.script);
  
   getPreferences();
   getActionSet(myConfig);
   getCreateContent(myConfig);
}

main();

'<tomcat>/shared/classes/alfresco/web-extension/site-webscripts/org/alfresco/components/documentlibrary/include/toolbar.lib.ftl':

<#include "../../../include/alfresco-macros.lib.ftl" />
<#macro toolbarTemplate>
<#nested>
<#assign el=args.htmlid?html>
<div id="${el}-body" class="toolbar">

   <div id="${el}-headerBar" class="header-bar flat-button theme-bg-2">
      <div class="left">
         <div class="hideable toolbar-hidden DocListTree">
            <div class="create-content">
               <button id="${el}-createContent-button" name="createContent">${msg("button.create-content")}</button>
               <div id="${el}-createContent-menu" class="yuimenu">
                  <div class="bd">
                     <ul>
                     <#list createContent as content>
                        <#assign href>create-content?mimeType=${content.mimetype?html}&amp;destination={nodeRef}<#if (content.formid!"") != "">&amp;formId=${content.formid?html}</#if></#assign>
                        <li><a href="${siteURL(href)}" rel="${content.permission!""}"><span class="${content.icon}-file">${msg(content.label)}</span></a></li>
                     </#list>
                     <#list uploadCustomContent as content>
                        <#assign href>custom-create-content?type=${content.type?html}&amp;destination={nodeRef}<#if (content.formid!"") != "">&amp;formId=${content.formid?html}</#if></#assign>
                        <li><a href="${siteURL(href)}" rel="${content.permission!""}"><span class="${content.icon}-file">${msg(content.label)}</span></a></li>                    
                     </#list>
                     </ul>
                  </div>
               </div>
            </div>
            <div class="separator"> </div>
         </div>
         <div class="hideable toolbar-hidden DocListTree">
            <div class="new-folder"><button id="${el}-newFolder-button" name="newFolder">${msg("button.new-folder")}</button></div>
            <div class="separator"> </div>
         </div>
         <div class="hideable toolbar-hidden DocListTree">
            <div class="file-upload"><button id="${el}-fileUpload-button" name="fileUpload">${msg("button.upload")}</button></div>
            <div class="separator"> </div>
         </div>
         <div class="selected-items hideable toolbar-hidden DocListTree DocListFilter TagFilter DocListCategories">
            <button class="no-access-check" id="${el}-selectedItems-button" name="doclist-selectedItems-button">${msg("menu.selected-items")}</button>
            <div id="${el}-selectedItems-menu" class="yuimenu">
               <div class="bd">
                  <ul>
                  <#list actionSet as action>
                     <li><a type="${action.asset!""}" rel="${action.permission!""}" href="${action.href}"><span class="${action.id}">${msg(action.label)}</span></a></li>
                  </#list>
                     <li><a href="#"><hr /></a></li>
                     <li><a href="#"><span class="onActionDeselectAll">${msg("menu.selected-items.deselect-all")}</span></a></li>
                  </ul>
               </div>
            </div>
         </div>
      </div>
      <div class="right">
         <div class="customize" style="display: none;"><button id="${el}-customize-button" name="customize">${msg("button.customize")}</button></div>
         <div class="hide-navbar"><button id="${el}-hideNavBar-button" name="hideNavBar">${msg("button.navbar.hide")}</button></div>
         <div class="rss-feed"><button id="${el}-rssFeed-button" name="rssFeed">${msg("link.rss-feed")}</button></div>
      </div>
   </div>

   <div id="${el}-navBar" class="nav-bar flat-button theme-bg-4">
      <div class="hideable toolbar-hidden DocListTree DocListCategories">
         <div class="folder-up"><button class="no-access-check" id="${el}-folderUp-button" name="folderUp">${msg("button.up")}</button></div>
         <div class="separator"> </div>
      </div>
      <div id="${el}-breadcrumb" class="breadcrumb hideable toolbar-hidden DocListTree DocListCategories"></div>
      <div id="${el}-description" class="description hideable toolbar-hidden DocListFilter TagFilter"></div>
   </div>

</div>
</#macro>

orichaud
Champ in-the-making
Champ in-the-making
Thanks a lot for your time. I will try it asap et will let you know.

billerby
Champ on-the-rise
Champ on-the-rise
Iblanco, thanks a lot your post helped me a lot. If you could post your modified version of the upload webscript as well you would save me even more time Smiley Happy

/Erik

iblanco
Confirmed Champ
Confirmed Champ
Ok, here you have, you'll probably need to make some changes to this files in order to work but I hope they help. As I already said, this is probably a dirty hack and not necessarily the best way to do it. An probably will break in future versions of Alfresco.


Description XML:

<webscript>
  <shortname>CUSTOM type documents</shortname>
  <description>Webscript to crete custom documents</description>
  <url>/custom/documentos/{type}</url>
  <format default="html"/>
  <authentication>user</authentication>
</webscript>

Javascript:

function _DOCUMENTOS_addFile(files, field, properties)
{
   var name = _DOCUMENTOS_prop2Name(field.name);
   files[name] = field;
   return true;
}

function _DOCUMENTOS_addProperty(properties, field)
{
   var separator = ",";
   var name = _DOCUMENTOS_prop2Name(field.name);
   if (!_DOCUMENTOS_isMultiValued(name)) {
      properties[name] = field.value;
   } else {
      properties[name] = field.value.split(separator);
   }   
}

function _DOCUMENTOS_constructDetailsUrl(callerUrl, nodeRef)
{
   var baseUrl = callerUrl.substring(0,callerUrl.lastIndexOf("/custom-create-content"));
   var url = baseUrl + "/document-details?nodeRef=" + nodeRef;
   return url;
}

function _DOCUMENTOS_isMultiValued(name)
{
   var qName = dictionaryService.getQName(name);
   var propDef = dictionaryService.getProperty(qName);
   return propDef.isMultiValued();
}

function main()
{
   var destination = null;
   var files = [];
   var i = null;
   var node = null;
   var properties = [];
   var redirect = null;
   var type = null;
   
   for each (field in formdata.fields)
   {
      var name = String(field.name).toLowerCase();
      if (field.isFile) {
         _DOCUMENTOS_addFile(files, field, properties);
      } else if ("alf_destination" == name) {
         destination = field.value;
      } else if ("alf_redirect" == name) {
         redirect = field.value;
      } else if (-1 != name.search("^prop_")){
         _DOCUMENTOS_addProperty(properties, field);
      }
   }

   parent = search.findNode(destination);
   if (undefined == properties['cm:name'] && undefined != files["cm:content"]) {
      filename = files["cm:content"].filename;
   } else {
      filename = properties["cm:name"];
   }
   
   
    var counter = 1,
    tmpFilename = filename,
    dotIndex;
    while (null !== parent.childByNamePath(tmpFilename))
   {
       dotIndex = filename.lastIndexOf(".");
      if (dotIndex == 0)
      {
         // File didn't have a proper 'name' instead it had just a suffix and started with a ".", create "1.txt"
         tmpFilename = counter + filename;
      }
      else if (dotIndex > 0)
      {
         // Filename contained ".", create "filename-1.txt"
         tmpFilename = filename.substring(0, dotIndex) + "-" + counter + filename.substring(dotIndex);
      }
      else
      {
         // Filename didn't contain a dot at all, create "filename-1"
         tmpFilename = filename + "-" + counter;
       }
       counter++;
   }
    filename = tmpFilename;
    properties["cm:name"] = filename;

   type = "custom:" + url.templateArgs.type;
   node = parent.createNode(filename, type, properties);
   for (i in files) {      
      node.properties[i].write(files[i]["content"]);
      node.properties[i].mimetype = files[i]["mimetype"];
      node.properties[i].guessEncoding();
   }

   model.node = node;
   model.type = url.templateArgs.type;
   model.detailsUrl = _DOCUMENTOS_constructDetailsUrl(headers.referer, node.nodeRef);
}

function _DOCUMENTOS_prop2Name(name)
{
   name = name.substring(5);
   name = name.replace("_", ":");
   return name;
}

main();

JSON template (.json.ftl), not sure why I needed this:

{   
    "persistedObject": "${node.nodeRef}",
    "message": "Successfully persisted form for item [type]eun_${type}"
}

HTML template, this is really the nastiest thing I could to manage to go back to the details view page after uploading, but couldn't find a better way:

<script>parent.location.href="${detailsUrl}"</script>

Bye.