Get list items created in current month

DateRangesOvelap might work when you are working with calendar list, but for sure is not working with custom lists. For this, you need to write your own CAML query and get the data based on it. Below I have a created a function to get the items and returns a promise object.

    function GetListItems(listName, caml) {
        let promise = new Promise(function (resolve, reject) {
            let ctx = SP.ClientContext.get_current();
            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 built the CAML query and call the function.

function getDaysInMonth(year, month) {
    // Get numbers of days in a month
    return new Date(year, month + 1, 0).getDate();
}

let currentDate = new Date();
let firstDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
let lastDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), getDaysInMonth(currentDate.getFullYear(), currentDate.getMonth()));

let caml = '<view>' +
    '<Query>' +
    '<Where>' +
    '<And>' +
    '<Geq><FieldRef Name="Created" /><Value Type="DateTime">' + firstDate.format('yyyy-MM-dd') + '</Value></Geq>' +
    '<Leq><FieldRef Name="Created" /><Value Type="DateTime">' + lastDate.format('yyyy-MM-dd') + '</Value></Leq>' +
    '</And>' +
    '</Where>' +
    '</Query>' +
    '</View > ';

GetListItems('List Name', caml).then(function (items) {
    // Do something with the items
}, function (err) {
    // Do something with error message
});

You can notice, is not complicated to achieve this using JS CSOM. Of course you can apply the filter on different date and time fields. The next article will be probably how to get the items for a specified week, as is more probably to be a goal in many projects.

Advertisements

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.

Copy current Access database with VBA

Access is still being in used by some companies, even is not perfect choice anymore. So, today I was requested to create a sort of back-up functionality for an Access database which contains linked tables to SharePoint. I have tried some methods, but all them are failed, including FileCopy method from VBA. Still VBA was solving my problem, but code was a little bit different.

Dim fDialog As Office.FileDialog
Set fDialog = Application.FileDialog(msoFileDialogSaveAs)

With fDialog

    If fDialog.Show() <&gt; 0 Then
    
        Dim Sourcefile As String
        Dim Destinationfile As String
        Sourcefile = Application.CurrentProject.Path &amp; "\" &amp; Application.CurrentProject.Name
        Destinationfile = fDialog.SelectedItems(1)
    
        With CreateObject("Scripting.FileSystemObject")
            .copyfile Sourcefile, Destinationfile
        End With
       
      
    End If
End With

Code is also opening a file save dialog and you can choose the location where you want to copy the database.

CDN issues in SharePoint apps

When I am developing SharePoint apps, in general, I create my custom master page and place it inside the application. I would not say I had too much problems with this approach, until I needed to deploy the application on a SharePoint Online site where I could not disabled CDN due to insufficient rights on tentant. JQuery loaded from CDN simply makes some Bootstrap functionality not to work anymore.

My scripts were loaded using ScriptManager control, which is the classic way to load javascript files in web forms sites. As SharePoint tried to load additional files, the salvation was again JavaScript.


function pageLoad(sender, args) {



    let scripts = document.querySelectorAll('script')
    for (let k = 0; k < scripts.length; k++) {
        if (scripts[k].src.includes('ajax.aspnetcdn.com/ajax/jquery') === true) {
            document.body.removeChild(scripts[k]);
            jQuery.getScript(_spPageContextInfo.webAbsoluteUrl + '/Scripts/Framework/jQuery/jquery.js', function () {
                jQuery.getScript(_spPageContextInfo.webAbsoluteUrl + '/Scripts/Framework/bootstrap/bootstrap.js', function () {
                    // Do something here if needed
                });
            });
        }
    }

}

Don’t judge me if the code is not optimized as I found the solution about 10 minutes ago and this article is only written to give you an idea about how to remove jQuery from CDN. You can remove of course old Bootstrap and jQuery files before adding new ones or try to find a better loader, like require.js.

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" /&gt;

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

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.