Oct 12, 2017

Note board comment notifications

It is common case to request notification for the comments someone makes in your pages through Note Board web part. Since this is not supported out-of-the-box, the goal could be fulfilled only through custom code or 3rd party solution.
Next objectives must be addressed for completing the task:
  • what will trigger the notification – time based timer job (implies WSP package on prem or Azure hosted .Net code in o365) or hitting the "Post" button in the Note Board web part (JavaScript somehow…)
  • what will notification include – I guess the bare minimum that would be required is what is the page and what the last posted comment was
  • what kind of notification is required – email or something else
The shortest path is going with JavaScript code that is triggered after the "Post" button has been hit by the end-user, which triggers email notification with the comment details to be sent out.
Here is how to achieve this:
  • subscribe to the "Post" button for the Note Board web part after the comment has been posted to the discussion history
$(".ms-socialCommentContents input[id$='_PostBtn']").click(function () {

       //todo: your custom code here

       return false;

   });
  • read with SocialDataService.asmx the last comment for this page
function GenerateDataXML (url) { 

 

       var d1 = new Date ();

       var d2 = new Date ( d1 );

       d2.setHours ( d1.getHours() - 1 );

       var d3 = d2.toISOString();;

       

       var soapXml = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +   

        "<soap:Body>" +   

        "<GetCommentsOnUrl xmlns=\"http://microsoft.com/webservices/SharePointPortalServer/SocialDataService\">" +   

            "<url>" + url + "</url>" +   

            "<maximumItemsToReturn>5</maximumItemsToReturn>" +

            "<startIndex>0</startIndex>" +

            "<excludeItemsTime>" + d3 + "</excludeItemsTime>" +

        "</GetCommentsOnUrl>" +   

        "</soap:Body>" +   

        "</soap:Envelope>" ;

        

        return soapXml;   

    }   
This example returns last 5 comments made within last hour.
Then, we can fetch the last comment made and retrieve the text and use who posted it:
$(content).find('SocialCommentDetail').each(function (index) {

            if (index == 0)

            {

                var comment = $(this);

                var owner = comment.find('Owner')[0].innerHTML;

                var text = unescape(comment.find('Comment')[0].textContent);

               

                callback(owner, text);


                return false;

            }


        });
  • then we use SP.Utilities.Utility.SendEmail to send email
Below is the source code of all actions:
function ReturnLastComment(url, callback) {   

    var dataXML = GenerateDataXML([url]); 

         


     var soapAction = "http://microsoft.com/webservices/SharePointPortalServer/SocialDataService/GetCommentsOnUrl";

     var svcUrl = window.location.protocol + "//" + window.location.host + _spPageContextInfo.webServerRelativeUrl + "/_vti_bin/SocialDataService.asmx";

     $.ajax({   

        url: svcUrl,   

        data: dataXML,  

        headers: {

            'SOAPAction': soapAction,

            'Content-Type': 'text/xml; charset=\"utf-8\"',

            'Accept': 'application/xml, text/xml, */*'

        },        

        dataType: "xml",        

        method: "POST",   

        transformRequest: null,

        success: processResult,   

        error: function (request, error) {


           console.log('error: ' + request.responseText);

        }    



     })   

   

     //Following function generates the require soap XML   

     function GenerateDataXML (url) { 

 

        var d1 = new Date ();

        var d2 = new Date ( d1 );

        d2.setHours ( d1.getHours() - 1 );

        var d3 = d2.toISOString();;

        

        var soapXml = "<?xml version='1.0' encoding='utf-8'?><soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +   

         "<soap:Body>" +   

         "<GetCommentsOnUrl xmlns=\"http://microsoft.com/webservices/SharePointPortalServer/SocialDataService\">" +   

             "<url>" + url + "</url>" +   

             "<maximumItemsToReturn>5</maximumItemsToReturn>" +

             "<startIndex>0</startIndex>" +

             "<excludeItemsTime>" + d3 + "</excludeItemsTime>" +

         "</GetCommentsOnUrl>" +   

         "</soap:Body>" +   

         "</soap:Envelope>" ;

         

         return soapXml;   

     }   

   

     function processResult(content, txtFunc, xhr) {  

         

         $(content).find('SocialCommentDetail').each(function (index) {

             if (index == 0)

             {

                 var comment = $(this);

                 var owner = comment.find('Owner')[0].innerHTML;

                 var text = unescape(comment.find('Comment')[0].textContent);

                

                 callback(owner, text);

 

                 return false;

             }

          

         });

     }  

}

 

function bindCommentsEvents()

{

    $(".ms-socialCommentContents input[id$='_PostBtn']").click(function () {

 

        var currentPageUrl = window.location.protocol + "//" + window.location.host + _spPageContextInfo.serverRequestPath;

        ReturnLastComment(currentPageUrl, function (owner, text) {

            var result = "last comment: " + owner + "; " + text;

            //alert(result);

 

            var mailBody = "<br>commented by: " + owner + "<br>" + "page: " + currentPageUrl +  "<br>comment: " + text;

            processSendEmails(mailBody)

        });

 

        return false;

    });

}

function processSendEmails(body) {

 

    var from = 'no-reply@domain',

        to = 'xxxx@domain',

        body = 'A comment has just been added to one of your pages.\n' + body,

        subject = 'New Comment';

 

    // Call sendEmail function

    //

    sendEmail(from, to, body, subject);

}

 

 

function sendEmail(from, to, body, subject) {

    //Get the relative url of the site

    var siteurl = _spPageContextInfo.webServerRelativeUrl;

    var urlTemplate = siteurl + "/_api/SP.Utilities.Utility.SendEmail";

    $.ajax({

        contentType: 'application/json',

        url: urlTemplate,

        type: "POST",

        data: JSON.stringify({

            'properties': {

                '__metadata': {

                    'type': 'SP.Utilities.EmailProperties'

                },

                'From': from,

                'To': {

                    'results': [to]

                },

                'Body': body,

                'Subject': subject

            }

        }),

        headers: {

            "Accept": "application/json;odata=verbose",

            "content-type": "application/json;odata=verbose",

            "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()

        },

        success: function(data) {


            console.log('Email Sent Successfully');

        },

        error: function(err) {


            console.log('Error in sending Email: ' + JSON.stringify(err));

        }

    });

}
Caveats:
  • Outgoing email settings must be configured if on-premises
  • make sure the function that subscribes to click button of the Post button (bindCommentsEvents) is invoked on your page load – it could be through Script Editor web part
  • if there is more than one web part on your page you may need to adjust your jquery selector

Read full article!

Sep 19, 2017

Delete SharePoint calendar event with JavaScript


Technorati Tags: ,
If you ever need to override ribbon actions or to implement custom calendar in SharePoint working with the out-of-the-box calendar list, you will end up implementing create, update and delete operations for your events.
The possibility of having re-occurring events makes the implementation of delete operation quite complex, because you would usually aim to delete single occurrence rather the whole series of given event.
The best approach would be to find out how to invoke the ootb function of the operation and develop your own custom code around it.
The server side logic implementation for the calendar is residing in Microsoft.SharePoint.ApplicationPages.Calendar.dll , and the client one could be found in SharePoint hive folder TEMPLATE\LAYOUTS - SP.UI.ApplicationPages.Calendar.js (respectively SP.UI.ApplicationPages.Calendar.debug.js).
If you search in this JavaScript file by "delete", you will find right away next function:
deleteItem: function SP_UI_ApplicationPages_CalendarContainer$deleteItem$in(itemId) {

        var $v_0 = window['ctx' + this.$U_1.ctxId];

        var $v_1;

        var $v_2 = $v_0['RecycleBinEnabled'];

 

        if (itemId.indexOf('.0.') !== -1 || itemId.indexOf('.1.') !== -1) {

            $v_1 = SP.Res.calendarDeleteConfirm;

        }

        else if (!SP.UI.ApplicationPages.SU.$1($v_2) && $v_2 === '1') {

            $v_1 = window.Strings.STS.L_STSRecycleConfirm_Text;

        }

        else {

            $v_1 = window.Strings.STS.L_STSDelConfirm_Text;

        }

        if (!confirm($v_1)) {

            return;

        }

        this.$c_1.$9M_0(itemId);

    }
It looks to be exactly the function that is called when we press delete:
deleteEvent_UI
The problem is that the delete action and invocation of the server side happens through calling this.$c_1.$9M_0(itemId);
There are two challenges here:
  • What is the value of the argument itemId that we must provide to make a valid invocation
  • Can we directly call $c_1.$9M_0 function
The latter is not a good approach considering Microsoft may change it in future upgrades/releases.
So, the only feasible solution would be how to call the deleteItem function and to pass valid argument value to it, so it can carry out the action for us.
If you scroll up in the file in the SP.UI.ApplicationPages.Calendar.debug.js, you will find out that the deleteItem function is defined for SP.UI.ApplicationPages.CalendarContainer object.
Calling SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance() is giving us the reference we need.
The value we need to pass is the value of the ID from the url.
It could be just a figure: ID=1, or combination of ID and time for repeating events: ID=2.0.2017-09-03T15:00:00Z
You can easily parse the URL and get the values from query string:
var readQueryString = function (src, key) {

    key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars

    var match = src.match(new RegExp("[?&]" + key + "=([^&]+)(&|$)"));

    return match && decodeURIComponent(match[1].replace(/\+/g, " "));

}

var id = readQueryString(this.location.href, "ID");
SP.UI.ApplicationPages.CalendarInstanceRepository.firstInstance().deleteItem(id);
You can give it a try in the console:
deleteEvent_JS
The solution about has been validated for on premises implementation, but it should be also applicable to o365 environment.
Read full article!