Search code examples
javascriptdojofilenet-p8ibm-content-navigator

IBM Content Navigator 3.x - Pre-filling ChoiceList values in AddContentItemDialog


We aim to implement a prefilled document creation form with values retrieved from a PluginService object.

When a user right-clicks on a document and select "New document from this one", it fires an Action which opens an AddContentItemDialog. Then, the service is invoked to retrieve the properties of the selected document (maybe it's not necessary, through Firefox developper panel I see that most, maybe all custom properties are already fetched).

I'm able to fill text field properties, but not the ChoiceList ones: they won't update, although they might be filled internally.

Here is a commented sample of code :

require(["dojo/_base/declare",
         "dojo/_base/lang",
         "dojo/aspect", 
         "ecm/model/Request",
         "ecm/widget/dialog/AddContentItemDialog"], 
function(declare, lang, aspect, Request, AddContentItemDialog) {        

    // Parameters passed to the service as HttpServletRequest
    // (Custom function)
    var serviceParams = getServicesParams(items[0]);

    // Object store and parentFolder retrieving (needed below)
    var parentFolder = items[0].parent;
    var objectStore = items[0].objectStore;

    // Retrieving the template to use for the dialog
    // (Custom function)
    var entryTemplate = retrieveTemplate(objectStore, parentFolder); 

    // Service call 
    Request.invokePluginService("FormPlugin", "FormService", {
        requestParams: serviceParams,
        requestCompleteCallback: function(response) {
            // Creating the global dialog  box
            var addContentItemDialog  = new AddContentItemDialog();
            // Box containing the document properties
            var addContentItemPropertiesPane =
                addContentItemDialog.addContentItemPropertiesPane;
            // Box containing general stuff
            var addContentItemGeneralPane =
                addContentItemDialog.addContentItemGeneralPane;

            // Showing the dialog box 
            addContentItemDialog.show(
                repository,
                parentFolder,  /* parent folder       */
                true,          /* document being added*/
                false,         /* not virtual          */
                null,          /* no callback function */
                null,          /* no teamspace */
                true,          /* use an entry template */
                entryTemplate, /* entry template */
                true           /* can't choose directory from another rep */
           );

            // Waiting for complete rendering before filling the properties and general fields
            aspect.after(addContentItemPropertiesPane,
                         "onCompleteRendering",
                         function() {
                             // Setting the destination and lock it
                             var folderSelector = addContentItemGeneralPane.folderSelector;
                             folderSelector.setRoot(parentFolder, objectStore);
                             folderSelector .setDisabled(true);

                             // Property filling - Work :-)
                             addContentItemDialog.setTitle("New document from another");
                             addContentItemDialog.setIntroText("This form allow you to create a document from another one.");
                             addContentItemPropertiesPane.setPropertyValue("DocumentTitle", "Prefilled title");                            
                             // Property filling - Doesn't work :-(
                             addContentItemPropertiesPane.setPropertyValue("A_ChoiceList_Prop",
                                    [ "Value\\1", "Value\\2", "Value\\3"]);

                         }, true);
            }
        });
    });
});

Maybe I missed some magic IBM code lines to get it done.


Solution

  • Updated. 1) The code waits now correctly for the retrieval of the entry template content. 2) This answer should also work with previous versions of ICN. 3) This answer defines functions at the global scope. Be extremely careful, you could encounter a conflict name with other plugins and ICN code. Instead use callbacks function, or name "strongly" your functions.

    I followed these steps to write the Plugin Action:

    1. Retrieve the source Document and the parent Folder
    2. Invoke the Plugin Service which fetches the Properties of the clicked Document
    3. Retrieve the Entry Template and fill its default values with the fetched Properties
    4. Create an AddContentItemDialog object and display it by passing to it the Entry Template.

    An Entry Template is described by an EntryTemplate object. It has a key propertiesOptions which reference an array. Each element of this array represents a Document Property. Each element contains an key named defaultValue:

    EntryTemplate {
        addClassDescription: "An Entry Template",
        ​addClassLabel: "An Entry Template",
        ​addClassName: "AnEntryTemplate",
        ​// [...]
        propertiesOptions: [
            {
                dataType: "xs:string",
                id: "a_prop",
                name: "A Property",
                // [...]
                defaultValue: "A default value",
                // [...]
            },
            // [...]
        ],
        // [...]
    }
    

    ​​ String values are passed as (obviously) strings, Dates as ISO8601 formatted strings (yyyy-MM-ddTHH:mm:ss) and Lists as arrays.

    For example, given n1, n2, n3 propertyOption entries :

    // "xs:string" dataType
    entryTemplate.propertiesOptions[n1].defaultValue = "Duck";
    
    // "xs:timestamp" dataType
    entryTemplate.propertiesOptions[n2].defaultValue = "1938-04-15T00:00:00";
    
    // "xs:string" dataType, "LIST" cardinality
    entryTemplate.propertiesOptions[n3].defaultValue = ["Huey", "Dewey", "Louie"]; 
    

    Below the implementation of the Javascript client-side code of the PluginAction. I didn't provide the service implementation nor the code to fill the Entry Template because it's a bit off topic. (For more informations about writing an ICN plugin, you can refer to the ICN Redbook Customizing and Extending IBM Content Navigator.)

    Please note also I don't consider this answer as to be the best way to design the Action Plugin, don't hesitate to suggest optimizations/good practices related editions. I just found painfull to imbricate callbacks function, so I aimed to defined most of them at top-level, I don't like monolithic code.

    First, the main block part :

    require(["dojo/_base/declare",
             "dojo/_base/lang",
             "dojo/aspect", 
             "ecm/model/Request",
             "ecm/widget/dialog/AddContentItemDialog"], 
             function(declare, lang, aspect, Request, AddContentItemDialog) {
        /**
         * Use this function to add any global JavaScript methods your plug-in requires.
         */
    
        lang.setObject("openFilledCreateDocumentFormAction",
                function(repository, items, callback, teamspace, resultSet, parameterMap) {
    
                    // Parameters passed to the service as HttpServletRequest
    
                    var serviceParams = new Object();
                    serviceParams.server = items[0].repository.id;
                    serviceParams.serverType = items[0].repository.type;
                    serviceParams.id = items[0].id;
    
                    // Object store and parentFolder retrieving (needed below)
                    var objectStore = items[0].objectStore;
                    var parentFolder = items[0].parent;
                    var entryTemplateClassName = null;
    
                    // Service call. "FormService" fetch the source document
                    // properties, then put them as JSON in the response.
                    // The response will be passed to the function
                    // requestCompleteCallback (see below)
                    Request.invokePluginService(
                            "FormPlugin",
                            "FormService", {
                                requestParams: serviceParams,
                                // Parameters below are for response callback
                                etClassName:"AnEntryTemplate",
                                repository:repository,
                                objectStore:objectStore,
                                parentFolder:parentFolder,
                                AddContentItemDialog:AddContentItemDialog,
                                aspect:aspect,
                                requestCompleteCallback: processRetrievalResponse
                            });
                });
    });
    

    FormService calls processRetrievalResponse() when completed. In this one, we'll start by retrieving the template we want.

    function processRetrievalResponse(response) {
        // Some data passed to the parent object of this callback (see above)
        var etClassName = this.etClassName;
        var repository = this.repository;
        var objectStore = this.objectStore;
        var parentFolder = this.parentFolder;
        var AddContentItemDialog = this.AddContentItemDialog;
        var aspect = this.aspect;
    
        // First we'll retrieve all the templates
        repository.retrieveEntryTemplates(
                function (entryTemplates, document_ET_count, folder_ET_count) {
                    var entryTemplate = null;
                    // Then we'll search for the one that we want
                    for (var i = 0; i < entryTemplates.length; i++) {
                        if (entryTemplates[i] &&
                                entryTemplates[i].addClassName == etClassName) {
                            entryTemplate = entryTemplates[i];
                            break;
                        }
                    }
                    // No Entry Template = abort.
                    if (!entryTemplate) {
                        alert("The Entry Template " +
                                "\"" + etClassName + "\" " +
                                "was not found. Please contact the administrators");
                        return;
                    }
    
                    // Now we got the Entry Template, time to retrieve its content
                    // First, we design a "waiter" object.
                    // We assume here the PluginService returns the values in
                    // the "properties" entry of the object response
    
                    retrievalWaiter =
                        new RetrievalWaiter (repository,
                                             objectStore,
                                             parentFolder,
                                             entryTemplate,
                                             response.properties,
                                             AddContentItemDialog,
                                             aspect);
                    // Then a call to retrieve its content
                    entryTemplate.retrieveEntryTemplate(null, false, true);
    
                    // We ignite the waiter. When the retrieval will be performed,
                    // It will fill its default values and use it to display
                    // the creation document dialog.                    
                    retrievalWaiter.wait();
    
    
                }, "Document", parentFolder.id, null, objectStore);
    }
    

    The RetrievalWaiter code. Here, no while loop, cause it will be as consuming as disgusting. This object simply relies on setTimeOut() to to periodically check the retrieval of the Entry Template content.

    function RetrievalWaiter(repository, objectStore, parentFolder,
                             entryTemplate, properties,
                             AddContentItemDialog, aspect) {
        this.repository = repository;
        this.objectStore = objectStore;
        this.parentFolder = parentFolder;
        this.entryTemplate = entryTemplate;
        this.properties = properties;
        this.aspect = aspect;
        this.wait = 
            function() {
                // If the Entry Template is not yet loaded, wait 500 ms
                // and recheck
                if (!this.entryTemplate.isRetrieved) {
                    var _this = this;
                    setTimeout(function() {_this.wait();}, 500);                    
                    return;
                }
                // Fill the Entry Template with defaults value
                // (got from the PluginServer response, see above)
                fillEntryTemplate(this.entryTemplate, this.properties);
    
                // Show the document creation dialog with the 
                showDialog(AddContentItemDialog,
                        this.aspect,
                        this.repository, this.objectStore,
                        this.parentFolder, this.entryTemplate);
            }
    }
    

    Now, time to show the dialog.

    function showDialog(AddContentItemDialog, aspect,
                        repository, objectStore,
                        parentFolder,
                        entryTemplate) {
        var addContentItemDialog  = new AddContentItemDialog();
        var addContentItemPropertiesPane =
            addContentItemDialog.addContentItemPropertiesPane;
        var addContentItemGeneralPane =
            addContentItemDialog.addContentItemGeneralPane;
    
        addContentItemDialog.show(
                repository,
                parentFolder,  // parent folder 
                true,          // document being added
                false,         // not virtual 
                null,          // no callback function 
                null,          // no teamspace 
                true,          // Use an Entry Template 
                entryTemplate, // Entry template 
                true           // don't allow choosing directory
                               // from another repository
        );
    
        // Use aspect to set the value *after* the complete rendering
        // of the properties pane   
        aspect.after(
                addContentItemPropertiesPane,
                "onCompleteRendering",
                function() {
                    addContentItemDialog.setTitle("Duplicate a document");
                    addContentItemDialog.setIntroText(
                            "This form is loaded from a right-clicked document.");
                    // Removing the help link to make the form looks like
                    // the document creation one
                    addContentItemDialog.setIntroTextRef("","");
                    // Set parent folder and prevent it from being edited.
                    addContentItemGeneralPane.folderSelector.setRoot(parentFolder, objectStore);
                    addContentItemGeneralPane.folderSelector.setDisabled(true);
    
                }, true);
    }
    
    // This function relies on the PluginService response.
    // It takes the Entry Template, a JSON formatted response
    function fillEntryTemplate(entryTemplate, jsonResponse) {
        // My mission ends here. :-)
    }