Sharepoint - Facebook-like notifications when new item is added to certain list/library?

Considered as answered. I've written two scripts for this: one to allow users to subscribe/unsubscribe to a list or library and one to show notifications whenever a new item was added to a list or library to which the user is subscribed to.

Create a list to store your subscribers:

You need to make a simple list at the top site level named "Subscribed users".

Your will then need to make three new columns for your list: ListLibID, PageURL and SubsiteURL.
I originally used to store the GUID of a list/library in the ListLibID column, but after several problems with that in different browsers I decided to just use the name of a list or library instead of the GUID. That was also the main reason on why I also needed a column to save the subsite in (required to call the list across sub sites).

Make sure your list named "Subscribed users" is allowed to be edited by everyone, meaning everyone should have edit permissions. You can change the settings of the view of the list so that it is only visible to you or not visible at all (use the filter for this, set some impossible filter so no item will ever be shown but will still be present in the list).That way you can avoid users sneaking around and trying to meddle with the list. I just used CSS to hide that particular list and the list itself from the site content, so nobody ever finds it.

Create buttons for your lists/libraries:

Now that you have your list ready, it is time to make some buttons. You'll only need to make these for list or libraries of which you want users to be able to subscribe to.

On the page that holds the web part of the list/library of which users should be able to subscribe to, place the following code directly above the code from the web part:

<div class="buttonContainer">
   <div class="subscrButton">
      <div class="subscrButtonTitle"></div>
      <div class="innerSubscr" style="display: none;">
         List or library name here​​
      </div>
   </div>
</div>

You can style your button with some CSS, but I'll leave that to you. ;)

Add references to your scripts:

Since you want to run the script on every page, you need to include both scripts on your master page. Make sure to change the path to the location where you stored the scripts and don't forget to change the id of the scriptlink (it has to be unique).

<!--SPM:<sharepoint:scriptlink id="scriptLink12" language="javascript" 
 localizable="false"  ondemand="false" runat="server"
 name="~sitecollection/Style Library/Scripts/subscribe-to-list-or-library.js">
-->
<!--SPM:<sharepoint:scriptlink id="scriptLink13" language="javascript" 
 localizable="false" ondemand="false" runat="server"
 name="~sitecollection/Style Library/Scripts/notification.js">
-->

Code for "subscribe-to-list-or-library.js":

SP.SOD.executeOrDelayUntilScriptLoaded( 'SP.UserProfiles.js', "~sitecollection/Style%20Library/Scripts/jquery.SPServices-2013.01.min.js");
SP.SOD.executeFunc('SP.js', 'SP.ClientContext');

var firstDiv;
var inList = new Boolean(); 
inList = false;
var listIDDel;
var SURL = $().SPServices.SPGetCurrentSite();
var PURL = window.location.pathname;
var LLID;
var USER = $().SPServices.SPGetCurrentUser({ 
    webURL: "", 
    fieldName: "Title", 
    fieldNames: {}, 
    debug: false
});

$("#content").find($("div.subscrButton")).each(function(){
    LLID = this.childNodes[1].innerHTML.replace(/[\u200B]/g, '');   
    firstDiv = this.childNodes[0].innerHTML.replace(/[\u200B]/g, '');
    $().SPServices({
        operation: "GetListItems",
        async: false,     
        webURL: 'https://your-site-here.com/',
        listName: 'Subscribed users',
        completefunc: function (xData, Status) {
            $(xData.responseXML).find("z\\:row, row").each(function() {
                if (($(this).attr("ows_Title") == USER) && ($(this).attr("ows_ListLibID") == LLID) ) {
                    //console.log('User: ' + $(this).attr("ows_Title") + '\tListLibId: ' + LLID);
                    inList = true;
                }
            });
        }
    });

    if (inList == false) {
        this.childNodes[0].innerHTML = 'SUBSCRIBE';
    }
    else if (inList == true) {
        this.childNodes[0].innerHTML = 'UNSUBSCRIBE';
        inList = false;
    }
});

$("div.subscrButton").click(function(){ 
    LLID = this.childNodes[1].innerHTML.replace(/[\u200B]/g, '');   
    firstDiv = this.childNodes[0].innerHTML.replace(/[\u200B]/g, '');
    console.log(firstDiv);
    if (firstDiv == "UNSUBSCRIBE") {
        this.childNodes[0].innerHTML = 'SUBSCRIBE';
        console.log('User "' + USER + '" wants to unsubscribe. ');

        $().SPServices({
            operation: "GetListItems",
            async: false,     
            webURL: 'https://your-site-here.com/',
            listName: 'Subscribed users',
            completefunc: function (xData, Status) {
                $(xData.responseXML).find("z\\:row, row").each(function() {
                    if (($(this).attr("ows_Title") == USER) && ($(this).attr("ows_ListLibID") == LLID) ) {
                        listIDDel = $(this).attr("ows_ID");
                    }
                });
            }
        });

        $().SPServices({
            operation: 'UpdateListItems',
            webURL: 'https://your-site-here.com/',
            listName: 'Subscribed users',
            updates: '<Batch OnError="Continue" PreCalc="True">' + 
                    '<Method ID="1" Cmd="Delete">' +
                    '<Field Name="ID">'+ listIDDel +'</Field>' +
                    '<Field Name="Title">'+ USER +'</Field>' +
                    '<Field Name="ListLibID">'+ LLID +'</Field>' +
                    '<Field Name="PageURL">'+ PURL +'</Field>' +
                    '<Field Name="SubsiteURL">'+ SURL +'</Field>' +
                    '</Method>' +
                 '</Batch>',
            completefunc: function(xData, Status) {
                console.log('User "'+ USER + '" is now removed from the subscribers list. ');   
            }
        });
        runInOtherFile();
        runDeleteInOtherFile(LLID);

    }
    else if (firstDiv == "SUBSCRIBE") {
        this.childNodes[0].innerHTML = 'UNSUBSCRIBE';
        console.log('User "' + USER + '" wants to subscribe. Adding user to subscribers list now. ');

        // You could add an optional check here, to see if there is already an item in the 'Subscribed users' list with the identical values. If there is, then do nothing. If there isn't, then add a new item.

        $().SPServices({
            operation: 'UpdateListItems',
            webURL: 'https://your-site-here.com/',
            listName: 'Subscribed users',
            updates: '<Batch OnError="Continue" PreCalc="True">' + 
                    '<Method ID="1" Cmd="New">' +
                    '<Field Name="Title">'+ USER +'</Field>' +
                    '<Field Name="ListLibID">'+ LLID +'</Field>' +
                    '<Field Name="PageURL">'+ PURL +'</Field>' +
                    '<Field Name="SubsiteURL">'+ SURL +'</Field>' +
                    '</Method>' +
                 '</Batch>',
            completefunc: function(xData, Status) {
                console.log('User "' + USER + '" has been added to the subscribers list. ');        
            }
        }); 
        runInOtherFile();
    }
});

Code for "notification.js":

// Get the name of the current user.
var USER = $().SPServices.SPGetCurrentUser({ 
    webURL: "", 
    fieldName: "Title", 
    fieldNames: {}, 
    debug: false
});

var currentViewUser;
var currentViewLLIB;
var currentViewPURL;
var currentViewSURL;
var currentViewCOUNT;
var counter = 0;
var tempArr;
var itemArray = new Array();
var itemArrayContainer = new Array();
var itemArrayReplaced = new Array();
var itemArrayContainerReplaced = new Array();


console.log('Notification script has been loaded! Starting script now...\n----------------------------------------------------------');
runOnLoad();

function runOnLoad() {
    $().SPServices({
        operation: "GetListItems",
        async: false,        
        webURL: 'https://your-site-here.com/',
        listName: 'Subscribed users',
        completefunc: function (xData, Status) {
            $(xData.responseXML).SPFilterNode("z:row").each(function() {
                if ($(this).attr("ows_Title") == USER) {
                    counter++;
                    itemArray[counter] = new Array($(this).attr("ows_Title"), unescape($(this).attr("ows_ListLibID")), $(this).attr("ows_PageURL"), $(this).attr("ows_SubsiteURL"), counter);
                    itemArrayContainer.push(itemArray[counter]);
                }
            });
        }
    });
}

if (counter != 0) {         
    for (var j = 0; j < itemArrayContainer.length; j++) {
        sessionItem = "sessionItem" + (j + 1);
        sessionStorage.setItem(sessionItem, "");
    }
    function repeatEvery5Seconds() {
        for (var i = 0; i < itemArrayContainer.length; i++) {
            currentViewUser = itemArrayContainer[i][0];
            currentViewLLIB = itemArrayContainer[i][1]; 
            currentViewPURL = itemArrayContainer[i][2];
            currentViewSURL = itemArrayContainer[i][3];
            currentViewCOUNT = "sessionItem" + itemArrayContainer[i][4];
            fetchCurrentListStatus(currentViewLLIB, currentViewCOUNT);
        }
    }
    var t = setInterval(repeatEvery5Seconds,5000);
}
else {
    console.log('User "' + currentViewUser + '" is not in the subscribers list. Notifications will not be shown.');
}

function fetchCurrentListStatus(currentViewLLIB, currentViewCOUNT) {
    var listItemArray = new Array();
    var listItemArrayBackup = new Array();

    $().SPServices({
        operation: "GetListItems",
        async: false,     
        crossDomain: true,
        webURL: currentViewSURL,
        listName: currentViewLLIB,
        completefunc: function (xData, Status) {
            $(xData.responseXML).find("z\\:row, row").each(function() {
                var rowData = $(this).attr("ows_ID");
                listItemArray.push(rowData);
                listItemArrayBackup.push(rowData);  
            });
        }
    });

    if (listItemArray.length == null || listItemArray.length == 0) {
        console.log('List is empty.');
        sessionStorage.setItem(currentViewCOUNT, listItemArray);
    }
    else if (listItemArray.length != null || listItemArray.length != 0) {
        console.log('Server list: \t\t"' + listItemArray + '"');
        if ( sessionStorage.getItem(currentViewCOUNT).length != 0 || sessionStorage.getItem(currentViewCOUNT) != 0 || sessionStorage.getItem(currentViewCOUNT) != "" ) {
            tempArr = sessionStorage.getItem(currentViewCOUNT).split(",");
            console.log('Session list: \t\t"' + tempArr + '"');
            itemChange();
        }
        else if (sessionStorage.getItem(currentViewCOUNT).length == 0 || sessionStorage.getItem(currentViewCOUNT) == 0 || sessionStorage.getItem(currentViewCOUNT) == "" ) {
        //Session was empty, so we'll set the value of the session item.
            sessionStorage.setItem(currentViewCOUNT, listItemArray);
            tempArr = sessionStorage.getItem(currentViewCOUNT).split(",");
            console.log('Session list: \t\t"' + tempArr + '"');
            itemChange();
        }
        if (tempArr.length == listItemArray.length) {
            console.log('No new item was added in the past 5 seconds. \n----------------------------------------------------------');   
        }
    }



    function itemChange() {
        if (listItemArray.length == tempArr.length) {
            //console.log('The array remains unchanged.');
        }
        else if (listItemArray.length < tempArr.length) {
            console.log('An item was deleted from a list. Now updating session storage. ');
            sessionStorage.setItem(currentViewCOUNT, listItemArray);        
        }
        else if (listItemArray.length > tempArr.length) {
            console.log('A new item has been added. Now updating session storage. '); 

            var array1 = listItemArray;
            var array2 = tempArr;
            var index;

            for (var i=0; i<array2.length; i++) {
                index = array1.indexOf(array2[i]);
                if (index > -1) {
                    array1.splice(index, 1);
                }
            }
            var currentItemName;
            var newListItemArray = new Array(); 
            for (var i = 0; i < array1.length; i++) {
                var currentItem = array1[i];
                 $().SPServices({
                    operation: "GetListItems",
                    ID: currentItem,
                    async: false,
                    crossDomain: true,
                    webURL: currentViewSURL,
                    listName: currentViewLLIB,
                    completefunc: function (xData, Status) {
                      $(xData.responseXML).SPFilterNode("z:row").each(function() {
                            if ($(this).attr("ows_ID") == currentItem) {
                                currentItemName = $(this).attr("ows_LinkFilename"); 
                            }
                            newListItemArray.push(currentItemName);
                      });
                   }
                });
            }

            var div = document.createElement("div");
            div.style.width = "auto";
            div.style.height = "21px";
            div.style.background = "#F7F7F7";
            div.style.color = "#3C82C7";
            div.style.border = "1px solid #d7d6d8";
            div.style.float = "right";
            div.style.padding = "5px";
            div.style.borderBottomLeftRadius = "4px";
            div.innerHTML = 'Een document genaamd "' + currentItemName + '" werd toegevoegd aan <a href="' + currentViewPURL + '">' + currentViewLLIB + '</a>.';
            div.className = "notification";
            document.getElementById("notificationArea").appendChild(div);
            $(document.getElementsByClassName("notification")).hide();
            $(document.getElementsByClassName("notification")).fadeIn(1500);
            setTimeout(function() { 
                $(document.getElementsByClassName("notification")).fadeOut(1500);
                document.getElementById("notificationArea").removeChild(div);
            }, 5000);


            if (tempArr != listItemArray) {
                sessionStorage.setItem(currentViewCOUNT, listItemArrayBackup);
            }
        }
    }
}

function runInOtherFile() {
    console.log('Now running "runInOtherFile()". ');
    counter = 0;
    setTimeout(function() { 
    itemArrayContainerReplaced.length = 0;
        $().SPServices({
            operation: "GetListItems",
            async: false,        
            webURL: 'https://your-site-here.com/',
            listName: 'Subscribed users',
            completefunc: function (xData, Status) {
                $(xData.responseXML).SPFilterNode("z:row").each(function() {
                    if ($(this).attr("ows_Title") == USER) {
                        counter++;
                        itemArrayReplaced[counter] = new Array($(this).attr("ows_Title"), unescape($(this).attr("ows_ListLibID")), $(this).attr("ows_PageURL"), $(this).attr("ows_SubsiteURL"), counter);
                        itemArrayContainerReplaced.push(itemArrayReplaced[counter]);
                    }
                });
            }
        });
        console.log('List of subscribed users has been updated. \n----------------------------------------------------------');
        itemArrayContainer = itemArrayContainerReplaced;
    }, 1000);
}   

function runDeleteInOtherFile() {
    for (var k = 0; k < itemArrayContainer.length; k++) {
        if (itemArrayContainer[k][1] == LLID) {
            var tempCurrentViewCOUNT = "sessionItem" + itemArrayContainer[k][4];
            sessionStorage.setItem(tempCurrentViewCOUNT, "");    // 0);
            itemArrayContainer.splice(k, 1);
        }   
    }
    console.log('Session corresponding to the list/library from which user has unsubscribed is now empty.');
}



I strongly suggest you remove most console logs and minify the javascript. Also check on errors before you apply it to your site. Just because it does not give any errors in my browsers does not mean it will work flawless on your end. I did my best to remove all the errors and mistakes I encountered and made sure all validated well (for example, no double subscribing, no empty values,...).


Screenshots:

Code in action, here I am subscribed for two lists:
Code in action, here I am subscribed for two lists
Session item being updated when a new list/library item has been added:
Session item being updated when a new list/library item has been added
Notification (in Dutch) of a new item that was added, as well as a link to the page that holds a web part of that item:
Notification (in Dutch) of a new item that was added, as well as a link to the page that holds a web part of that item
Subscribing to a list:
Subscribing to a list
Unsubscribing from a list:
Unsubscribing from a list

I also posted about this on my blog here, with a link to here of course for credit.

Thank you everyone here who answered to my question, because of you I actually began searching the possibilities. I plan on making a C# solution when I'm able to, and will make an update when such a solution is available.


SharePoint 2010/2013 has a notification support SP.UI.Notify.addNotification(strHtml, bSticky);You can use them. More info here:

http://msdn.microsoft.com/en-us/library/office/ff407632(v=office.14).aspx


The best way would be to use WebSockets. With WebSockets the server can send the notifications to the browsers so the browsers don't have to poll the server for updates.

You will need some server side and some client side code. With SignalR the process is simplified and you'll see that very little code is needed to get things working.

Main steps:

  1. Setup SignalR for use in SP (for example in HttpHandlers init method)
  2. In ItemAdded event receiver broadcast the message with SignalR
  3. Write client side javascript code that will accept the message from the server and display it.

Here's one example http://blog-aspc.azurewebsites.net/sharepoint-with-signalr/

With only client side code you can't get the real-time behaviour. The closest you can get to real-time behaviour without WebSockets is by periodically running a script to check for any changes (Brent Ellis's answer).

Tags:

Alert