Working with folders

Add a new folder

I really wanted to write this because I had some hard time figuring out how this works. The documentation is just wrong and I had to debug quite deeply ICN to understand what the problem was. The function to add a folder is:

ecm.model.Repository.addFolderItem(parentFolder, objectStore, templateName, criterias, childComponentValues, permissions, securityPolicyId, teamspaceId, callback)

JSDoc, but since it’s wrong let’s comment it:

addFolderItem(parentFolder, objectStore, templateName, criterias, childComponentValues, permissions, securityPolicyId, teamspaceId, callback)

Adds a folder to the repository. Parameters:
  • parentFolder: A ecm.model.ContentItem object for the parent folder.
  • objectStore: A object store object holding identifier information (FileNet P8 only).
  • templateName: A string value holding the content class name.
    This is actually the classId and not an entry template ID as you could thing.
  • criterias: An object holding the criteria information.
    This one is all wrong, that’s not the criterias but an object very special actually containing the criterias. If you don’t have it right, the CE will throw an exception. Here is what it should be and I’ll detail after the screenshot.
    addFolderCriterias

    • childComponentValues: you can use an empty array for p8 []
    • criterias, this is where you actually give your criterias as for an add, and not straigt as the documentation let you think. It looks like (This comes from an add document screenshot, you will have to set the FolderName property instead of DocumentTitle):
      icn_debug_criteria
    • docid: This is the id of the parent, which you also give in the parentFolder parameter. I have no clue why it’s needed twice…
    • documentType: don;t try to understand, you use a addFolder function but you still need to repeat that’s a folder 🙂
    • folderName: the name of your folder, you have to add it also as criterias
  • childComponentValues: An object holding the child component values (Content Manager only).
    Again, use an [] for p8.
  • permissions: An object holding the permission information.
    You can leave this empty if you are using the default settings. If you want to set property, this is an array of Permissions object, see my post Working with documents.
  • securityPolicyId: A string value holding the security policy id (FileNet P8 only).
  • teamspaceId: A string value holding the teamspace id.
  • callback: A callback function called after the item has been added. Will pass a ecm.model.ContentItem object as a parameter.

And here is an example of how to use this:

// Build this strange object the doc does'nt mention and you can't guess
var folderProperties = {
     childComponentValues: [], // Use [] for p8
     criterias: [{dataType: 'xs:string', name: 'FolderName', value: itemName}], // Your criterias, here I have only the name
     documentType: 'Folder', // The type is always Folder for a folder, even if you are using a child class
     folderName: itemName, // Again, the name of the folder, needs to be here and in the criterias
     docid: parentContentItem.id // the parent folder id
};
repository.addFolderItem(
     parentContentItem, // the parent folder ad a ecm.model.ContentItem
     parentContentItem.objectStore, // the objectstore, you can use the parent to get this
     "Folder", // The class id, use Folder for a classic folder, or the id of your class inherited of Folder for instance
     folderProperties, // This is the strange object you can not guess
     [], // The childComponentValues again, use [] for p8
     null, // the permission, you can use null to use the default permissions
     null, // the security policity id, use null if none
     null, // the teamspace if, use null if none
     lang.hitch(this, function (newItem) {
         // What do you want to do after the add
         // A parentContentItem.refresh(); could be nice for the user :)
     })
);

Test the existence of a folder

This is one is also a bit tricky, because their is no API allowing us to do that. and there is a problem with the following code:

repository.retrieveItem('/MyFolder/Guillaume', function (item) {
    // Here the item exist and you fetched it
};

But the problem with that is, if the item does not exist, the retrieveItem function add an error message to the desktop, which will be displayed ad an error dialog for the user. There is no way to specify an error callback, nor to aspect a return function (believe me I tried but the Request class directly adding the error to the desktop via a private function, that would be really ugly to aspect and we might catch return from other Request), which makes this function pointless for what we want to do.

We have to find a way test the existence and get an error callback if it does not exist. Here is what I’m using:

doesFolderExist: function (repository, folderPath) {
    var res = new Deferred();
    var requestParams = {
        repositoryId: repository.id,
        objectStoreId: repository.objectStore.id || "",
        docid: folderPath,
        template_name: "Folder",
        version: ""
        };
    Request.invokeService("getContentItems", repository.type, requestParams, lang.hitch(this, function (response) {
        res.resolve(true);
    }), false, false, lang.hitch(this, function () {
        res.resolve(false);
    }));
    return res.promise;
}

That way you can handle the case where the item does not exist.

Fetch a folder without error if the folder does not exist.

Now you can use what’s above to retrieve a folder without failing if the folder does not exist.

getFolderSmart: function (repository, folderPath, parent) {
     var res = new Deferred();
    var requestParams = {
        repositoryId: repository.id,
        objectStoreId: repository.objectStore.id || "",
        docid: folderPath,
        template_name: "Folder",
        version: ""
    };
    Request.invokeService("getContentItems", repository.type, requestParams, lang.hitch(this, function (response) {
        // Build the ContentItem from the response
        var item = ContentItem.createFromJSON(response.rows[0], repository, null);
        // When you build a ContentItem from JSON, of course it has no idea of its context,
        // Which means he has no parent and the getPath() will return /nameOfTheFolder
        // We have to give it a context by settings the parent
        item.parent = parent;
        res.resolve(item);
    }), false, false, lang.hitch(this, function () {
        res.resolve(null);
    }));
    return res.promise;
}

Fetch the content of a folder

The method is the following:

retrieveFolderContents(folderOnly, callback, orderBy, descending, noCache, teamspaceId, filterType, parent, criteria)

However it’s worth documenting it better because the documentation just doesn’t say anything… especially about what we can and can’t do with the criterias, filter and so on.

There is a lot of limitations on this method because it is intended for UI only and not complex search, I’ll talk about them after explaining some simple use cases:

Get only the sub-folders (for a folder tree for instance):

folder.retrieveFolderContents(true, function (resultSet) {
    // You will have your items in resultSet.items, which is an ContentItem[]
}, null, null, null, null, null, folder, null);

Get sub-folders and document (for a content pane for instance):

folder.retrieveFolderContents(false, function (resultSet) {
    // You will have your items in resultSet.items, which is an ContentItem[]
}, null, null, null, null, '', folder, null);

Apparently, from the code, you can not fetch document in the Root directory, which makes sense since it is not allowed in P8 anyway, I could’t tell for other repository types.

Now let’s talk complicated stuff, how to filter this :). That where it starts hurting the brain because there is no documentation, and even debugging the client doesn’t really help since there just don’t use it in the existing ICN, and almost everything is done on the server side.

Let’s take all parameters one by one

  • folderOnly: this one’s easy, true you get only sub-folders, false you get also documents. Just know that if this is true, then the filterType doesn;t matter anymore and will be set to searchFolder anyway
  • callback: what to do with the result, take an ecm.model.ResultSet as parameter
  • orderBy: This one can give you an headache, the only possible values are:
    • !MimeTypeIcon (don’t ask my why the ! 🙂 ), but good thing is that it will work for Folder and Document
    • !Name: Also work for both
    • Id
    • LastModifier
    • DateLastModified
    • ContainerType
    • All others would simply be ignored
  • descending: true or false, used only if orderBy is used
  • noCache: if set to false or undefined, you will get the same result as the previous request if the filterType is the same. If set to true, it will ask the server any times. Set it to true if you are filtering or you will have the same answer, except if you name your fileType depending of what you filter, so if you ask the same thing you can benefit of the cache system. How smart ?!! 🙂
  • teamspaceId: can be null
  • filterType: Can be
    • searchFolder which is equivalent to foldersOnly to true (foldersOnly overwrite this value with searchFolder anyway of set to true)
    • searchAndFolderSearch
    • anything else which means “I don’t care”, but can be used to cache result of your filtered request if you reuse the same filterType (anything you want since ICN doesn’t care), if null will be set to ‘folderSearch’ or ” depending of foldersOnly
  • parent: The parent where to look, this is a ContentItem, not an ID
  • criterias: Here things become interesting. Crtierias would be used to build the WHERE clause, but for both the Sub-Folders and the Documents. That’s usually not a problem but just know it because it can be. Criterias is an object with a type member, and an array of conditions item, looking like this.
    {
        type: "OR", // OR or AND
        conditions: [
            {
                name: "{NAME}", // Can only be {NAME} or {ALL}, which to a OR on all searchable fields
                condition: "contain", // can only be contain, startWith or endWidth, can NOT be =
                value: "myText", // Can be anything
            }
        ]
    }
    

 

4 thoughts on “Working with folders

  1. Kieren

    Hello. This is brilliant and I really enjoy your advanced articles. I’m new to ICN plugin development and I dont know how to use your getFolderSmart with folder.retrieveFolderContents. Any help would be greatly appreciated.

    Reply
    1. Guillaume Post author

      Hi Kieren,

      I’m really sorry for the delay I was ou of the country for a while and didn’t get a chance to answer any of the comments I had.

      Not sure where you are stuck, but basically use the getSmartFolder to get the folder (in a promise), then if the result is not null (meaning the folder exists), use the retrieveFolderContents on it.

      Briefly, that would look like this:


      this.getSmartFolder(repository, folderPath, parent).then(function (res) {
        if (res != null) {
          res.retrieveFolderContents(false, function (resultSet) {
            // Here you have your folder's content, I didn't use any filtering that's why I use only the first two parameters of retrieveFolderContents
          });
        }
      });

      Hope that helps (and that wasn’t too late 🙂 ), if you need more help let me know.

      Reply
  2. Amit

    Hi. Great article. I am trying to sort documents and folders by order by clause to a custom property. It is working for documents but not for folders. As you mentioned sorting for folders only works for certain system properties. Can you suggest any alternative approach in case we want to sort even folders by custom properties. Thanks.

    Reply

Leave a Reply