4

I am trying to create one SP hosted app which will read all events (includes recurring) from SharePoint calendar list. I tried but am enable to reach to it.

my code is as below.

function retrieveListItems() {
        var appContextSite;
        ListName = "MyList";
        web = context.get_web();
        context.load(web);

       var myList = web.get_lists().getByTitle(ListName);
       var query = new SP.CamlQuery();

     query.set_viewXml(
     "<ViewFields>"
     "<FieldRef Name='EventDate' />"+
     "<FieldRef Name='EndDate' />"+
     "<FieldRef Name='RecurrenceData' />"+
     "<FieldRef Name='fRecurrence' />"+
     "</ViewFields>"+
     "<Query><Where>" +
    "<DateRangesOverlap>" +
    "<FieldRef Name='EventDate' />" +
     "<FieldRef Name='EndDate' />" +
     "<FieldRef Name='RecurrenceID' />" +
     "<Value  Type='DateTime'>" + currentDate + "</Value>" +
     "</DateRangesOverlap>" +
     "</Where>" +
     "<queryOptions>" +
     " <QueryOptions>" +
     "<ExpandRecurrence>TRUE</ExpandRecurrence>" +
     "</QueryOptions>" +
     " <QueryOptions>" +
     "<CalendarDate>" + currentDate + "</CalendarDate>" +
     "<RecurrencePatternXMLVersion>v3</RecurrencePatternXMLVersion>" +
     "</QueryOptions>" +
     "</queryOptions>" +
     "<OrderBy>" +
     "<FieldRef Name='EventDate' Ascending='True' />" +
     "</OrderBy></Query>");
    var items = myList.getItems(query);
    context.load(items);
    debugger;
   context.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));

    function onQuerySucceeded(sender, args) {

    var listItemInfo = '';

    var listItemEnumerator = collListItem.getEnumerator();

    while (listItemEnumerator.moveNext()) {
        var oListItem = listItemEnumerator.get_current();
        listItemInfo += '\nID: ' + oListItem.get_id() + 
            '\nTitle: ' + oListItem.get_item('Title') + 
            '\nBody: ' + oListItem.get_item('Body');
    }

    alert(listItemInfo.toString());
}

function onQueryFailed(sender, args) {

    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

}
  • If I remember correctly, it is not possible to expand recurring events through the client object model (unclear why) – Robert Lindgren Aug 4 '16 at 14:37
  • @Robert Lindgren it's bit late am replaying but i have to know,why it is not possible?do you have seen/read it somewhere,any article which says whatever you have said? – sharepoint2013 Sep 30 '16 at 12:04
  • I have tried it, with no success – Robert Lindgren Sep 30 '16 at 13:32
  • am also trying the same,but no luck yet,so how did you got any other solution for this? – sharepoint2013 Oct 3 '16 at 5:54
  • Why people are not giving answers for SharePoint hosted app related. None of the things will work in SP hosted apps. In REST you have to make two calls like: getByTitle('Events')/items('7')/RecurrenceData. This will return you xml string. from there you can write your logic – Mihir Oct 11 '17 at 17:09
3

I suggest you to use the SPServices library. It is based on the classical SharePoint web services interface. Based on my experience, it is the single interface available for client-side development that supports expanding of recurrance events into a series of distinct events.

See exmples here or here.

But you don't have to use the SPServices library, it makes only calling the web services simpler. But calling them directly is OK as well.

Here is the most relevant part of the example I used as base for my custom solution:

function( start, end, callback ) {

            // Create an array to hold the events.
            var events = [];

            // Set the date from which to pull events based on the first visible day in the current calendar view. For a month view, this will usually be several days into the previous month. We can use FullCalendar's built-in getView method along with the formatDate utility function to create a date string in the format that SharePoint requires. It must be in the format YYYY-MM-DDTHH:MM:SSZ. Due to time zone differences, we will omit everything after the day.
            //var startDate = $.fullCalendar.formatDate( $( '#calendar2' ).fullCalendar( 'getView' ).start, "u" ).split( "T" )[0];

            $( calendarName ).fullCalendar( 'gotoDate', cyr, cmo );        
            var startDate = cDate;            

            // Get the current view of the calendar (agendaWeek, agendaDay, month, etc.). Then set the camlView to the appropriate value to pass to the web service. This way we will only retrieve events needed by the current view (e.g. the agendaWeek view will only retrieve events during the current week rather than getting all events for the current month).
            var calView = $( calendarName ).fullCalendar( 'getView' ).title;
            var camlView = "";

            switch( calView ) {
                case "agendaWeek":
                    camlView = "<Week />";
                    break;
                case "agendaDay":
                    camlView = "<Week />";
                    break;
                default: // Default to month view
                    camlView = "<Month />";
            }               

            // Set the camlFields, camlQuery, and camlOptions to the appropriate values to pass to the web service. You can add additional <ViewFields /> or adjust the CAML query if you have some custom columns that you want to filter by or display data from. The values below are the pretty much the minimum you'll want to start from to get it working.
            var camlFields = "<ViewFields><FieldRef Name='Title' /><FieldRef Name='EventDate' /><FieldRef Name='EndDate' /><FieldRef Name='Location' /><FieldRef Name='Description' /><FieldRef Name='fRecurrence' /><FieldRef Name='RecurrenceData' /><FieldRef Name='RecurrenceID' /><FieldRef Name='fAllDayEvent' /><FieldRef Name='Color' /><FieldRef Name='Department' /></ViewFields>";
            var camlQuery = "<Query><CalendarDate>" + startDate + "</CalendarDate><Where><DateRangesOverlap><FieldRef Name='EventDate' /><FieldRef Name='EndDate' /><FieldRef Name='RecurrenceID' /><Value Type='DateTime'>" + camlView + "</Value></DateRangesOverlap></Where><OrderBy><FieldRef Name='EventDate' /></OrderBy></Query>";
            var camlOptions = "<QueryOptions><CalendarDate>" + startDate + "</CalendarDate><RecurrencePatternXMLVersion>v3</RecurrencePatternXMLVersion><ExpandRecurrence>TRUE</ExpandRecurrence><DateInUtc>TRUE</DateInUtc></QueryOptions>";

            // Make the web service call to retrieve events.
            $().SPServices({
                operation: "GetListItems",
                async: false,
                listName: listCalendarName, // Change this to the GUID or display name of your calendar. If the calendar is on a different site, you can use the display name with the webURL option (see SPServices.CodePlex.com for more information).
                CAMLViewFields: camlFields,
                CAMLQuery: camlQuery,
                CAMLQueryOptions: camlOptions,
                completefunc: function( xData, Status ) {                

                $(xData.responseXML).find("z\\:row, row").each(function() {

                        // Check for all day events
                        var fADE = $( this ).attr( 'ows_fAllDayEvent' );
                        var thisADE = false;
                        var thisStart;
                        var thisEnd;

                        if ( typeof fADE !== "undefined" && fADE !== "0" ) {
                            thisADE = true;
                            // Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, and we don't need to do any local time conversions for all day events, so we can use the UTC date strings from SharePoint without converting them to local time.
                            var thisStart = $( this ).attr( 'ows_EventDate' );
                            var thisEnd = $( this ).attr( 'ows_EndDate' );
                        }
                        else {
                            // Get the start and end date/time of the event. FullCalendar will parse date strings in local time automagically, so we need to convert the UTC date strings from SharePoint into local time. The formatDateToLocal() function above will take care of this. See comments in that function for more information.
                            var thisStart = formatDateToLocal( $( this ).attr( 'ows_EventDate' ) );
                            var thisEnd = formatDateToLocal( $( this ).attr( 'ows_EndDate' ) );
                        }

                        // Get the list item ID and recurrence date if present. This will be used to generate the ID query string parameter to link to the event (or the specific instance of a recurring event). The ID query string parameter must be in the format "ID.0.yyyy-MM-ddTHH:mm:ssZ" for recurring events (where "ID" is the list item ID for the event). Event ID's are returned as just a number (for non-recurring events) or several numbers separated by ";#" in 2007 or "." in 2010 to indicate individual instances of recurring events. By splitting and joining the ID this way, thisID will be set to a valid query string parameter whether an event is recurring or not for both versions of SharePoint.
                        var thisID = $( this ).attr( 'ows_ID' ).split( ';#' ).join( '.' );

                        // FullCalendar documentation specifies that recurring events should all have the same id value when building the events array (the id is optional, but I'm including it for completeness). We can get the list item ID (which is the same for all instances of recurring events) without the recurrence information by simply splitting thisID.
                        var eventID = thisID.split( '.' )[0];

                        // Get the event title. This is displayed on the calendar along with the start time of the event.
                        var thisTitle = $( this ).attr( 'ows_Title' );

                        // Get the event description. I don't use it in this example, but you could use it for something, perhaps as a tooltip when hovering over the event.
                        var thisDesc = $( this ).attr( 'ows_Description' );
                        if( thisDesc == "<div></div>")
                            thisDesc = "";

                        var thisColor = $( this ).attr( 'ows_Color' );
                        if( thisColor != null && thisColor != "")
                        {
                            thisColor = thisColor.split(";#")[1];
                        }
                        else
                        {
                            thisColor = "Yellow";
                        }

                        var thisDepartmentName = $( this ).attr( 'ows_Department' );                        
                        var deptName = AddColorToLegend(thisDepartmentName, thisColor);                        

                        events.push({
                            title: thisTitle,
                            id: eventID,
                            start: thisStart,
                            end: thisEnd,
                            allDay: thisADE,
                            backgroundColor: thisColor,
                            textColor:'Navy',
                            // Adjust this URL to link to the display form for your calendar events. You can include a Source parameter to allow users to easily return to the FullCalendar page.
                            url: siteURL + '/Lists/' + listCalendarName + '/DispForm.aspx?ID=' + thisID + '&Source=' + sourceUrl,
                            description: thisDesc,
                            department:deptName
                        });

                    });

                    callback( events );

                }
            });
  • i dont understant ,how do i implement the SPService stuff with my code,i added the code in question,can you please have a glance over it? – sharepoint2013 Aug 5 '16 at 7:11
  • i tried your mentioned method as well,still i am not able to read recurring events.... :( – sharepoint2013 Aug 5 '16 at 11:17
  • I make a quick comparision with my working code and found that in my case there is no <CalendarDate> in the query: var camlQuery = "<Query><Where><DateRangesOverlap><FieldRef Name='EventDate' /><FieldRef Name='EndDate' /><FieldRef Name='RecurrenceID' /><Value Type='DateTime'><Month /></Value></DateRangesOverlap></Where><OrderBy><FieldRef Name='EventDate' /></OrderBy></Query>"; Could you please try this version? If it helps, I'll update my answer accordingly. – pholpar Aug 5 '16 at 11:53
  • have applyed that code with sharepoint hosted app? – sharepoint2013 Aug 5 '16 at 12:22
  • no nothing happening after making that change ,which you have mentioned – sharepoint2013 Aug 5 '16 at 12:24
2

In server-side code you can get around recurrence by setting the ExpandRecurrence property to true on the SPQuery object used to query the list. However, as of SP2010 and SP2013, that property is not exposed on the equivalent JavaScript Object Model.

Workaround: Using the Lists.GetListItems web service

An alternative is to use one of the old (technically deprecated) web services that are still floating around... specifically the Lists web service accessible at /_vti_bin/Lists.asmx. This web service has a GetListItems method that accepts a SOAP message in which you can specify a query option to expand recurrence as you would on the server side.

Here's an example demonstrating how you can query the Lists web service using plain JavaScript:

// Set webUrl and listGuid to values specific to your site and list
var webUrl = "http://server/sitewhereyourlistexists";
var listGuid = "{000000000-0000-0000-0000-000000000000}"

// An XMLHttpRequest object is used to access the web service
var xhr = new XMLHttpRequest();
var url = webUrl + "/_vti_bin/Lists.asmx";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type","text/xml; charset=utf-8");
xhr.setRequestHeader("SOAPAction","http://schemas.microsoft.com/sharepoint/soap/GetListItems");

// The message body consists of an XML document 
// with SOAP elements corresponding to the GetListItems method parameters
// i.e. listName, query, and queryOptions
var data = "<?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>" +
        "<GetListItems xmlns=\"http://schemas.microsoft.com/sharepoint/soap/\">" + 
                "<listName>"+listGuid+"</listName>" + 
                "<query>" + 
                    "<Query><Where>" +
                        "<DateRangesOverlap>" + 
                            "<FieldRef Name=\"EventDate\"/>"+
                            "<FieldRef Name=\"EndDate\"/>"+
                            "<FieldRef Name=\"RecurrenceID\"/>"+
                            "<Value Type=\"DateTime\"><Today/></Value>"+
                        "</DateRangesOverlap>"+
                    "</Where></Query>"+
                "</query>" +
                "<queryOptions>"+
                    "<QueryOptions>"+
                        "<ExpandRecurrence>TRUE</ExpandRecurrence>"+
                    "</QueryOptions>"+
                "</queryOptions>" +
        "</GetListItems>" +
        "</soap:Body>" +
    "</soap:Envelope>";

// Here we define what code we want to run upon successfully getting the results
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){
        if(xhr.status == 200){
            var doc = xhr.responseXML;
            // grab all the "row" elements from the XML results
            var rows = doc.getElementsByTagName("z:row");
            var results = "Today's Schedule ("+rows.length+"):\n\n";
            var events = {};
            for(var i = 0, len = rows.length; i < len; i++){
                var id = rows[i].getAttribute("ows_FSObjType"); // prevent duplicates from appearing in results
                    if(!events[id]){ 
                        events[id] = true;
                        var allDay = rows[i].getAttribute("ows_fAllDayEvent"),
                            title = rows[i].getAttribute("ows_Title"),
                            start = rows[i].getAttribute("ows_EventDate"); 
                        var index = start.indexOf(" "); 
                        var date = start.substring(5,index)+"-"+start.substring(2,4); // get the date in MM-dd-yyyy format
                        start = start.substring(index, index+6); // get the start time in hh:mm format
                        var end = rows[i].getAttribute("ows_EndDate"); 
                        index = end.indexOf(" "); end = end.substring(index,index+6); // get the end time in hh:mm format
                        results += date + " " + (allDay == "1" ? "All Day\t" : start + " to " + end ) + " \t " + title + "\n";
                    }
                }
                alert(results);
            }else{
                alert("Error "+xhr.status);
            }   
    }
};

// Finally, we actually kick off the query
xhr.send(data);

Note that this web service is also what is used by the 3rd party SPServices JavaScript library.

  • so for using SP.Service(),i can directly include this code in my app.js file? – Madhav Sep 26 '16 at 14:00
  • is it possible to pass List Name rather GUID? – Madhav Sep 27 '16 at 4:07
  • @Ma6139735 No, but you can use the list name to retrieve the list guid using the JavaScript client object model. – Thriggle Sep 28 '16 at 4:57
  • 1
    i tried this code /method but it shows me one error as XMLHttpRequest cannot load my URL/_vti_bin/Lists.asmx. Response for preflight is invalid (redirect),so do you have any idea regarding this? – Madhav Sep 29 '16 at 5:48
  • I get the same thing @Madhav – Mike Jul 27 '17 at 17:48
1

The client object model does not expand recurring events for you (which would be nice...) but you can get this information yourself from the response data. I think this is due to the fact that each event (even recurring events) are stored in the list as an individual item. SharePoint uses some logic of its own to display all of the recurrences, but the list only contains one "master copy" of the recurring event.

You'll have to create the recurrences based on some data in a field attached to the calendar list item. The value you'll need to display the appropriate information is in a field called RecurrenceData (you can see all available fields by using get_fields() on the list object)

The RecurrenceData field will have either a null value (for events that do not recur) or some XML data we can use to determine how often an event recurs.

Here is an example of accessing the RecurrenceData field on a calendar item using the JavaScript Client Object Model

var myCtx = SP.ClientContext.get_current();
var myList = myCtx.get_web().get_lists().getByTitle('Calendar');
var myItems = myList.getItems(SP.CamlQuery.createAllItemsQuery());
myCtx.load(myItems, "Include(ID,Title,EventDate,EndDate,RecurrenceData)");
myCtx.executeQueryAsync(function() {
        var itemRecurrenceData = myItems.getItemAtIndex(1).get_item("RecurrenceData");
        console.log(itemRecurrenceData);
    }, function(request, failedEventArgs) {
        // failure stuff here
        console.log(failedEventArgs)
    });

the RecurrenceData field will have some XML in it that looks like this (i've added the line breaks for this example):

<recurrence>
    <rule>
        <firstDayOfWeek>su</firstDayOfWeek>
        <repeat><daily dayFrequency="1" /></repeat>   
        <repeatInstances>10</repeatInstances>
    </rule>
</recurrence>

We can basically read this XML and understand that the event repeats every day for 10 days.

You might also get something like

<recurrence>
    <rule>
        <firstDayOfWeek>su</firstDayOfWeek>
        <repeat><weekly th="TRUE" weekFrequency="1" /></repeat>
        <repeatInstances>10</repeatInstances>
    </rule>
</recurrence>

Which is for an event that repeats once every week on Thursday, again for 10 weeks.

If you had an event that used a date range instead of number of instances you could look at when the EndDate occurred.

Once you have this data you can create the event items yourself in whatever JavaScript format you're using by copying the relevant information into your own data structure. This technique is admittedly alot of work, it might be more time efficient to use a web services approach, but if you aren't already familiar with that approach this will get you to where you can build the list of items you need.

  • can you please show some code/any example ? – Madhav Sep 23 '16 at 10:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.