Using REST to upload a File to Document library (Including different site collection)


The code I’m going to show you today will show you how you can upload a file to a document library using REST. This solution could be used within a Sandbox application.

To upload files, it is much better to use REST than the SharePoint API. The main reason is that SharePoint JSOM/CSOM can only upload files a maximum size of 1.5MB, where REST is good up to 2GB.

My example here has a simple GUI, it is an Upload form, which allows the user to pick from a list the site they wish to upload the file to, a file upload box, and an Upload button.

HTML Code.

<SharePoint:FormDigest runat="server"/>
<div>
   <label id="lblInfo" class="label" for="ProposalSiteUrl">Please enter your Site Collection URL or leave blank to upload file to this Site Collection document library:</label>
</div>
<div>
   <select id="ProposalSiteUrl" class="dropdown" title="Select a site">
                    <option value="http://intranet.cannonfodder.local">This Site</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite1">Team Site 1</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite2">Team Site 2</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite3">Team Site 3</option>
      </select>
</div>
<div class="div">
        <input id="AttachmentUploadField" type="file" size="98"/>
        <input id="AttachAttachmentButton" class="button" onclick="CannonFodder.Utilities.SaveAttachment();" type="button" value="Upload"/>
</div>
<div class="div">
         <label id="lblResult" class="result"></label>
</div>

The user can select from the dropdown a site to send the file to. Select the file using the file picker and click Upload. You can see in this example I have hard coded the option values for the dropdown list. Any results pass or failed will be displayed to the user using the lblResult label.

JavaScript

This code will require JQuery for grabbing the element values from the page and to perform JQuery Ajax request. Ensure that your page has JQuery loaded. In this example I grabbing JQuery from Google.

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Personally, JavaScript isn’t my preferred programming language and I’m trying to write better JavaScript. I’m ensuring I’m not making it part of the Global namespace, and using boxing methods etc. Therefore in this example here you should find this a good practice writing JavaScript.
First I’m going to namespace my JavaScript Methods.

var CannonFodder = CannonFodder || {};
CannonFodder.Utilities = function(){
var currentDlg;
 //Code to write in here.
 return {
      SaveAttachment:saveAttachment
 };
}();

The above piece of code, will create a namespace called CannonFodder.Utilities with one public method called SaveAttachment which calls the private method saveAttachement. We haven’t written saveAttachment yet. We will write a bunch of functions inside the section where I have commented ‘//Code to write in here‘. The first one will be saveAttachement. (Notice small ‘s’ for save) This method will be the only method in this section that can be called from outside of CannonFodder.Utilities. The variable currentDlg
is a global variable for the scope of CannonFodder.Utilities. It is so I can display a waiting dialog screen to the user. This isn’t necessary for this code to work, it just gives the demo a nicer user experience.

The saveAttachment method

var saveAttachment = function(){
    //Validate if the upload file has a file selected.
    if(!validated()){
            jQuery('#lblResult').text("Please choose a file!");
            return;
    }
    
    //Dialog variables
    var dlgTitle;
    var dlgMsg;
    var dlgWidth;
    var dlgHeight;
    dlgTitle = "Uploading Your File...";
    dlgMsg = "<br />Please wait whilst your file is being uploaded<br /><br />Please do not close your browser window.";
    dlgHeight = 300;
    dlgWidth = 500;
        
    //Create Dialog box.
   if (currentDlg == null) {
           currentDlg = SP.UI.ModalDialog.showWaitScreenWithNoClose(dlgTitle, dlgMsg, dlgHeight, dlgWidth);
   }

   //Get File        
   var file = jQuery("#AttachmentUploadField")[0].files[0];

   //Upload the file
   uploadFile(file);
};

The code above doesn’t do much, it first calls a validation method (yet to write) to check if the user has selected a file to upload. If not, it updates the lblresult text to inform the user they must upload a file. If validation passes, we set up a dialog box to inform the user that the upload is taking place (it will take a long time for a 2GB file). Next we display the dialog to the user, grab the file and passes it to the uploadFile method.

The validated method

var validated = function(){
   var file = jQuery("#AttachmentUploadField")[0].files[0];
   if(file == null){        
          return false;
  }
  else{
    return true;
  }
};

The validated method grabs the Upload control and checks if there is a file available. If it is there then the function returns true, else it returns false.

The uploadFile method

var uploadFile = function (file) {
    proposalSiteUrl = jQuery('#ProposalSiteUrl option:selected').val();        
        
    if (proposalSiteUrl == _spPageContextInfo.webAbsoluteUrl) {
        uploadFileLocal(file);
    }
    else{
        uploadFileCrossSite(file, proposalSiteUrl);
    }
};

Our upload file still isn’t the actual code that uploads the file to the given URL. Here I’m checking if the site I’m uploading to is the same Site Collection I’m running my upload page from or not. If it is, then I can do a very simple upload (uploadFIleLocal). If I need to upload to a different Site Collection, then I will be required to perform a cross site call (uploadFileCrossSite).

The uploadFileLocal method

To explain this, I am going to show this method in bits and explain each section.

var uploadFileLocal = function (file) {
      var digest = jQuery("#__REQUESTDIGEST").val();
      var webUrl = _spPageContextInfo.webAbsoluteUrl;
      var libraryName = "Documents";

      var reader = new FileReader();
      var arrayBuffer;

First we need to set all our variables. The digest request is important, it is a client side token to validate post backs to SharePoint to prevent attacks where the user might be tricked into posting data back to the server. It is unique to a user and a site. It is only valid for a limited time. The first line gets the current request digest from the page. This digest is required in our ajax post request when we upload the file. The other variables are pretty standard, web url, the library name to upload to, a file reader and array buffer.

        reader.onload = function (e) {
            arrayBuffer = reader.result;

            url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
               "@TargetLibrary='" + libraryName + "'" +
               "&@TargetFileName='" + file.name + "'";

            //JQuery Ajax call here

       };
       reader.readAsArrayBuffer(file);
  };

The next part of the uploadFileLocal function that continues straight after var arrayBuffer. Our reader needs to read in the file, and on the onload event, we need to prepare our REST url to past into the JQuery ajax request. (The JQuery Ajax call is shown in the next piece of code). The reader.onload function doesn’t fire until after reader.readAsArrayBuffer(file) is called. The REST url uses two parameters @TargetLibrary and @TargetFileName, the actual file data is put into arraryBuffer and used in the JQuery Ajax call.

        
jQuery.ajax({
        url: url,
       type: "POST",
       data: arrayBuffer,
    headers: {
            "Accept": "application/json; odata=verbose",
            "X-RequestDigest": digest
            },    
contentType: "application/json;odata=verbose",
processData: false,
    success: function () {
            jQuery('#lblResult').text("Successfully uploaded file locally.");
            if (currentDlg != null) {
                        currentDlg.close();
            }
         },
     error: function (arr, error) {
               jQuery('#lblResult').text("Error uploading file locally.");
               if (currentDlg != null) {
                      currentDlg.close();
               }
          }
});

Insert the above code in the previous snippet where the comment //JQuery Ajax call here is. This is a typical Ajax call using JQuery. http://api.jquery.com/jquery.ajax/ We pass in our REST url, state that it’s a POST request, and set the data to our file arrayBuffer. Our headers show that the call is a json call, and here we pass in the request digest we obtained in the very first variable of the uploadfileLocal function. The contentType and processData are standard for this type of call. Lastly I have created a success and error callbacks, with inline functions. The functions are similar, both updating the lblResult text with a success or fail message, and then close the dialog that is on show to the user. In the error function we could use the error variable to obtain the actual error message and display it to the user.

This concludes uploading a file to the same site. However if you try to use the same code to upload to a different site it will fail. The reason is all to do with the digest request. As I stated earlier, this is a client side token that is unique to the user and site. Therefore when you try to access a different site to what you are on, you will be passing an invalid digest request. You first need to obtain the digest request for the other site before you can upload/post data to it. Please note: The user will need contribute access to the site to be able to upload a file.

The uploadFileCrossSite method

var uploadFileCrossSite = function (file, webUrl) {
        url = webUrl + "/_api/contextinfo";
        jQuery.ajax({
            url: url,
            type: "POST",
            headers: {
                "Accept": "application/json; odata=verbose"
            },
            contentType: "application/json;odata=verbose",
            success: function (data) {
               var digest = data.d.GetContextWebInformation.FormDigestValue;
                var libraryName = "Documents";

                var reader = new FileReader();
                var arrayBuffer;

                reader.onload = function (e) {
                    arrayBuffer = reader.result;

                    url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
                       "@TargetLibrary='" + libraryName + "'" +
                       "&@TargetFileName='" + file.name + "'";

                    jQuery.ajax({
                        url: url,
                        type: "POST",
                        data: arrayBuffer,
                        headers: {
                            "Accept": "application/json; odata=verbose",
                            "X-RequestDigest": digest
                        },
                        contentType: "application/json;odata=verbose",
                        processData: false,
                        success: function () {
                        jQuery('#lblResult').text("Successfully uploaded file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }
                        },
                        error: function () {
                          jQuery('#lblResult').text("Error uploading file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }

                        }
                    });
                };

                reader.readAsArrayBuffer(file);

            },
       error: function () {
            jQuery('#lblResult').text("Error accessing other site.");
               if (currentDlg != null) {
                   currentDlg.close();
              }
          }
      });
 };

The uploadFileCrossSite code I have shown as whole, mainly because I have explained most of it in the uploadFileLocal function above. The lines I’ve highlighted 1-11 is a JQuery Ajax call into the target site. By requesting the context info, we can obtain the request digest of the target site in the success handler, data.d.GetContextWebInformation.FormDigestValue. We can then use this value to pass in as the digest to upload the file to the target site. The code to upload the file now is identical as before, obtaining the reader, calling the onload event, then on the success of the Ajax request we update the lblResult with a success message and close the dialog displayed.

The final error:function() call lines 53-59 highlighted at the bottom of the code is the error callback if there is an error contacting the target site for the request digest.

I can now test the form, I’ve selected my second team site, and selected a document DemoWordDoc3.docx to upload. I’ve then clicked the Upload button.

The file dialog appears in the centre of the screen so the user knows something is happening.

When the file has been uploaded, the user gets the message that it has successfully uploaded.

If I check out my Team Site 2, you can see that the DemoWordDoc3 is in the Documents library.

You can obtain my upload page from my OneDrive. To get into your SharePoint site, it is easiest just to copy it in using SharePoint Designer.

http://1drv.ms/1tcIDIQ

Why are my publishing pages saying I have so many spelling errors?


In my publishing site, I have a basic custom content type that is based on the SharePoint out the box Page Content type. I have created a custom page layout. My layout contains a PublishingRollupImage, PublishingPageContent, and I’ve created a section on my page layout that only shows when you edit the page which contains PublishingContact. The publishing contact is mainly for search.

When my page is in edit mode, the screen looks like the following:

So I add a picture and some content to the page, and then go to publish it. The Page Content has been double checked and there is no spelling mistakes in there. However when I click publish, I get a pop-up box saying there are 73 spelling error(s) found. How is this possible? There isn’t even 73 words on the page.

The clue why this is happening is actually shown on the page. Where I have Page Owner at the bottom of the page, it clearly states “Spelling Errors Found.” But why? It’s just my name. If you click on the red text, it brings up a pop up, showing you what the spelling mistakes are.

What is actually happening is the user control HTML is being read. The user control has <textarea> which is hidden by css. This textarea is used by SharePoint to correctly display the User on the page, with the link to the user profile information. It is this text that is being spell checked.

There is a simple fix that you can add to your page layouts (assuming your page has jQuery already loaded). The textarea needs an attribute added to it called “excludeFromSpellCheck” which makes the SharePoint spell checker ignore the text within it. On your page layout page, find the ContentPlaceHolderID=”PlaceHolderMain” and right at the bottom of this section add a PublishingWebControls:EditModePanel with the following Javascript inside.

<PublishingWebControls:EditModePanel ID="SpellCheckEditModePanel" runat="server">
<script type="text/javascript">
jQuery(".ms-usereditor textarea").attr("excludeFromSpellCheck", "true");
</script>
</PublishingWebControls:EditModePanel>

The reason why we put it inside an EditModePanel is because the spell checking is only carried out when the page is in edit mode, so there is no need to run the JavaScript in display mode.

As you can see after implementing the jQuery change, we no longer get any spelling errors found.

Calling Generic HTTPHandler from Client side


In my last blog post I explain what an HTTPHandler was, and how to implement it. In this post I will explain to you how to call your HTTPHandler using JavaScript. Please note this solution uses jQuery and assumes this is already being loaded in your page. Please follow my blog inserting jquery into your sharepoint site as a feature, to ensure that jQuery is available within your SharePoint site every time, if you currently don’t load jQuery.

This demo will allow a user to enter a name of a list on the site and return some properties regarding this list. By following the initial steps from the last blog post, create your ashx file and called it ListHandler.ashx.

  • First create a ListDetails.cs class. This class will hold the properties of the SPList we wish to pass back from the handler. Right Click the Project > Add> New Item… and create the class called ListDetails.cs
  • In ListDetails.cs class we will set up 4 properties. Title, DefaultViewURL, Description, ItemCount.

public class ListDetails

    {

        public String Title { get; set; }

        public String DefaultViewURL {get;set;}

        public String Description {get;set;}

        public String ItemCount {get;set;}

    }

  • Save the ListDetails.cs file.
  • Now open the ListHandler.ashx file. From following the last blog post you should have already implemented the IHttpHandler Interface. When implementing the IHttpHandler interface we need both IsReusable and ProcessRequest. Set the IsReusable as the following.

public bool IsReusable

        {

            get

            {

                return true;

            }

        }

 
  • The ProcessRequest method is going to check for query string name called listname. Then get the SPList object, retrieve the details we want in the ListDetails class, then JavaScriptSerialize our class to write back our results out as JavaScript.

  public void ProcessRequest(HttpContext context)

        {

            var listName = string.Empty;

            //Obtain the Query String

            if (context.Request.QueryString["listName"] != null)

            {

                listName = context.Request.QueryString["listName"];

            }

            string results = String.Empty;

            try

            {

                if (!string.IsNullOrEmpty(listName))

                {

                    //Get the SPList object.

                    SPList list = SPContext.Current.Web.Lists.TryGetList(listName);

                    if (list != null)

                        //Pass SPList to get details and convert to JSON string.

                        results = GetJSON(list);

                    else

                    {

                        throw new Exception("List Not Found!");

                    }

                }

            }

            catch (Exception ex)

            {

                throw ex;

            }

            context.Response.Write(results);

        }

        private string GetJSON(SPList list)

        {

            //Initialise the JavaScript initializer

            System.Web.Script.Serialization.JavaScriptSerializer serializer = new System.Web.Script.Serialization.JavaScriptSerializer();

            //Get ListDetails Class and obtain values.

            var curList = new ListDetails();

            curList.Title = list.Title;

            curList.Description = list.Description;

            curList.DefaultViewURL = list.DefaultViewUrl;

            curList.ItemCount = list.ItemCount.ToString();

            //Return Class Serialized for JavaScript.

            return serializer.Serialize(curList);

        }

If you save and deploy your project as is, and navigate to <Site>/_layouts/GenericHandler/ListHandler.ashx?listName=<listName>, replacing site and listName with your details you should see some results. For example I have passed in the list name Shared Documents as I’m using a TeamSite for the below results.

To create the front end, we are going to create a simple application page. Create a page called ListCaller.aspx.

  • Within the Content PlaceHolderMain put a Label, TextBox and Button. Below we will display the 4 values Title, Description, DefaultURL and ItemCount.

<asp:Content ID="Main" ContentPlaceHolderID="PlaceHolderMain" runat="server">

    <asp:Label ID="lblInstructions" runat="server" Text="Please enter a name of a list"></asp:Label>

    <asp:TextBox ID="txtName" runat="server"></asp:TextBox>

    <asp:Button ID="btnGetList" runat="server" Text="Get List Details" />

    <div id="listTitle"></div>

    <div id="listDescription"></div>

    <div id="listDefaultURL"></div>

    <div id="listItemCount"></div>

</asp:Content>

  • Create a JavaScript file. I have called mine ListCaller.js, this file is stored in layouts directory too.

  • Open this file. We are going to create a simple ajax call to our handler. On success it will return data from the handler, or it will throw an error. Create a function called getListDetails that is expecting the list name.

function getListDetails(listName) {

       jQuery.ajax({

        url: '/_layouts/GenericHandler/ListHandler.ashx',

        data: 'listName=' + listName,

        dataType: "json",

        contentType: "application/json; charset=utf-8",

        method: 'GET',

        success: function (data) {

            jQuery('#listTitle').html('<b>Title: </b>' + data.Title);

            jQuery('#listDescription').html('<b>Description: </b>' + data.Description);

            jQuery('#listDefaultURL').html('<b>Default URL: </b>' + data.DefaultViewURL);

            jQuery('#listItemCount').html('<b>Item Count: </b>' + data.ItemCount);

        },

        error: function (jqXHR, textStatus, errorThrown) {

            jQuery('#listTitle').text('Failed');

            jQuery('#listDescription').text('');

            jQuery('#listDefaultURL').text('');

            jQuery('#listItemCount').text('');

        }

    });

};

  • Now we need to call this JavaScript method from our aspx page. Open up your aspx page. Within the PlaceHolderAdditionalPageHead we need to add a link to the ListCaller.js file. Also here I’m going to add a JavaScript function that gets the value from within textbox and calls getListDetails().

<asp:Content ID="PageHead" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">

<script type="text/javascript" src="/_layouts/GenericHandler/js/ListCaller.js"></script>

    <script>

        function details() {

          getListDetails(jQuery('#' + '<%=txtName.ClientID%>').val())

        };

    </script>

</asp:Content>

  • Lastly add an OnClientClick to your Button. Your <asp:Button will now look like below.

<asp:Button ID="btnGetList" runat="server" Text="Get List Details" OnClientClick="javascript:details();return false;"/>

By navigating to the application page, and type in a name of a library or list can click Get List Details it will return the details of that list.

Using HTML template and JQuery.clone()



In a recent proof of concept I was putting together, I needed a way to display different data on the fly, but with the same layout. As it was for a SharePoint 2013 SharePoint hosted app, all I had was Java Script and HTML to perform my task. Originally when I first started to write the code I was building the DIV, SPAN and other objects in the code, line by line. When I was about to start the code for the 3rd section, I thought there must be an easier way.

And there is! Create a template on the HTML page to clone when needed, but have it hidden.

Below is an image of the template if it was visible on the page.


If we take a look at the HTML for it, you will see it just a simple HTML layout. The div with the ID “details” is where we will be cloning the template to in code. (Please note I’m not a designer and there are probably huge mistakes with this. Happy to be corrected).

</pre>
<div id="details"></div>
<div class="hiddenTemplate">
<div id="Template">
<h2><label class="header">This is Header</label></h2>
<div class="buttonDiv"><button class="deleteButton" type="button">Delete</button></div>
<b>ID: </b><span class="itemId">ID</span>

<b>Name: </b><span class="&quot;itemName">Name</span>

<span class="descriptionArea">
<b>Description: </b><span class="itemDescription">Description</span>

</span>
<span class="parentArea">
<b>Parent ID: </b><span class="itemParentID">Parent ID</span>

<b>Parent Name: </b><span class="&quot;itemParentName">Parent Name</span>

</span>
<div class="addItem"><span class="addItemInstruction">Add Items here</span>

<input class="addItemInput" type="text" />
<span class="addButtonSpan"><button class="addButton" type="button">Add</button></span></div>
</div>
</div>
<pre>

Using css I’m able to style and hide the template from the user on the page.

.hiddenTemplate {
display:none;
}
.deleteButton
{
margin-left:0px;
margin-top:2px;
}
.addItem
{
border:1px dotted lightgray
}
.parentArea
{
.display:none;
}

In your JavaScript code, once you have obtained your data you simply grab a copy of the template, clone it using $(‘#Template’).clone(); and then update any details before adding it to the “details” div.

I needed to use the template 4 times and show it slight different for each one, and also depending on values returned. Below is the code for just one of my returned items, however you can see how simple in a loop you could reuse this, or design it differently depending on the data coming back.

 //TermDetails.
 //Get if it is the root term, Term Name and Description.
 var root = termDetails.get_isRoot();
 var termName = termDetails.get_name();
 var termDescription = termDetails.get_description();

//Find template and Clone it.
 var termTemplate = $('#Template').clone();
 //Get the header, and update the text within the clone template.
 termTemplate.find('.header').text("Term Details");
 //Give the template a unique ID, so it can be referenced later in code if needed.
 termTemplate.attr('id', "boxDetails" + termId.toString());

//Show the display button, and give it an on click function.
 termTemplate.find('.deleteButton').show().on('click', function () { removeTerm(termStoreId, groupId, termSetId, termId) });
 //Display Item ID.
 termTemplate.find('.itemId').text(termId.toString());
 //Display Item Name.
 termTemplate.find('.itemName').text(termName);
 //If there is no description don't show it
 if (termDescription == "")
 termTemplate.find('descriptionArea').hide();
 else
 termTemplate.find('itemDescription').text(termDescription.toString());

//If not root, then it has a parent, find out who the parent is.
 var parent;
 if (!root) {
 parent = termDetails.get_parent();
 context.load(parent);
 context.executeQueryAsync(
 function () {
 //success getting parent
 var parentName = parent.get_name();
 var parentId = parent.get_id();
 //Show Parent Area.
 termTemplate.find('.parentArea').show();
 //Display Parent ID.
 termTemplate.find('.itemParentID').text(parentId.toString());
 //Display Parent Name.
 termTemplate.find('.itemParentName').text(parentName);
 }, function () {
 //failed
 console.log("Error obtaining parent details");
 });
 }

//Put instructions for the add button
 termTemplate.find('.addItemInstructions').text("Please enter the name of the Term to add to " + termName);
 //Give input box an ID to reference later
 termTemplate.find('.addItemInput').attr('id', 'createSubTermTextBox');
 //Give button an ID.
 termTemplate.find('.addButton').attr('id', 'createSubTermButton');
 //Change text of button.
 termTemplate.find('.addButton').attr('value', 'Add Term');
 //Give the button an on click function
 termTemplate.find('.addButton').on('click', function () { addSubTerm(termStoreId, groupId, termSetId, termId); });

//Add the template to the details tag.
 $('#details').append(termTemplate);
 

With some additional css the final outcome looks like below.
 

Truncating multiple lines of text SPField in a list


One thing I have noticed with users for SharePoint, is that they like using lists as if it was excel. The views have all the columns in it, and the notes/description fields are filled with lines of text, which blows out the page. You are lucky to see any more than 2 items on the page, and after a lot of scrolling you reach the bottom. Wouldn’t it be nice to have all that text truncated in the list? Wouldn’t it also be better that your users could modify the settings so that they could change the (Show More) link, or display more words? Wouldn’t it be cruel of me now not to tell you how to do this, after you have Google this and found out that this is what you wanted? (Yes, but I’m not going to do that). I have been able to do this by adding a WebPart (that is hidden) onto the page that inserts JavaScript into the page. The JavaScript finds all multiple lines of text fields and then perform Jquery command (via a plugin) on them to truncate the text. The WebPart has configurable settings to allow it to be changed by the user. The Jquery plugin credit goes to Keith Wood. You can download the plugin directly from his site http://keith-wood.name/more.html. This site also has documentation on how to use the plugin.

Building the WebPart.

Prerequisite to this WebPart is to ensure Jquery is enabled on the page you are adding the WebPart to. I have written a blog on adding JQuery to your site using a Sandbox and Farm Solution that will help you.

  1. Create a new Blank SharePoint Project in Visual Studio, set as a Farm solution.
  2. Add a Visual WebPart. This will automatically create a Feature for you. Rename the feature, and remember to add the Visual WebPart to the feature. (Number of times I’ve been caught out by this).
  3. Add the jquery.more.min.js file from http://keith-wood.name/more.html to your Visual WebPart. In the properties change the deployment type to ClassResource and the Deployment location path to Scripts\

    Note: The reason why we use ClassResource instead of the Layouts folder is because it will be cached. The script will be loaded from the server only the first time it is referenced, and all references therafter will be loaded from the browser’s cache.
  4. As it stands, build and deploy your webpart. You don’t need to add it to the page yet. Navigate to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\wpresources you should find a folder in there with the same name as your project. Open that folder and inside you will have a folder named similar to 1.0.0.0__a08a25e201fade25. Inside that folder you will see your Scripts folder, and lastly inside that you will see the jquery.more.min.js file. Take note of the path from the folder of the project name to the JavaScript file. In my case this is TruncatingNotes\1.0.0.0__a08a25e201fade25\Scripts\jquery.more.js
  5. Open the user control .ascx page. Add the line (remembering to put your path name)
    <script type="text/javascript" src="_wpresources/TruncatingNotes/1.0.0.0__a08a25e201fade25\Scripts\jquery.more.js"></script>
    
  6. Add a new JavaScript file to the project and call it expand.js. In the properties change the deployment type to ClassResource and the Deployment location path to Scripts\
  7. In the expand.js file put the following JavaScript
    function collapseText(group){
    if(!group) group = "#MSO_ContentTable";
    $(group + " td.ms-vb2&gt;div").each(function (i, e){
    $(e).more({ length: myLength, leeway: myLeeway, wordBreak: myWordBreak, ellipsisText: myEllipsisText, moreText: myMoreText, lessText: myLessText });
    });
    }
    $(function() {
     collapseText();
    });
    

What the code above is actually doing is looking for each Notes field and then with the jquery plugin (e).more() passing in the user parameters. This in turn truncates each note field. (Sorry wordpress for some reason wouldn’t let me continue numbering, so back to 1.)

  1. Add a new css file to the project and call it Truncate.css. In the properties change the deployment type to ClassResource and the Deployment location path to Styles\.
  2. In the Truncate.css add the following css:
    .more-hidden {display: none;}
    .more-link {margin-left: 1em;padding: 0.125em;-moz-border-radius: 0.25em;-webkit-border-radius: 0.25em;text-decoration: none;}
    .more-link img {border: none;}
    

    The above css names are defined in the Jquery plugin documentation.

  3. Back on the user control .ascx page, now add the script and css link to the two new files. (remember to put your path name)
    <script type="text/javascript" src="_wpresources/TruncatingNotes/1.0.0.0__a08a25e201fade25\Scripts\expand.js"></script>
    <link type="text/css" rel="Stylesheet" href="_wpresources/TruncatingNotes/1.0.0.0__a08a25e201fade25\Styles\Truncate.css"></script>
    
  4. Now we need to set up the WebPart to have user configurable settings. Open up the WebPart .cs file. Add the following user settings within the WebPart class.
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("Text Length"),
    WebDescription("The number of characters shown after truncation.")]
    public UInt32 TextLength { get; set; }
    
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("Text Leeway"),
    WebDescription("The number of additional characters allowed before truncation is actually applied.")]
    public UInt32 TextLeeway { get; set; }
    
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("Word Break"),
    WebDescription("When unselected the text is truncated at the character specified by Text Length, regardless of its position within a word. When selected the text is truncated at the first word boundary before Text Length")]
    public bool AllowWordBreak { get; set; }
    
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("Ellipsis Text"),
    WebDescription("The text used at the end of a truncation to indicate that more text is available.")]
    public string EllipsisText { get; set; }
    
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("More Text"),
    WebDescription("The text shown on the link to expand the text.")]
    public string MoreText { get; set; }
    
    [Category("Truncated Settings"),
    WebBrowsable(true),
    Personalizable(PersonalizationScope.Shared),
    WebDisplayName("Less Text"),
    WebDescription("The text shown on the link to collapse the text.")]
    public string LessText { get; set; }
    
  5. To ensure that we can access these parameters within the UserControl replace what is inside CreateChildControls() method with the following
    var control = Page.LoadControl(_ascxPath) as TruncatedWebPartUserControl;
    if(control != null)
    {
     control.WebPart = this;
     Controls.Add(control);
    }
     
  6. Open up the .cs of the UserControl. The following code will create a global variable for the WebPart so we can retrieve the parameter values, then on a PreRender we will create the JavaScript variables and add them to the page. As you can see the JavaScript variables match the variables in the expand.js file. Lastly if using in production environment I would escape your parameters before passing them. You don’t want a script attack.

    public partial class TruncatedWebPartUserControl : UserControl
    {
    public TruncatedWebPart WebPart { get; set; }
    public TruncatedWebPartUserControl()
    {
     this.PreRender += new EventHandler(TruncatedWebPartUserControl_PreRender);
    }
     void TruncatedWebPartUserControl_PreRender(object sender, EventArgs e) {
    //Create the JavaScript Variables then add to the page.
    var embeddedScript = String.Format("<script language=\"javascript\">
    var myLength = {0};" +
    "var myLeeway = {1};" +
    "var myWordBreak = {2};" +
    "var myEllipsisText = \"{3}\";" +
    "var myMoreText = \"{4}\";" +
    "var myLessText = \"{5}\";" +
    "</script>",
    WebPart.TextLength,
    WebPart.TextLeeway,
    WebPart.AllowWordBreak.ToString().ToLower(),
    WebPart.EllipsisText,
    WebPart.MoreText,
    WebPart.LessText);
    ClientScriptManager cs = Page.ClientScript;
    if (!cs.IsClientScriptBlockRegistered("Parameters"))
    cs.RegisterClientScriptBlock(
    this.GetType(),
    "Parameters",
    embeddedScript);
    }
    }
    

    You could just deploy the WebPart and all would work fine once you have configured it, but this next step will pre-configure the WebPart when it is added to the page. Open the .webpart file and add these additional properties. Because I used UInt I needed to include the full type.

    <property name="Hidden" type="bool">True</property>
    <property name="TextLength" type="System.UInt32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089>20</property>
    <property name="TextLeeway" type="System.UInt32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">5</property>
    <property name="AllowWordBreak" type="bool">True</property>
    <property name="EllipsisText" type="string">...</property>
    <property name="MoreText" type="string">(Show More)</property>
    <property name="LessText" type="string">(Show Less)</property>
    
  7. Deploy the solution to your server, activate the feature if necessary. Goto a library that has long multiple lines of text showing. Add the webpart to the page and save the page.

    To download the project without typing it all out yourself http://sdrv.ms/YYyQY3 There is also some other bits of code that I’ve added to the project to make it act and feel like a proper WebPart solution.

Inserting JQuery into your SharePoint site as a feature (Sandbox)


In this example I’m going to show you how to create a feature that will insert the JQuery file directly into SharePoint Library folder and add a script link directly to the page. The advantage of making it a site feature allows you to only have the scripts included if you know jquery is being used on the site. Otherwise you are just loading up the JQuery files on each page for no reason. First you will need to download the latest jquery file from the jquery site. http://jquery.com/ . This example is the Sandbox way of doing it.

 

  1. Open visual studio and chose File > New Project
  2. Select SharePoint > 2010 > Empty SharePoint Project. Name the project JQuerySandbox and click OK.
  3. In the next dialog, enter a valid URL and select Sandbox
    solution. Click Finish.
  4. Right click the project in the Solution Explorer and select Add > New Item. Select a Module and name it Asset. Click Add.
  5. Delete the Sample.txt file. Add your Jquery file to the Assets module. Right click Assets select Add > Existing Items. Find the location of your downloaded JQuery file and add it.
  6. Now open the elements.xml and change the file to look like the following.
    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Module Name="Assets" Url="SiteAssets">
    <File Path="Assets\jquery-1.7.1.min.js" Url="jQuery/jquery-1.7.1.min.js"/>
    </Module>
    </Elements>
    

    The Url attribute to the Module element tells SharePoint to deploy this module to the SiteAssets Library.

  7. Right click your project in the Solution Explorer and select Add > New Item. Select Empty Element and call it ScriptElement.
  8. Open the Elements.xml file which sites under the ScriptElements item and add the following.
    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
      <CustomAction ScriptSrc="~SiteCollection/SiteAssets/jQuery/jquery-1.7.1.min.js"
                    Location="ScriptLink"
                    Sequence="100">
      </CustomAction>
    </Elements>
    
  9. You might have noticed that when you added the Module a Feature was created. Rename the feature to JQuerySandbox. Double click the JQuery.feature folder, this will allow you to change the title and description. Change the Scope to Site and also add the Asset module and ScriptElement element as items in the feature.
  10. That’s it. Deploy your solution. On your site you should see it has been deployed and activated.
  11. To ensure it is currently working, you need to see if the JQuery script is loaded on the page. If using Internet Explorer, press F12 and load up the developers tools. In the Script tab, you can select which script to debug, you should see the jquery file.
  12. If you deactivate the feature, and then check again, you will find it isn’t showing.
  13. Lastly, if you deployed this to a Team Site for example, there is a Site Asset library displayed when viewing all Site Content. If you navigate to the folder you should see the jQuery folder and the jquery file which has been deployed by your feature.