SharePoint Lists-Smarter with JavaScript

SharePoint lists are great for tracking data-easy to create, export to Excel, develop against using JSOM.  Problem is when you have a list sometimes you have documents that are associated with the list item.  There are attachments to the list but better to use a document library right?  Maybe create a folder in the document library for each new list item created and tag the folder for the specific folder associating the item to the folder.

Here is where some simple JavaScript and the SharePoint JSOM come in to save the day…

Step 1: Create your SharePoint list, add your fields, etc.

Step 2: Create a new item, when the newform.aspx opens simply select Edit Page from settings.  Settings-Edit page.  Now you can place a content editor web part referencing your script file.

Screenshot (Standard NewForm.aspx un-customized)

SPLISTFORM

Also, add a document library web part to display the folders we are going to create from the script and filter for your specific list item using the web part connection from the list item web part to the document library web part.

Add any other web parts to the page, calendar, tasks lists etc.  This provides a simple all up view for related content tagged to the list item.

Add a content editor web-part or script editor web part referencing your script file (I typically organize conveniently in the site collections style library)

Example script:
//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js

//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js

var matterName;
var matterNum;
var folderName;
var collListItem;
var folderUrl ;
var folderpath;

//Execute when the user saves the item create a folder in our document library and tag the folder with relevant data from our list item in order to associate the two
function PreSaveItem()
{

//grab some fields off the newform.aspx to use for tagging the folder we create
matterNum = $(‘input[title=”MatterNum”]’).val();
matterName = $(‘input[title=”Name”]’).val();

//Create the folder

folderName = matterName;
folderUrl = “[SiteUrl]” + “/” + folderName;

createFolder(folderUrl, folderName, matterNum);

return true;
}
function createFolder(folderUrl, fname, matterNumber) {

var clientContext;
var oWebsite;
var oList;
var itemCreateInfo;

clientContext = new SP.ClientContext.get_current();
oWebsite = clientContext.get_web();
oList = oWebsite.get_lists().getByTitle(“Documents”);

itemCreateInfo = new SP.ListItemCreationInformation();
itemCreateInfo.set_underlyingObjectType(SP.FileSystemObjectType.folder);
itemCreateInfo.set_leafName(folderName);
//I am using a specific content type inherited from the folder type
this.oListItem = oList.addItem(itemCreateInfo);
this.oListItem.set_item(“ContentTypeId”, “0x0120003C4F41AA23709B458E3DA2EEFAA5DCAA”);
this.oListItem.set_item(“Title”, fname);
this.oListItem.set_item(“MatterNum”, matterNumber);
this.oListItem.update();
clientContext.load(this.oListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, successHandler),
Function.createDelegate(this, errorHandler)
);
}
function successHandler() {

//Yeah have a new folder

}

function errorHandler(sender, args) {
alert(‘Record Create failed. ‘ + args.get_message() + ‘\n’ + args.get_stackTrace());
}

//TO DO-This part for next time-build out a complete folder structure based on a pre-built document libary structure
function retrieveFolderStructure() {
var _clientContext1;
_clientContext1 = SP.ClientContext.get_current();
var oList = _clientContext1.get_web().get_lists().getByTitle(‘Matter Documents’);

var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml(“500”);

this.collListItem = oList.getItems(camlQuery);

_clientContext1.load(collListItem);

_clientContext1.executeQueryAsync(Function.createDelegate(this, this.onQuerySucceeded2), Function.createDelegate(this, this.onQueryFailed2));

}

 

Summary:

Make your SharePoint lists a little smarter.  Why not?  Of course all this works on SharePoint 2013 and Office 365.  Also can use on editform.aspx, the script to access the form fields slightly different but doable np.

Bonus material:

Add a calculated column to your SharePoint list that links users to the specific folder:

=”<a class=’docs’ title=’Documents’ href=”&”‘”&”/[Site]/Shared Documents/Forms/AllItems.aspx?RootFolder=/[Site]/Shared Documents/”&Name&”‘”&”>Documents</a>”

*Set your column format to “number” so displays HTML

 

SharePoint 2013 and AngularJS perform CRUD operations in multiple site collections with REST

SharePoint 2013 and AngularJS-performing CRUD operations in multiple site collections with REST

Overview:

This post covers a specific scenario with SharePoint 2013 and AngularJS (or other javascript framework) to host HTML and JavaScript in one site collection that create/update SharePoint list items that reside in a completely different site collection, and doing so without using SharePoint Hosted Apps.

If you are using AngularJS/SharePoint 2013 in a project that requires both querying AND updating data stored in lists that reside in a separate site collection than where the AngularJS app files are hosted than you may want to read on.

Issue:

The app files are stored at the root of the web application while the data is distributed to separate site collections and team sites for each organization and individual teams. This structure provides a mechanism to distribute the data surfaced in the AngularJS UI using SharePoint to provide the necessary security trimming of the data at each level of the organization.  Using SharePoint for the backend data and security and structuring the information architecture in this manner was designed to eliminate the need to manage user permissions as part of the custom application.

In SharePoint 2013 hosted apps the ability to query and update list items dispersed among multiple site collections is provided by scoping the SharePoint app to tenant and using the SP.RequestExecutor to make the REST calls (I covered in a recent post)

In this particular application however the decision was made not to go with a SharePoint hosted app until a solution could be developed to safely remove the app id from the URL.

Recently,  in fact just last week we ran into a blocking issue performing updates to lists that reside in a different site collection than were the AngularJS app files reside.  Once we rolled out the new site collection/team site structure the development team reported that the REST calls to perform updates to the list items were now all of a sudden returning an error. Curiously these identical REST calls functioned as expected when the data resided in the same site collection as the REST code and although errors now returned when performing updates the code was able to query the data in the separate site collection as expected.

Error:

The REST call to the _api/ContextInfo was failing with the message “the Method was not allowed and that it was using a GET but required a POST”  This was odd since the code was in fact using a POST, not a GET.

Solution:

When I first dug into the issue myself it occurred to me that maybe the form digest issue was the culprit. I reviewed the code being used to make the REST calls to update the data and it all appeared fine-very similar to documented examples from Jeremy Thake, Andrew Connell, and others.   Wictor Wilen has a well documented fix for the form digest issue on his blog but after reviewing against our existing code did not see any substantial difference that would explain why our code was failing.  The code in our specific application is slightly different since it uses promises but had similar characteristics to examples from other sources.

However I did notice our code was hard-coded to the root URL so I tried changing the call to use the URL to the site where the data resided rather than the root site collection.

Example:

FROM:

$.ajax({
    url: "/_api/contextinfo",
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
TO:
$.ajax({
    url: /org/team/subteam/_api/contextinfo",
    method: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
Unfortunately this did not resolve the issue entirely-I did additional research along with trial and error to determine the root cause of the error.  Finally thought to read the comments on Wictor Wilens post on the form digest issue and there the solution presented itself in one of the comments-one line from a comment stood out that if you use Type: Post  rather than Method: Post the call will give you the $(‘#__REQUESTDIGEST’).val() rather than the object.  Sure enough, turned my chair around and asked the developer to give it a try.  IT WORKED!  So simple but seemed like a needle in the haystack on a day where this one issue was holding up deploying the app for our customer to test.  Seems obvious looking back, but like my grandma said hindsight is 20/20.
Root cause:
SharePoint requires the form digest for creating and updating items in separate site collections but not for querying items and our code was not accounting for providing the correct URL for the source data and was using method: POST rather than TYPE: POST when attempting to access the #_REQUESTDIGEST.val required for the post to execute successfully.

Resolution:
When creating or updating list items from javascript executed in a separate site collection than the SharePoint list resides AND you are not using a SharePoint Hosted App and SP.RequestExecutor ensure you set your REST call to use type: “POST” rather than method: POST AND  pass in the URL to the site where the data actually resides for the _api/contextinfo.

Updated example:

$.ajax({
    url: /org/team/subteam/_api/contextinfo",
    type: "POST",
    headers: { "Accept": "application/json; odata=verbose"},
    success: function (data) {
        $('#__REQUESTDIGEST').val(data.d.GetContextWebInformation.FormDigestValue)
    },
    error: function (data, errorCode, errorMessage) {
        alert(errorMessage)
    }
});
Guess its a good idea to read blog comments:)

Also, big props to Wictor Wilen for sharing great info and also to whoever posted this insightful comment on his blog that saved the day:

“it’s a great post, but one issue is coming with “method: ‘POST’ – which is it is throwing “The HTTP method ‘GET’ cannot be used to access the resource ‘GetContextWebInformation’. The operation type of the resource is specified as ‘Default’. Please use correct HTTP method to invoke the resource”.
 If we’ll use “type – ‘Post” rather than “method – ‘Post’, it will give you the $(‘#__REQUESTDIGEST’).val(). :)”

http://www.wictorwilen.se/sharepoint-2013-how-to-refresh-the-request-digest-value-in-javascript

Rod Stagg