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. 🙂

Advertisements

Calculate working hours between two days with JavaScript

Calculating working hours between two days with JavaScript is a regular requirement in large companies. In general, managers want to measure clean performance of the employees, excluding weekends and what is outside the business hours (in my case 9 AM to 6 PM). The problem appears when you cannot use C# or Java or any other language of this type and you are limited to JavaScript. JavaScript cannot offer an elegant solution, but is still offering the functionality.

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

function getBusinessHoursDiff(start, end) {
    /// <summary>Calculate business hours between two dates</summary>
    /// <param name="start" type="Date" maybNeNull="false" optional="false">Start date</param>
    /// <param name="end" type="Date" maybNeNull="false" optional="false">End date</param>
    var e = Function.validateParameters(arguments, [{ name: 'start', type: Date, maybNeNull: false, optional: false },
                                                    { name: 'end', type: Date, maybNeNull: false, optional: false }, ], true);

    // Passing incorrect parameters type will result in returning an empty string, you can change the logic as you wish here
    if (e) return '';

    // Define variables and clone the provides date, we do not want to modify them outside as are passed as reference
    var startDate = new Date(startDate.valueOf());
    var endDate = new Date(endDate.valueOf());
    var count = 0;

    for (var i = start.valueOf() ; i < end.valueOf() ; i = (start.setMinutes(start.getMinutes() + 1)).valueOf()) {
        if (start.getDay() != 0 && start.getDay() != 6 && start.getHours() >= 9 && start.getHours() < 18) {
            count++;
        }
    }

    return count / 60;
}

Function is simple, even to be honest I did find it after almost two days. Is also not perfect when it comes to performance as is calculating the different till seconds. Still, if you do not want to be so exact, you can calculate till hours level (setMinutes method with set setHours). I am not sure how suitable is this solution for you, but is for sure something you can start from.

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.

Accessing some SharePoint properties from JavaScript

We know now Microsoft is pushing the SharePoint development to client side as much as possible. This means developers should use JavaScript more instead of server side code where is possible and Microsoft made this easier by exposing some SharePoint properties to client side code. There is a JavaScript object called _spPageContextInfo which contains some properties you might use in your code:

  • webServerRelativeUrl
  • webAbsoluteUrl
  • siteAbsoluteUrl
  • serverRequestPath
  • layoutsUrl
  • webTitle
  • webTemplate
  • tenantAppVersion
  • isAppWeb
  • webLogoUrl
  • webLanguage
  • currentLanguage
  • currentUICultureName
  • currentCultureName
  • clientServerTimeDelta
  • siteClientTag
  • crossDomainPhotosEnabled
  • webUIVersion
  • webPermMasks
  • pageListId
  • pageItemId
  • pagePersonalizationScope
  • userId
  • systemUserKey
  • alertsEnabled
  • siteServerRelativeUrl
  • allowSilverlightPrompt

List is long and you can figure out yourself how to use them. Probably the most noticeable property is userId. I remember when people moved to SharePoint 2013, a lot of developers were upset they could not use any more _spUserId. That’s probably they did not know it was replaced with _spPageContextInfo.userId. I would say, Microsoft did not communicate enough new functionalities, but this doesn’t mean they did not offer something to replace old functionality.

Send list items to recycle bin with JavaScript CSOM

Delete operations examples provided by Microsoft include how to delete list items using JavaScript CSOM. And everything is fine, with a small exception. “deleteObject” operation is completely deleting items and you cannot restore them from recycle bin. But instead you can use “recycle” method of SP.ListItem object. My article will show how to sent multiple items to recycle bin using JavaScript CSOM.

As usual, I am using Microsoft Ajax to organize my code in namespaces and classes:

/// <Reference Name="MicrosoftAjax.js" />
/// <Reference Path="jquery.js" />
Type.registerNamespace('Shp');

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

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

I will have a static class called “Lists” inside “Shp” namespace and with some help from jQuery (I will need it for deferred object) I will add my methods to complete the operations. In my case method will be called “RecycleItems”.

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

Type.registerNamespace('Shp');


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

Shp.Lists.RecycleItems = function (listName, listItems, web) {
    ///	 <summary>Recycle items from the list</summary>
    ///  <param name="listName" type="String" optional="false" mayBeNull="false">List name</param>
    ///  <param name="listItems" type="Array" elementType="Number" elementInteger="true" elementMayBeNull="false" optional="false" mayBeNull="false">Array with string representation of list items id</param>
    ///  <param name="web" type="SP.Web" optional="true" mayBeNull="false">Web</param>
    ///  <returns type="jQuery.deffered" />
    var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                   { name: 'listItems', type: Array, elementType: Number, elementInteger: true, elementMayBeNull: false, optional: false, mayBeNull: false },
                                                   { name: 'web', type: SP.Web, optional: true, mayBeNull: false }], true);
    if (e) throw e;

    // Depending if I provided web parameter, I build internal variable to call internal method
    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;

    // Call and return internal method result, which is a jQuery.deffered
    return Shp.Lists._DefferedRecycleItems(listName, listItems, ctx, web);

}

Shp.Lists._DefferedRecycleItems = function (listName, listItems, ctx, web) {
    // Implementation will go here
}

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

My method is validating parameters types and called an internal method if parameters are in expected format. Last parameter, which is SP.Web, is optional. If you do not provide it, code will use website that is associated with the client context. And entire implementation will go into my internal method.

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

Type.registerNamespace('Shp');


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

Shp.Lists.RecycleItems = function (listName, listItems, web) {
    ///	 <summary>Recycle items from the list</summary>
    ///  <param name="listName" type="String" optional="false" mayBeNull="false">List name</param>
    ///  <param name="listItems" type="Array" elementType="Number" elementInteger="true" elementMayBeNull="false" optional="false" mayBeNull="false">Array with string representation of list items id</param>
    ///  <param name="web" type="SP.Web" optional="true" mayBeNull="false">Web</param>
    ///  <returns type="jQuery.deffered" />
    var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, optional: false, mayBeNull: false },
                                                   { name: 'listItems', type: Array, elementType: Number, elementInteger: true, elementMayBeNull: false, optional: false, mayBeNull: false },
                                                   { name: 'web', type: SP.Web, optional: true, mayBeNull: false }], true);
    if (e) throw e;

    // Depending if I provided web parameter, I build internal variable to call internal method
    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;

    // Call and return internal method result, which is a jQuery.deffered
    return Shp.Lists._DefferedRecycleItems(listName, listItems, ctx, web);

}

Shp.Lists._DefferedRecycleItems = function (listName, listItems, ctx, web) {

    var deferred = jQuery.Deferred();
    var oList = web.get_lists().getByTitle(listName);
    var recycledItems = [];

    // For each provided list item id we perform recycle method
    for (var i = 0; i < listItems.length; i++) {
        var oListItem = oList.getItemById(listItems[i]);
        var recycleItem = oListItem.recycle();
        recycledItems.push(recycleItem);
    }

    ctx.executeQueryAsync(function () {

        // In case of success, we refine the results into an array of GUID and use it with deferred.resolve
        Array.forEach(recycledItems, function (element, index, array) {
            array[index] = element['m_value']['_m_guidString$p$0'];
        }, null);

        deferred.resolve(recycledItems);

    }, function (sender, args) {

        // In case of fail, we use error message with deferred.reject
        deferred.reject(args.get_message());

    });

    return deferred.promise();

}

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

In my development tasks, code above provided an easy way to remove items from a list, being also capable to restore them. Without entering into details, I can show a basic example.

// Array containing ID of the list items to be sent to recycle bin
var deleteIds = ["1", "2", "3", "6"];

// Define delete operation
var deleteOps = Shp.Lists.RecycleItems('List Name', deleteIds);

// In case of fail we show error message
deleteOps.fail(function (error) {
    alert(error)
});

// In case of success, we show  identifier (GUID type) of the new recycle bin item
deleteOps.done(function (listItemsGuid) {
    for(var i = 0; i < listItemsGuid.length; i++)    {
        alert(listItemsGuid[i]);
    }
});

Hoping my code will help you, I will let you know continue your work. And thank you for ready my post. 🙂

Adding multiple items to a list in a single operation

For each operation you perform on SharePoint list using JavaScript client object model, client context is required to execute query asynchronous operation. But this doesn’t mean is an equal sign between what I have just called it operation and, as an example, each item you want to add to SharePoint list. You can add multiple items to the list and execute client context operation only once.

As in general I like JavaScript organized code, I have created a small file, presented below, which for the moment requires only jQuery to be loaded. There is a plan to extend it and integrate it into a bigger project, but for this example is sufficient to copy the code below only.

/// <Reference Name="MicrosoftAjax.js" />
/// <Reference Path="../jQuery/jquery.js" />



(function () {

    var scriptName = 'SharePoint';


    function execute() {

        Type.registerNamespace('Sys.SharePoint');
        

        Sys.SharePoint.Data = function () {
            /// <summary>Sys.SharePoint.Data static class</summary>
            throw 'Cannot initialize Sys.SharePoint.Data static class';
        }        


        Sys.SharePoint.Data.AddItems = function (listName, listItems, web, success, fail) {
            /// <signature>
            ///     <summary>Add items to the list</summary>
            ///     <param name="listName" type="String" mayBeNull="false" optional="false">List name</param>
            ///     <param name="listItems" type="Array" mayBeNull="false" optional="false" elementType="Object" elementMayBeNull="false">List items</param>
            ///     <param type="SP.Web" mayBeNull="false" optional="true">Web</param>
            /// </signature>
            /// <signature>
            ///     <summary>Add items to the list</summary>
            ///     <param name="listName" type="String" mayBeNull="false" optional="false">List name</param>
            ///     <param name="listItems" type="Array" mayBeNull="false" optional="false" elementType="Object" elementMayBeNull="false">List items</param>
            ///     <param name="web" type="SP.Web" mayBeNull="true" optional="false">Web</param>
            ///     <param name="success" type="Function" mayBeNull="false" optional="false">Success</param>
            ///     <param name="fail" type="Function" mayBeNull="false" optional="true">Fail</param>
            /// </signature>
            var e1 = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
                                                             { name: 'listItems', type: Array, mayBeNull: false, optional: false, elementType: Object, elementMayBeNull: false },
                                                             { name: 'web', type: SP.Web, mayBeNull: false, optional: true }], true);
            var e2 = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
                                                             { name: 'listItems', type: Array, mayBeNull: false, optional: false, elementType: Object, elementMayBeNull: 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 (e1 !== null && e2 !== null)
                throw 'Invalid parameters calling AddItems';

            // Define context and web objects for operations
            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;

            // Use jQuery deffered for operation
            if (e1 === null && e2 !== null) {
                return Sys.SharePoint.Data._defferedAddItems(listName, listItems, ctx, web);
            }

            // Choose success and fail methods to be executed when operation is ready
            if (e1 !== null && e2 === null) {
                var fail = fail || function (err) { alert(err); };
                Sys.SharePoint.Data._nonDefferedAddItems(listName, listItems, ctx, web, success, fail);
            }

            return null;
        }


        Sys.SharePoint.Data._defferedAddItems = function (listName, listItems, ctx, web) {
            /// <summary>Add items to the list using jQuery deffered (internal use)</summary>
            /// <param name="listName" type="String" mayBeNull="false" optional="false">List name</param>
            /// <param name="listItems" type="Array" mayBeNull="false" optional="false" elementType="Object" elementMayBeNull="false">List items</param>
            /// <param name="ctx" type="SP.ClientContext" mayBeNull="false" optional="false">Context</param>
            /// <param name="web" type="SP.Web" mayBeNull="false" optional="false">Web</param>

            var oList = web.get_lists().getByTitle(listName);
            var deffered = jQuery.Deferred();
            var results = [];


            // for each item of in list items we create item in SharePoint list
            for (var i = 0; i < listItems.length; i++) {

                var listItem = listItems[i];
                var listItemCreationInfo = new SP.ListItemCreationInformation();
                var oListItem = oList.addItem(listItemCreationInfo);

                // for each property of current item, we assign field values
                for (var field in listItem) {
                    oListItem.set_item(field, listItem[field]);
                }

                // update and load SharePoint list item and add it to results array
                oListItem.update();
                ctx.load(oListItem);
                results.push(oListItem);
            }


            // context will execute query async
            ctx.executeQueryAsync(function () {
                deffered.resolve(results);
            }, function (sender, args) {
                deffered.reject(args.get_message());
            });

            return deffered.promise();
        }

        Sys.SharePoint.Data._nonDefferedAddItems = function (listName, listItems, ctx, web, success, fail) {
            /// <summary>Add items to the list (internal use)</summary>
            /// <param name="listName" type="String" mayBeNull="false" optional="false">List name</param>
            /// <param name="listItems" type="Array" mayBeNull="false" optional="false" elementType="Object" elementMayBeNull="false">List items</param>
            /// <param name="ctx" type="SP.ClientContext" mayBeNull="false" optional="true">Context</param>
            /// <param name="web" type="SP.Web" mayBeNull="false" optional="false">Web</param>
            /// <param name="success" type="Function" mayBeNull="false" optional="false">Success</param>   
            /// <param name="fail" type="Function" mayBeNull="false" optional="false">Fail</param>   

            var oList = web.get_lists().getByTitle(listName);
            var results = [];


            // for each item of in list items we create item in SharePoint list
            for (var i = 0; i < listItems.length; i++) {

                var listItem = listItems[i];
                var listItemCreationInfo = new SP.ListItemCreationInformation();
                var oListItem = oList.addItem(listItemCreationInfo);

                // for each property of current item, we assign field values
                for (var field in listItem) {
                    oListItem.set_item(field, listItem[field]);
                }

                // update and load SharePoint list item and add it to results array
                oListItem.update();
                ctx.load(oListItem);
                results.push(oListItem);
            }

            // context will execute query async
            ctx.executeQueryAsync(function () {
                success(results);
            }, function (sender, args) {
                fail(args.get_message());
            });
        }


        Sys.SharePoint.Data.registerClass('Sys.SharePoint.Data');

    }

    

    if (window.Sys && Sys.loader) {
        Sys.loader.registerScript(scriptName, null, execute);
    }
    else {
        execute();
    }


})();

Deffered operation

Let’s take a look at the following example.

	var items = [];
	items.push({ Title: 'Title 1' });
	items.push({ Title: 'Title 2' });
	items.push({ Title: 'Title 3' });
	
	var addItems = Sys.SharePoint.Data.AddItems('List Name', items);
	
	addItems.fail(function(error) {
		alert(fail);
	});
	
	addItems.done(function(results) {
		alert('Success');
	});

You can notice I have used an object for each item I wanted to create and properties have the same name as internal fields. I have add them to an array and pass it as a parameter to Sys.SharePoint.Data.AddItems method. And instead of using classic example, I have used jQuery deffered object to write code easier.

If web parameter is not specified, the web for current context is used. However, it can be specified as additional parameter.

	var context = new SP.ClientContext("https://mydomain.com/sites/toolkit");
	var web = context.get_web();
	
	
	var items = [];
	items.push({ Title: 'Title 13' });
	items.push({ Title: 'Title 12' });
	items.push({ Title: 'Title 10' });
	
	var addItems = Sys.SharePoint.Data.AddItems('List Name', items, web);
	
	addItems.fail(function(error) {
		alert(fail);
	});
	
	addItems.done(function(results) {
		alert('Success');
	});

Classic way

However, you may prefer to use the classic way and pass success and fail callback functions as parameters.

	var context = new SP.ClientContext("https://mydomain.com/sites/toolkit");
	var web = context.get_web();
	
	
	var success = function(results) {
		alert('Operation completed and insert ' + results.length + ' items');
		alert('First item title is ' + results[0].get_item('Title'));
		
	}
	
	
	var fail = function(error) {
		alert(error);
	}
	
	
	var items = [];
	items.push({ Title: 'Title 100' });
	items.push({ Title: 'Title 101' });
	items.push({ Title: 'Title 102' });
	
	Sys.SharePoint.Data.AddItems('List Name', items, web, success, fail);

Conclusion

Sys.SharePoint.Data.AddItems is a method with two signatures. If you want to use jQuery.Deffered object to work with results, you need pass these parameters

  • listName as string and mandatory
  • listItems as array of objects (not null and not optional)
  • web as SP.Web (not null but optional). If not specified, it is considered to be web for current client context.

The second signature accepts five parameters.

  • listName as string and mandatory
  • listItems as array of objects (not null and not optional)
  • web as SP.Web (can be null, but not optional). If not specified, it is considered to be web for current client context.
  • success as function (not null and not optional). It should accept as parameter an array containing created items.
  • fail as function (not null, but optional). It should accept as a parameter a string representing error message.

Serialize list items from lists.asmx

Web service list.asmx is deprecated, but somehow it seems to be the only way to obtain the desired results. For example, I am not aware of any other way to expand recurrence from an event list using client side code. So, in case you are required to use it, here is how you can obtain an array of objects from XML sent back by the web service.

function parseResponse(xmlResponse) {
    /// <summary>Obtain an array of objects from server responseXML</summary>
    /// <param name="xmlResponse"></param>
    /// <returns type="Array"></returns>


    var items = new Array();
    var rows = (typeof (xmlResponse.getElementsByTagNameNS) === 'undefined') ? xmlResponse.getElementsByTagName("z:row") : xmlResponse.getElementsByTagNameNS("*", "row");

    for (var i = 0; i < rows.length; i++) {
        var row = rows.item(i);
        var item = new Object();
        for (var j = 0; j < row.attributes.length; j++) {
            var currentNodeName = row.attributes[j].nodeName.replace('ows_', '');
            var currentNodeValue = row.attributes[j].nodeValue;
            if ((currentNodeValue == undefined) || (currentNodeValue == null)) {
                item[currentNodeName] = '';
            }
            else {
                item[currentNodeName] = currentNodeValue;
            }
        }
        items.push(item);
    }
    return items;
}

The function worked for me in Chrome, IE11 and Firefox. However, it seems “nodeValue” is a deprecated property, is still working, but it should be replaced with “value” property to avoid possible future problems.