Get data from multiple lists using Promise.all

I always had this question in mind, how can I optimize data operations using JavaScript CSOM. With some help from Promise object, this is possible. Below, I have written a small portion of code for getting data from lists and returning a promise object.

class $SPData {

    constructor() {
        throw '$SPList is a static class and cannot be instantiated';
    }

    /**
     * Get list items
     * @param {string} listName
     * @param {string} camlQuery
     * @param {string} webUrl
     */
    static GetListItems(listName, caml, webUrl) {
        let e = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
        { name: 'caml', type: String, mayBeNull: false, optional: false },
        { name: 'webUrl', type: String, mayBeNull: false, optional: true }], true);
        if (e) throw e;

        let promise = new Promise(function (resolve, reject) {
            let ctx = (arguments.length === 2) ? SP.ClientContext.get_current() : new SP.ClientContext(webUrl);
            let oList = ctx.get_web().get_lists().getByTitle(listName);
            let camlQuery = new SP.CamlQuery();
            camlQuery.set_viewXml(caml);
            let oListItems = oList.getItems(camlQuery);
            ctx.load(oListItems);
            ctx.executeQueryAsync(function () {
                resolve(oListItems);
            }, function (sender, args) {
                reject(args.get_message());
            });
        });
        return promise;
    }
}

Now, let’s see how you can use this in your code.

let getItemsFromListOne = $SPData.GetListItems('List One', '<View&gt;</View&gt;');
let getItemsFromListTwo = $SPData.GetListItems('List Two', '<View&gt;</View&gt;');
Promise.all([getItemsFromListOne, getItemsFromListTwo]).then(function (values) {
    let itemsFromListOne = values[0];
    let itemsFromListTwo = values[1];
});

This should simplify the way you are working with SharePoint data. You do not need to have wait for a call to finish to start another one. I have tested only to get data from the lists, but it can used also to insert and update list items also.

Advertisements

Create folder with properties using JavaScript CSOM

JavaScript CSOM is already on the market for a while. Introduces on version 2010, it can cover today a lot of scenarios. One of them is to create a folder in a document library with some other associated fields populated. This was one of my recent tasks. Code below should to do everything for you:

Type.registerNamespace('Shp');
        
        
        Shp.DocLib = function() {
            throw 'Cannot initiate Shp.DocLib static class';        
        }
        
        Shp.DocLib.createFolder = function(listName, folderName, fields, webUrl, success, fail) {
            var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                            { name: 'folderName', type: String, optional: false, mayBeNull: false },
                                                            { name: 'fields', type: Object, optional: false, mayBeNull: false },
                                                            { name: 'webUrl', optional: false, mayBeNull: true },
                                                            { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                            { name: 'fail', type: Function, optional: true, mayBeNull: false }], true);
           if(e) throw e;

   		  var fail = fail || function (err) { alert(err); };
          var ctx = (webUrl === null) ? SP.ClientContext.get_current() : new SP.ClientContext(webUrl);          
          Shp.DocLib._createFolder(listName, folderName, fields, ctx, success, fail);
        }
        
        Shp.DocLib._createFolder = function(listName, folderName, fields, ctx, success, fail) {
        
        	var oLib = ctx.get_web().get_lists().getByTitle(listName); 
        	var itemCreateInfo = new SP.ListItemCreationInformation();
        	itemCreateInfo.set_underlyingObjectType(SP.FileSystemObjectType.folder); 
        	itemCreateInfo.set_leafName(folderName);
        	var oFolder = oLib.addItem(itemCreateInfo);
        	for (var field in fields) {
                if (fields.hasOwnProperty(field) === true) {
                    oFolder.set_item(field, fields[field]);
                }
            };
            
     		oFolder.update();
            ctx.load(oFolder);     
            
			ctx.executeQueryAsync(function () {
                success(oFolder);
            }, function (sender, args) {
                fail(args.get_message());

            });			  
        }
        
        Shp.DocLib.registerClass('Shp.DocLib');

In order to use, you just need to provide right types of parameters, as a validation is performed.

Shp.DocLib.createFolder("List Name", "Folder Name", { "Field1": "some value", "Field2": "some value" }, "path to sharepoint",
    function (folder) {
        // Do something with the created folder
    }, function (err) {
        // Do something with the error message
    });

Web URL parameter can be null, if you want to use the client context for current web. Also, if you do not provide an error callback method, a simple alert with an error is displayed.

Attaching a file to a list item in SharePoint add-in model

Long time since I wrote about the SharePoint. Is still my job, I am still working on this, but I simply did not find the time to discover new things. Now, as SharePoint add-in model is more and more adopted in SharePoint development, due to the migration of large companies to Office 365, I am starting to face new challenges. One of this is how to attach a file to an existing list item.

<input type="file" id="attachment1" />

As HTML5 is the new standard for web, file control is able to read the content of a file as a binary string, so no more needed to server side languages. For rest, jQuery and SharePoint JavaScript libraries will do the work for you.


       Type.registerNamespace('Shp');

       Shp.Attachments = function () {
            /// <summary>Shp Attachments static class</summary>
            throw 'Cannot initiate Shp.Attachments static class';
        };

        Shp.Attachments.get_file = function (fileInput) {
            /// <summary>This method is used to get the content of the file as binary string</summmary>

            var deffered = jQuery.Deferred();
            var reader = new FileReader();
            reader.onload = function (e) { 
                deffered.resolve(e.target.result);
            };  

            reader.onerror = function (e) {
                deffered.reject(e.target.error);
            };

            reader.readAsBinaryString(fileInput.files[0]);
            return deffered.promise();
        };


        Shp.Attachments.add = function (listName, itemId, fileInput, webUrl, success, fail) {
            /// <summary>Add attachments</summary>
            /// <param>List name</param>
            /// <param>Item Id</param>
            /// <param>File input controls</param>
            /// <param>Web url</param>
            var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                            { name: 'itemId', type: String, optional: false, mayBeNull: false },
                                                            { name: 'filesInput', type: HTMLElement, optional: false, mayBeNull: false },
                                                            { name: 'webUrl', type: String, optional: false, mayBeNull: true },
                                                            { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                            { name: 'fail', type: Function, optional: true, mayBeNull: false }], true);
            if (e) throw e;


            var webUrl = webUrl || _spPageContextInfo.webAbsoluteUrl;
            var fail = fail || function (err) { alert(err); };

            Shp.Attachments.get_file(fileInput).then(function (fileContent) {
                var parts = fileInput.value.split("\\");
                var fileName = parts[parts.length - 1];
                // Attachments add internal method
                Shp.Attachments._add(listName, itemId, fileContent, fileName, webUrl, success, fail);
            });
        };

        Shp.Attachments._add = function (listName, itemId, fileContent, fileName, webUrl, success, fail) {

            var scriptBase = webUrl + "/_layouts/15/";
            jQuery.getScript(scriptBase + "SP.RequestExecutor.js", function () {

                var executor = new SP.RequestExecutor(webUrl);
                executor.executeAsync({
                    url: webUrl + "/_api/web/lists/GetByTitle('" + listName + "')/items(" + itemId + ")/AttachmentFiles/add(FileName='" + fileName + "')",
                    method: "POST",
                    binaryStringRequestBody: true,
                    body: fileContent,
                    state: "Update",
                    success: function () {
                        success(itemId);
                    },
                    fail: function (data) {
                        fail(data.responseText);
                    }

                });

            });
        };

        Shp.Attachments.registerClass('Shp.Attachments');

Once code is organized, attaching a file become an easy task.

 Shp.Attachments.add('Tickets', '2', document.getElementById('attachment1'), null, function (itemId) {
        alert(itemId);
    }, function (err) {
        alert('Error: ' + err);
    });

Shp.Attachments.add accepts following parameters:

  • List name as string
  • Item id as string
  • HTML file control as HTML element
  • Web URL. It can be nul and in this case is used _spPageContextInfo.webAbsoluteUrl
  • Success method to be executed if attachment is added
  • Fail method to be executed if attachment failed

Copy, adapt the code according to your needs and use it. Happy coding! If it helps, you can just say hi to me and I will be happy as well. 🙂

Adding items in multiple lists using JavaScript CSOM

In general my ideas for my posts are generated by problems I am facing in my development activity. One of the problems working JavaScript CSOM is sometimes projects require very complex data operations, like adding items into multiple SharePoint lists. I would not call this a challenge, as you always can use classical examples and perform insert operations one by one, waiting for previous operation to be completed before starting a new one. But why not make it easier?

If you want to achieve this in a simple way, just take a look on the code below.

Type.registerNamespace('Shp');


Shp.Lists.AddItemsInLists = function (addItemsArguments, webUrl, success, fail) {
    /// <summary>Add items in multiple lists calling execute query async once</summary>
    /// <param name="addItemsArguments" type="Array" elementType="Object" elementMayBeNull="false" mayBeNull="false" optional="false">Add items arguments</param>
    /// <param name="webUrl" type="String" mayBeNull="true" optional="false">Web url</param>
    /// <param name="success" type="Function" mayBeNull="false" optional="false">Success</param>
    /// <param name="fail" type="Function" mayBeNull="false" optional="true">Fail</param>
    var e = Function.validateParameters(arguments, [{ name: 'addItemsArguments', type: Array, elementType: Object, elementMayBeNull: false, optional: false, mayBeNull: false },
                                                    { name: 'webUrl', type: String, optional: false, mayBeNull: true },
                                                    { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                    { name: 'fail', type: Function, optional: true, mayBeNull: false }], true);
    if (e) throw e;
    var ctx = (typeof webUrl === 'undefined' || webUrl === null) ? SP.ClientContext.get_current() : new SP.ClientContext(webUrl);
    var fail = fail || function (err) { alert(err); };
    Shp.Lists._AddItemsInLists(addItemsArguments, ctx, success, fail);
};


Shp.Lists._AddItemsInLists = function (addItemsArguments, ctx, success, fail) {
    var results = {}, current, i, k;

    for (i = 0; i < addItemsArguments.length; i++) {

        current = addItemsArguments[i]; // Store current item

        // We check if list name property if defined before do other operations
        if (current.hasOwnProperty("listName") === true) {

            if (results.hasOwnProperty(current.listName) === false) results[current.listName] = []; // Results object doesn't have a slot for current list name, create it
            var oList = ctx.get_web().get_lists().getByTitle(current.listName);

            // We check if list items property is an array
            if (current.hasOwnProperty("listItems") === true && isArray(current.listItems) === true) {
                for (k = 0; k < current.listItems.length; k++) {
                    var listItem = current.listItems[k];
                    var oListItem = oList.addItem(new SP.ListItemCreationInformation());
                    for (var field in listItem) {
                        oListItem.set_item(field, listItem[field]);
                    }
                    oListItem.update();
                    ctx.load(oListItem);
                    results[current.listName].push(oListItem);
                }
            }
        }
    }

    ctx.executeQueryAsync(function () {
        success(results);
    }, function (sender, args) {
        fail(args.get_message());
    });

};

Shp.Lists.registerClass('Shp.Lists');

I have created a method “AddItemsInLists” for “Shp.Lists”, which is accepting 4 parameters:

  • addItemsArguments – which is an array of objects containing information about list items you want to insert.
  • webUrl – which is the URL of the web inside current site collection. If null, web for current context is considered.
  • success – function to be executed if everything goes fine.
  • fail – function to be executed if fails. It is optional and if not specified a simple alert containing the error is shown.

Let’s concentrate on “addItemsArguments” parameter. As I said, it is an array of objects, but let me explain how these objects should look like. Basically it should contain 2 properties: “listName” (name of the list where you want to insert items) and “listItems” (another array of objects, this time representing the items).

var firstListItems = { listName: 'Name of the list', listItems: [{ 'Field 1': 'Value', 'Field 2': 'Value' }, { 'Field 1': 'Value', 'Field 2': 'Value'}]  };
var secondListItems = { listName: 'Name of the 2nd list', listItems: [{ 'Field 1': 'Value', 'Field 2': 'Value' }, { 'Field 1': 'Value', 'Field 2': 'Value'}]  };
var addItemsArguments = [firstListItems, secondListItems];

Now calling the method I have created is easy and no rocket science is required.

Shp.Lists.AddItemsInLists([firstListItems, secondListItems, ], null, function (results) {
    /*  Do something with the results.
        You can get items inserted in a list like this: results['name of the list']  */
}, function (err) {
    alert(err); // Alert the error or do something elsel
});

Stop SharePoint converting dates to GMT timezone

I often used with SharePoint custom forms and in general I based my development on SharePoint JSOM. Working with it, I am often requested to update date and time fields using JSOM. But there is a small issue with it.

var dt = new Date();
item.set_item(dt);

The code above is with working, with an exception. It converts the date to GMT timezone. If my timezone is GMT + 2, recorded date will have 2 hours less then I have specified. The workaround is to convert the date into formatted string.

var dt = new Date();
item.set_item(dt.format('yyyy-MM-ddTHH:mm:ssZ'));

Date format function is provided by MicrosoftAjax, which is included by default.

Create document set with custom properties using JSOM

This is article is not about something new, is about how you organize the code to create a document set with custom properties. Let’s imagine I have created a column “Employee” (people or group field type) and associate it with Document Set content type. Of course I want to assign a value to this field when I create a new document set. What I did was to get the pieces of information from the internet and consolidate my code based on these.

Shp.DocumentSet = function () {
    throw 'Cannot instantiate Shp.DocumentSet static class';
}

Shp.DocumentSet.createDocumentSet = function (listName, docSetName, web, properties, success, fail) {
    /// <signature>
    ///	    <summary>Create document set</summary>
    /// 	<param name="listName" type="String" optional="false" mayBeNull="false">Document library name</param>
    /// 	<param name="docSetName" type="String" optional="false" mayBeNull="false">Document set name</param>
    ///     <param name="web" type="SP.Web" optional="false" mayBeNull="true">Web</param>
    ///     <param name="success" type="Function" optional="false" mayBeNull="false">Success</param>
    ///     <param name="error" type="Function" optional="true" mayBeNull="false">Fail</param>
    /// </signature>

    var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                     { name: 'docSetName', type: String, optional: false, mayBeNull: false },
                                                     { name: 'web', type: SP.Web, optional: false, mayBeNull: true },
                                                     { name: 'properties', type: Object, optional: false, mayBeNull: true },
                                                     { name: 'success', type: Function, optional: false, mayBeNull: false },
                                                     { name: 'fail', type: Function, optional: false, mayBeNull: true }], true);
    if (e) throw e;


    var ctx = (typeof web === 'undefined' || web === null) ? SP.ClientContext.get_current() : web.get_context();
    var web = (typeof web === 'undefined' || web === null) ? ctx.get_web() : web;
    var fail = fail || function (err) { alert(err); }

    Shp.DocumentSet._createDocumentSet(listName, docSetName, ctx, web, properties, success, fail);

};

Shp.DocumentSet._createDocumentSet = function (listName, docSetName, ctx, web, properties, success, fail) {

    var list = web.get_lists().getByTitle(listName);
    ctx.load(list);

    var parentFolder = list.get_rootFolder();
    ctx.load(parentFolder);



    var docSetContentTypeID = "0x0120D520";
    var docSetContentType = ctx.get_site().get_rootWeb().get_contentTypes().getById(docSetContentTypeID);
    ctx.load(docSetContentType);

    ctx.executeQueryAsync(function () {
        SP.DocumentSet.DocumentSet.create(ctx, parentFolder, docSetName, docSetContentType.get_id());
        var docSetFolder = web.getFolderByServerRelativeUrl(parentFolder.get_serverRelativeUrl() + '/' + docSetName);
        var docSetFolderItem = docSetFolder.get_listItemAllFields();
        if (properties != null) {
            for (var property in properties) {
                if (properties.hasOwnProperty(property) === true) {
                    docSetFolderItem.set_item(property, properties[property]);
                }
            }
        }
        docSetFolderItem.update();
        ctx.load(docSetFolderItem);
        ctx.executeQueryAsync(function () {
            success(docSetFolderItem);
        }, fail);
    },
    fail);

};

Shp.DocumentSet.registerClass('Shp.DocumentSet');

I have created a static class called Shp.DocumentSet with Shp.DocumentSet.createDocumentSet method, which accepts these parameters:

  • listName: document library name
  • docSetName: document set name
  • docSetName: SP.Web I want to use for current operation. If null, web for current context will be considered.
  • properties: an object containing custom properties I want to update. If null, no custom property will be updated.
  • success: function to be called on success. This will be called passing list item associated with document set folder as a parameter.
  • fail: function to be called on fail.

Let’s see how you can use it.

var docLibraryName = 'Doc library name';
var docSetName = 'Doc set name';
var spWeb = null;
var properties = {};
properties['Employee'] = employeeId;
var success = function(item) {
    // Do something with item
};

var fail = function (err) {
    // Do something with error
};

Shp.DocumentSet.createDocumentSet(docLibraryName, docSetName, spWeb, properties, success, fail);

Creating document set with custom property is now a simple a operation. I do not need write a lot of code each time I need to this.

Autocomplete textbox with JavaScript CSOM

I have searched on the internet about how to create an auto-complete functionality in SharePoint. Of course, jQuery UI was the solution with an Ajax request to REST service. For some reason, I cannot understand it, the examples I have seen are based on synchronous Ajax request. So I simply said no way. I needed something asynchronous to avoid page freeze.

Normally to create an auto-complete is a simple thing.


    jQuery('#txtBox').autocomplete({
        minLength: 3,
        source: function(request, response) {
         // At the and of the async operation call response with obtained results
         }
     });

Asynchronous operation will be placed inside source function, but instead using classic Ajax examples, I will use JavaScript CSOM. It is not better, but I like it more. So is more a personal choice.

For getting data from SharePoint, you can use classical example from MSDN website, but I prefer to reorganize the code a little bit. After all I still have Microsoft Ajax library available so I can put classes in namespaces or I can validate parameters type.

/// <Reference Name="MicrosoftAjax.js" />


Type.registerNamespace('Shp');


Shp.Lists = function () {
    throw 'Cannot instantiate Shp.Lists static class';
}


Shp.Lists.GetItems = function (listName, query, web, success, fail) {
    /// <summary>Get list items based on provided CAML query</summary>
    /// <param name="listName" type="String" optional="false" mayBeNull="false">List name</param>
    /// <param name="query" type="String" optional="false" mayBeNull="false">Query</param>
    /// <param name="web" type="SP.Web" optional="false" mayBeNull="true">Web</param>
    /// <param name="success" type="Function" optional="false" mayBeNull="false">Success callback</param>
    /// <param name="fail" type="Function" optional="true" mayBeNull="false">Fail callback</param>

    var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
                                                   { name: 'query', type: String, mayBeNull: false, optional: false },
                                                   { name: 'web', type: SP.Web, mayBeNull: true, optional: false },
                                                   { name: 'success', type: Function, mayBeNull: false, optional: false },
                                                   { name: 'fail', type: Function, mayBeNull: false, optional: true }], true);
    if (e) throw e;

    var fail = fail || function (error) { alert(error); };
    var ctx = (web === null) ? SP.ClientContext.get_current() : web.get_context();
    var web = (web === null) ? ctx.get_web() : web;


    Shp.Lists._GetItems(listName, query, ctx, web, success, fail);
}

Shp.Lists._GetItems = function (listName, query, ctx, web, success, fail) {

    var oList = web.get_lists().getByTitle(listName);
    var camlQuery = new SP.CamlQuery();
    camlQuery.set_viewXml(query);
    var oListItems = oList.getItems(camlQuery);
    ctx.load(oListItems);
    ctx.executeQueryAsync(function () {
        success(oListItems);
    }, function (sender, args) {
        fail(args.get_message());
    });
}

Shp.Lists.registerClass('Shp.Lists');

“Shp.Lists.GetItems” should be called with the following parameters:

  • List name, as string, not optional and cannot be null.
  • CAML query as string, not optional and cannot be null.
  • Web as SP.Web, not optional but can be null. In this case web associated with the current context is used.
  • Success as function, not optional and cannot be null. It is executed if operation is a success.
  • Fail function, optional and cannot be null. If not specified and operation fails, code will alert the error message.

Now as I created a reusable function for reading list items, everything should be much easier. I just need to call “Shp.Lists.GetItems” with correct parameters inside auto-complete source and, if operation is successfully, to add suggestions based on list items.

jQuery('#txtBox').autocomplete({
    minLength: 3,
    source: function (request, response) {
       
        var term = request.term;
        var query = '<View><Query><Where><Contains><FieldRef Name="Title" /><Value Type="Text">' + term + '</Value></Contains></Where></Query></View>';
        Shp.Lists.GetItems("list name", query, null, function (items) {
            var suggestions = [];
            var listItemEnumerator = items.getEnumerator();
            while (listItemEnumerator.moveNext()) {
                suggestions.push(listItemEnumerator.get_current().get_item('Title'));
            }
            // Add suggestions
            response(suggestions);

        });

    }
});

This was my approach of creating the auto-complete functionality. As I said, is a personal option to use JavaScript CSOM because looks for me more organized and structured. Of course code can be extended and you can even create an Ajax client side control to incorporate this functionality.

Thank you for reading my post!