In this final part of the series we’re going to complete the holiday sync app by importing the selected calendar exceptions into Project Server.
We’ll start from where we finished up in Part 2 with our app loading data from both existing project server calendars and our external web service, so to follow this article make sure you start with the solution from the end of Part 2.
Part 3: Importing data into Enterprise Calendars from our App
Now that we have a list of calendar exceptions that we want to import we need to use some JSOM to actually import those exceptions.
To break-down what we need to do here I’m going to review this JSOM in three steps;
Step 1 Prepare context and get objects to update
// Get the Project Server context var projContext = PS.ProjectContext.get_current(); // Get our Calendar Collection var eCalColl = projContext.get_calendars(); var eCalendar = eCalColl.getByGuid(calUid); var eCalBaseExcep = eCalendar.get_baseCalendarExceptions();
In step 1 we instantiate our object variables using the PS.js JSOM library to first get our calendar collection, then get our calendar by GUID from that collection and finally we get our base calendar exceptions from our calendar.
Next we’ll update those collections.
Step 2 Create the exception(s)
// Step 2 Loop through and add each exception
for (var i = 0; i < exceptions.length; i++) {
var excepInfo = new PS.CalendarExceptionCreationInformation();
// Set the exception properties
excepInfo.set_name(exceptions[i].Descriptor);
excepInfo.set_start(exceptions[i].Date);
excepInfo.set_finish(exceptions[i].Date);
// Finally add the exception info to the base calendar object
eCalBaseExcep.add(excepInfo);
}
// Update the collection
eCalColl.update();
Now we’re going to create each calendar exception using the PS.CalendarExceptionCreationInformation constructor, you’ll find one of those constructors for most of the objects in JSOM, if you want more on this see MSDN; http://msdn.microsoft.com/en-us/library/office/jj669390.aspx.
Once we have our excepInfo object we set the required properties; Name, Start and Finish, then finish by adding that item to our exception collection before moving to the next exception to be added. Finally we update the calendar collection object with all the exceptions created.
Now as we’re working asynchronously the last step is to execute the above update(s);
Step 3 Update the calendar asynchronously
// Finally asynchronously execute the update
projContext.executeQueryAsync(Function.createDelegate(this, function () {
// Success update our grid and finish up
this.grid.setSelectedRows([]);
// Display the results and remove the progress msg
SP.UI.Notify.addNotification("Exceptions added successfully", false);
}), Function.createDelegate(this, function (call, error) {
// Handle Error
alert(error.get_message());
}));
So using the JSOM executeQueryAsync function we execute the change and handle the result, here we simply want to notify the user of the result.
Full code block follows, paste this into the bottom of the App.js:
// Function to add calendar exceptions via JSOM
HolidaySync.prototype.addCalendarException = function (calUid, exceptions) {
// Show a progress message
this.notifyMsg = SP.UI.Notify.addNotification('<img src="/_layouts/images/loadingcirclests16.gif" style="vertical-align: top;"/> Importing...', true);
// Step 1 Get the Project Server context and objects
var projContext = PS.ProjectContext.get_current();
// Get our Calendar Collection
var eCalColl = projContext.get_calendars();
var eCalendar = eCalColl.getByGuid(calUid);
var eCalBaseExcep = eCalendar.get_baseCalendarExceptions();
//CSOM Ref (no JSOM): http://msdn.microsoft.com/en-us/library/office/microsoft.projectserver.client.calendarexceptioncollection_di_pj14mref_members.aspx
// Step 2 Loop through and add each exception
for (var i = 0; i < exceptions.length; i++) {
// Create our Calendar Exception Info
//http://msdn.microsoft.com/en-us/library/office/jj669390.aspx
var excepInfo = new PS.CalendarExceptionCreationInformation();
// Append the year to the name to prevent future duplicates
var exName = exceptions[i].Descriptor + " " + new Date(exceptions[i].Date).getFullYear();
// Set the exception properties
excepInfo.set_name(exName);
excepInfo.set_start(exceptions[i].Date);
excepInfo.set_finish(exceptions[i].Date);
// Finally add the exception to the collection
eCalBaseExcep.add(excepInfo);
}
// Update the collection
eCalColl.update();
// Step 3 Asynchronously execute the update
projContext.executeQueryAsync(Function.createDelegate(this, function () {
// Success update our grid and finish up
this.grid.setSelectedRows([]);
// Display the results and remove the progress msg
SP.UI.Notify.addNotification("Exceptions added successfully", false);
SP.UI.Notify.removeNotification(this.notifyMsg);
}), Function.createDelegate(this, function (call, error) {
// Handle Error
SP.UI.Notify.removeNotification(this.notifyMsg);
alert(error.get_message());
}));
};
Final bit: Import Button
Okay we’re almost done, just one last thing to do and that is to handle the click of the import button. A bit of jQuery will handle that for us, and in which we need to do just one more thing which is to check for any duplicates being imported to prevent import errors.
Paste the following in the main code block of App.js (should be right after the ‘$(“#importBtn”).click(…’ function:
// Button to import selected exceptions
$("#importBtn").click(Function.createDelegate(this, function () {
var selectedRows = holidaySync.grid.getSelectedRows();
// Use helper function to check for any duplicates before importing
var exceptionsToImport = Helpers.removeDuplicates(selectedRows, holidaySync.data);
// Import the exceptions
if (exceptionsToImport.length > 0) {
holidaySync.addCalendarException(holidaySync.data.calendarId, exceptionsToImport);
}
else {
// Mark all existing
holidaySync.grid.setSelectedRows([]);
alert("All selected exceptions already exist.");
}
}));
What we need to do in that function is call our removeDuplicates helper function which was created back in part 1 of this series. The function will return a filtered array of exceptions that we can then pass as a parameter to our addCalendarException function.
Now we should be able to test it and see the following:

All Done
This app documented here is now available on the SharePoint App store, so please rate it if you use it!
Source Download / Repository
You can browse or download the full source code for the completed app on the following GitHub repository: