Adding image in master page (SharePoint apps model)

When I developing SharePoint apps, I prefer to use my custom master page, as the idea is to create responsive design. One of the issues I had was to how to add a logo or an image in the master page. The solution is still classic asp.net control called image.

<asp:Image runat="server" ImageUrl="../../Images/sp_logo.png" />

You can apply style on the image by setting CssClass property of the control, as well as you can use all other properties. See full documentation for this tag here: http:// https://docs.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.image?view=netframework-4.7.2

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.

Image gallery from attachments in tooltips

Do you want to obtain a small image gallery display in a tooltip based on a files attached to an item?

Not too complicated to obtain this. We just need JavaScript tooltipster and DataTable library and CSS files. But first let’s build the code for getting attachments based on item id.

Type.registerNamespace('Shp');

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

 Shp.Lists.GetAttachments = function (listName, itemId, webUrl, success, fail) {
            /// <summary>Get attachments for an item</summary>
            /// <param name="listName" type="String" optional="false" mayBeNull="false">List name</param>
            /// <param name="itemId" type="Number" integer="true" optional="false" mayBeNull="false">List item</param>
            /// <param name="success" type="Function" optional="false" mayBeNull="false">Success</param>
            /// <param name="fail" type="Function" optional="true" mayBeNull="false">Fail</param>
            var e = Function.validateParameters(arguments, [{ name: 'listName', type: String, mayBeNull: false, optional: false },
                                                            { name: 'itemId', type: Number, integer: true, mayBeNull: false, optional: false },
                                                            { name: 'webUrl', type: String, mayBeNull: true, optional: false },
                                                            { name: 'success', type: Function, mayBeNull: false, optional: false },
                                                            { name: 'fail', type: Function, mayBeNull: false, optional: true }], true);
            if (e) throw 'Shp.Lists.GetAttachments was called with invalid parameters. ' + e;
            var fail = fail || (function (err) { alert(err); });
            var ctx = (webUrl === null) ? SP.ClientContext.get_current() : new SP.ClientContext(webUrl);

            Shp.Lists._GetAttachments(listName, itemId, ctx, success, fail);
        };

     Shp.Lists._GetAttachments = function (listName, itemId, ctx, success, fail) {
            var oList = ctx.get_web().get_lists().getByTitle(listName);
            var oListItem = oList.getItemById(itemId);
            var files = oListItem.get_attachmentFiles();
            ctx.load(files);

            ctx.executeQueryAsync(function () {
                var results = [];
                for (var i = 0; i < files.get_count() ; i++) {
                    var file = files.itemAt(i);
                    results.push({ 'fileName': file.get_fileName(), 'serverRelativeUrl': file.get_serverRelativeUrl() });
                }
                success(results);
            }, function (sender, args) {
                fail(args.get_message());
            });
        };

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

Now, let have a look in XSLT code. We need it to display the data.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" xmlns:SharePoint="Microsoft.SharePoint.WebControls">
  
    <xsl:output method="html" indent="yes"/>
    <xsl:template match="/">


      <div class="card-body">
          <table class="table table-striped table-bordered nowrap" id="dataTable" width="100%" cellspacing="0">
            <thead>
              <tr>
                <th>ID</th>
                <th>Product<br />Name</th>
                <th>Product<br />Code</th>
                <th>Description</th>
                <th>Country</th>
                <th>Supplier</th>
                <th>Available<br />Qty</th>
                <th>Currency</th>
                <th>Price</th>
              </tr>
            </thead>
            <tbody>
              <xsl:for-each select="/dsQueryResponse/Rows/Row">
                <tr>
                  <td>
                    <a title="Loading..." class="product-images" data-id="{@ID}">
                      <xsl:value-of select="@ID" disable-output-escaping="yes" />
                    </a>
                  </td>
                  <td>
                    <xsl:value-of select="@ProductName" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@ProductCode" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@ProductDescription" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@CountryCode" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@Supplier" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@Qty" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@Currency" disable-output-escaping="yes" />
                  </td>
                  <td>
                    <xsl:value-of select="@Price" disable-output-escaping="yes" />
                  </td>
                </tr>
              </xsl:for-each>
            </tbody>
          </table>

      </div>
         
    </xsl:template>
</xsl:stylesheet>

Our scope is to display a small image gallery in a toolip when we click in a link. So, for this, we need to run the following code when page loads.

jQuery('#dataTable').DataTable({
        scrollX: true,
        paging: false,
        fixedColumns: {
            leftColumns: 1
        },
        'aoColumnDefs': [
            { "bSortable": false, "aTargets": [0] }
        ],

        'drawCallback': function (settings) {
            jQuery('a[class*="product-images"]').tooltipster({
                trigger: 'click',
                contentAsHTML: true,
                interactive: true,
                theme: 'tooltipster-shadow',
                functionBefore: function (instance, helper) {
                    var $origin = jQuery(helper.origin);
                    var poId = $origin.attr('data-id');
                    if (Boolean($origin.data('loaded')) !== true) {
                        Shp.Lists.GetAttachments('Products list name', parseInt(poId), null, function (attachments) {
                            if (attachments.length === 0) {
                                instance.content('No image found');
                                $origin.data('loaded', true);
                                return;
                            }

                            var html = '';
                            for (var k = 0; k < attachments.length; k++) {
                                html += '<div class="mySlides">' +
                                    '<img src="' + attachments[k].serverRelativeUrl + '" style="width:100px; height: 100px">' +
                                    '</div>';
                            }
                            instance.content('<div class="slideshow-container">' + html + '</div>');

                        },
                            function (err) {
                                instance.content('Cannot get images:' + err);
                                $origin.data('loaded', true);
                            });
                    }
                }
            });
        }
    });

Of course we can apply different style on the gallery, but this is not the scope here. This is just to give you a start from where to start.

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

SharePoint survey and incomplete results

Is very easy to create SharePoint survey by the end users. Normally it covers all requirements, but there is only one minus. Very often, users click “Save and close” button instead of the “Next”, which creates incomplete results. Out of the box, you are allowed to see only complete results and only incomplete results created by you. That’s why I think is a very good idea to hide “Save and close” button and force users to complete all the questions. Definitely we can have some help from JavaScript.

function pageLoad() {
    var buttons = document.querySelectorAll('input[type="button"]');
    for(var j = 0; j < buttons.length; j++) {
        var button = buttons[j];
        if(button.value === 'Save and Close') {
            button.style.display = 'none';
        }
    }
}

Place this code in a text file, upload it on the server and display the content using a content editor web part you place on the page. Because you include everything in “pageLoad” function it will be executed when page is loading or after a postback.

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
});

Adding Chrome to browser identification list for Microsoft Ajax

Microsoft Ajax library can identify browser type and version for Firefox, Internet Explorer and Safari. Chrome is missing from this list and it seems Microsoft added Chrome to “init.js” but did not extend Microsoft Ajax. However, as Microsoft Ajax is not limited to SharePoint, you can add Chrome by adding the following code in another JavaScript file (be sure you load the file after Microsoft Ajax is loaded).

(function () {

    function execute() {
        Sys.Browser.Chrome = {};
        var a = navigator.userAgent.toLowerCase();
        if (a.toLowerCase().indexOf("chrome") != -1) {
            Sys.Browser.agent = Sys.Browser.Chrome;
            Sys.Browser.version = parseInt(a.substring(a.indexOf("chrome/") + 7));
        }
    }

    if (typeof (Sys) !== "undefined" && typeof (Sys.Browser) !== "undefined") {
        execute();
    }

})();

And now you can easily identify is browser is Chrome and what version is used.

// Check if browser is Chrome
if (Sys.Browser.agent === Sys.Browser.Chrome) {
    // Alert current Chrome version
    alert(Sys.Browser.agent);
}