Dev Notes

Notes on Development with Microsoft Technologies

Utilizing TypeScript with SharePoint 2013 JavaScript Object Model

4 Comments

Today I began an experiment to see if I could utilize TypeScript to improve my JavaScript code for a solution to retrieve discussions from a SharePoint 2013 Site. I was surprised at how much TypeScript has been able to help me write fewer errors the first time around.

Note: My assumption in this article is that you have some an understanding of the following concepts: SharePoint 2013 JavaScript Client Object Model, JavaScript classes and modules, jQuery and Handlebars.js or other template tools.

The experiment started in SharePoint Designer 2013 with a brand new ASPX page. After clearing out a few items, here’s the resulting markup:

<%@ Page Language="C#" inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" title="Untitled 1" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>
<%@ Register tagprefix="SharePoint" namespace="Microsoft.SharePoint.WebControls" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="WebPartPages" namespace="Microsoft.SharePoint.WebPartPages" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="wssuc" tagname="Welcome" src="~/_controltemplates/15/Welcome.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>

<head runat="server">
	<meta name="ProgId" content="SharePoint.WebPartPage.Document" />
	<meta name="WebPartPageExpansion" content="full" />
	<meta name="GENERATOR" content="Microsoft SharePoint" />
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=10"/>
</head>
<body>	
	<div id="Discussions"></div>
</body>
</html>

The basic idea is to retrieve a list of discussions from a specified list and render them onto the page. To do this, I want to make use of the SharePoint 2013 JavaScript Client Object Model. I’m also going to utilize jQuery and Handlebars.js to actually render the returned items.

You are not required to have Visual Studio installed to begin working with TypeScript. Just head to TypeScript and click on the Play link. This will open up a fully functional TypeScript editor. I recommend exploring the tutorials provided on the site and watching the TypeScript: Application Scale JavaScript video presented by Anders Hejlsberg.

I know that my code will need to handle a few objects: Topics and Replies. This is pretty easy to do in TypeScript.

class DiscussionItem{
	TopicID: Number;
	Subject:String;
	Body:String;
	ReplyCount: Number;
	Author: String;
	CreateDate: Date;
	IsQuestion: Boolean;
	IsFeatured: Boolean;
	IsAnswered: Boolean;
	
	constructor (contextItem:any){
              this.TopicID = contextItem.get_id();
              this.Subject = contextItem.get_item("Title");
              this.Body = contextItem.get_item("Body");
              this.ReplyCount = contextItem.get_item("ItemChildCount");
              this.Author = contextItem.get_item("Author").get_lookupValue();
              this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
              this.IsQuestion = contextItem.get_item("IsQuestion");
              this.IsFeatured = contextItem.get_item("IsFeatured");
              this.IsAnswered = contextItem.get_item("IsAnswered");
	}
}
		
class ReplyItem{
	TopicID: Number;
	ReplyID: Number;
	Body: String;
	Author: String;
	CreateDate: Date;
	IsAnswer: Boolean;
	
	constructor (contextItem:any){
		this.TopicID = contextItem.get_item("ParentItemID");
		this.ReplyID = contextItem.get_id();
		this.Body = contextItem.get_item("Body");
                this.Author = contextItem.get_item("Author").get_lookupValue();
             	this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
		this.IsAnswer = contextItem.get_item("IsAnswer");
	}
}

First, the DiscussionItem class is a simple representation of the Discussion contained in a Discussion List. To create a new DiscussionItem, I pass in a SP.ListItem and allow the class to pull the properties directly from the retrieved item. The ReplyItem works in the same way.

Because these items are models of my data, it would be nice to encapsulate that within a namespace. This can be done by creating a module. Wrapping the above classes into a module should accomplish the goal.

module Model {
	class DiscussionItem{
              //...
	}
	
	class ReplyItem{
              //...
	}
}

However, neither of the classes are available since they are scoped as local variables within the module. You must indicate which items should be exposed outside of the module by using the export keyword in TypeScript.

	module Model {
		export class DiscussionItem{
                       //...
		}
		
		export class ReplyItem{
                       //...
		}
	}

Now that my models have been created, focus turns to getting the data. I created a new class and decided to encapsulate everything within a module. My result is the following:

module Portal{
	export module Model {
		export class DiscussionItem{
                       //...      
		}
		
		export class ReplyItem{
                       //...
		}
	}
	
	export module Data {
		export class DiscussionDataService {
			retrievedItems;
			constructor(public ctx:any, public discussionListName: string){
				
			}
			
			GetDiscussionTopics(onSuccess:RenderTopicsCallback, onFailed:FailureCallback) {
				var web = this.ctx.get_web(),
				list = web.get_lists().getByTitle(this.discussionListName);
				var instance = this;	
				var query = new SP.CamlQuery();
				query.set_viewXml("<View/>");
				 
				var items = list.getItems(query);
				this.ctx.load(items);
				this.ctx.executeQueryAsync(
				 	function(s,a){
					   instance.retrievedItems = items;
					   instance.OnTopicsRetrieved(onSuccess);
					 },
					 function (s,a){
					   onFailed(s,a);
					 }
				 );
			}
			
			GetRepliesForTopic(TopicID:Number, onSuccess: RenderRepliesCallback, onFailed:FailureCallback){
				var web = this.ctx.get_web(),
				list = web.get_lists().getByTitle(this.discussionListName);
				var instance = this;	
				var query = new SP.CamlQuery();
				query.set_viewXml('<View Scope=\'RecursiveAll\'><Query><Where><Eq><FieldRef Name=\'ParentItemID\'/><Value Type=\'Number\'>' + TopicID + '</Value></Eq></Where></Query><RowLimit>100</RowLimit></View>');
				 
				 var items = list.getItems(query);
				 this.ctx.load(items);
				 this.ctx.executeQueryAsync(
				 	function(s,a){
					 	instance.retrievedItems = items;
						 instance.OnRepliesRetrieved(onSuccess);
					 },
					 function (s,a){
					 	onFailed(s,a);
					 }
				 );		
			}
			
			OnTopicsRetrieved(Callback:RenderTopicsCallback)	{
				var iterator = this.retrievedItems.getEnumerator();
				var items: Model.DiscussionItem[] = [];
				while(iterator.moveNext()){
					var item = iterator.get_current();
					var d = new Model.DiscussionItem(item);
					items.push(d);
				}
				Callback(items);
			}
			
			OnRepliesRetrieved(Callback:RenderRepliesCallback){
				var iterator = this.retrievedItems.getEnumerator();
				var items: Model.ReplyItem[] = [];
				while(iterator.moveNext()){
					var item = iterator.get_current();
					var d = new Model.ReplyItem(item);
					items.push(d);
				}
				Callback(items);
			}
		}
	}
}

Another nice feature of TypeScript is the ability to provide interfaces that define the objects you expect to utilize. In the above code, you will see a reference to RenderTopicsCallback, FailedCallback and RenderRepliesCallback. These can be defined through interfaces to insure the methods passed conform to a specific signature.

interface RenderTopicsCallback {
	(item: Portal.Model.DiscussionItem[]): any;
}
interface RenderRepliesCallback{
	(e: Portal.Model.ReplyItem[]): any;
}
interface FailureCallback{
	(s,a):any;
}

The final TypsScript code is:

interface RenderTopicsCallback {
	(item: Portal.Model.DiscussionItem[]): any;
}
interface RenderRepliesCallback{
	(e: Portal.Model.ReplyItem[]): any;
}
interface FailureCallback{
	(s,a):any;
}

module Portal{
	export module Model {
		export class DiscussionItem{
			TopicID: Number;
			Subject:String;
			Body:String;
			ReplyCount: Number;
			Author: String;
			CreateDate: Date;
			IsQuestion: Boolean;
			IsFeatured: Boolean;
			IsAnswered: Boolean;
			
			constructor (contextItem:any){
	            this.TopicID = contextItem.get_id();
	            this.Subject = contextItem.get_item("Title");
	            this.Body = contextItem.get_item("Body");
	            this.ReplyCount = contextItem.get_item("ItemChildCount");
	            this.Author = contextItem.get_item("Author").get_lookupValue();
	           	this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
	            this.IsQuestion = contextItem.get_item("IsQuestion");
	            this.IsFeatured = contextItem.get_item("IsFeatured");
	            this.IsAnswered = contextItem.get_item("IsAnswered");
			}
		}
		
		export class ReplyItem{
			TopicID: Number;
			ReplyID: Number;
			Body: String;
			Author: String;
			CreateDate: Date;
			IsAnswer: Boolean;
			
			constructor (contextItem:any){
				this.TopicID = contextItem.get_item("ParentItemID");
				this.ReplyID = contextItem.get_id();
				this.Body = contextItem.get_item("Body");
	            this.Author = contextItem.get_item("Author").get_lookupValue();
	           	this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
				this.IsAnswer = contextItem.get_item("IsAnswer");
			}
		}
	}
	
	export module Data {
		export class DiscussionDataService {
			retrievedItems;
			constructor(public ctx:any, public discussionListName: string){
				
			}
			
			GetDiscussionTopics(onSuccess:RenderTopicsCallback, onFailed:FailureCallback) {
				var web = this.ctx.get_web(),
				    list = web.get_lists().getByTitle(this.discussionListName);
				var instance = this;	
				 var query = new SP.CamlQuery();
				 query.set_viewXml("<View/>");
				 
				 var items = list.getItems(query);
				 this.ctx.load(items);
				 this.ctx.executeQueryAsync(
				 	function(s,a){
					 	instance.retrievedItems = items;
						 instance.OnTopicsRetrieved(onSuccess);
					 },
					 function (s,a){
					 	onFailed(s,a);
					 }
				 );
			}
			
			GetRepliesForTopic(TopicID:Number, onSuccess: RenderRepliesCallback, onFailed:FailureCallback){
				var web = this.ctx.get_web(),
				    list = web.get_lists().getByTitle(this.discussionListName);
				var instance = this;	
				 var query = new SP.CamlQuery();
				 query.set_viewXml('<View Scope=\'RecursiveAll\'><Query><Where><Eq><FieldRef Name=\'ParentItemID\'/><Value Type=\'Number\'>' + TopicID + '</Value></Eq></Where></Query><RowLimit>100</RowLimit></View>');
				 
				 var items = list.getItems(query);
				 this.ctx.load(items);
				 this.ctx.executeQueryAsync(
				 	function(s,a){
					 	instance.retrievedItems = items;
						 instance.OnRepliesRetrieved(onSuccess);
					 },
					 function (s,a){
					 	onFailed(s,a);
					 }
				 );		
			}
			
			OnTopicsRetrieved(Callback:RenderTopicsCallback)	{
				var iterator = this.retrievedItems.getEnumerator();
				var items: Model.DiscussionItem[] = [];
				while(iterator.moveNext()){
					var item = iterator.get_current();
					var d = new Model.DiscussionItem(item);
					items.push(d);
				}
				Callback(items);
			}
			
			OnRepliesRetrieved(Callback:RenderRepliesCallback){
				var iterator = this.retrievedItems.getEnumerator();
				var items: Model.ReplyItem[] = [];
				while(iterator.moveNext()){
					var item = iterator.get_current();
					var d = new Model.ReplyItem(item);
					items.push(d);
				}
				Callback(items);
			}
		}
	}
}

TypeScript generates the following JavaScript:

var Portal;
(function (Portal) {
    (function (Model) {
        var DiscussionItem = (function () {
            function DiscussionItem(contextItem) {
                this.TopicID = contextItem.get_id();
                this.Subject = contextItem.get_item("Title");
                this.Body = contextItem.get_item("Body");
                this.ReplyCount = contextItem.get_item("ItemChildCount");
                this.Author = contextItem.get_item("Author").get_lookupValue();
                this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
                this.IsQuestion = contextItem.get_item("IsQuestion");
                this.IsFeatured = contextItem.get_item("IsFeatured");
                this.IsAnswered = contextItem.get_item("IsAnswered");
            }
            return DiscussionItem;
        })();
        Model.DiscussionItem = DiscussionItem;

        var ReplyItem = (function () {
            function ReplyItem(contextItem) {
                this.TopicID = contextItem.get_item("ParentItemID");
                this.ReplyID = contextItem.get_id();
                this.Body = contextItem.get_item("Body");
                this.Author = contextItem.get_item("Author").get_lookupValue();
                this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
                this.IsAnswer = contextItem.get_item("IsAnswer");
            }
            return ReplyItem;
        })();
        Model.ReplyItem = ReplyItem;
    })(Portal.Model || (Portal.Model = {}));
    var Model = Portal.Model;

    (function (Data) {
        var DiscussionDataService = (function () {
            function DiscussionDataService(ctx, discussionListName) {
                this.ctx = ctx;
                this.discussionListName = discussionListName;
            }
            DiscussionDataService.prototype.GetDiscussionTopics = function (onSuccess, onFailed) {
                var web = this.ctx.get_web(), list = web.get_lists().getByTitle(this.discussionListName);
                var instance = this;
                var query = new SP.CamlQuery();
                query.set_viewXml("<View/>");

                var items = list.getItems(query);
                this.ctx.load(items);
                this.ctx.executeQueryAsync(function (s, a) {
                    instance.retrievedItems = items;
                    instance.OnTopicsRetrieved(onSuccess);
                }, function (s, a) {
                    onFailed(s, a);
                });
            };

            DiscussionDataService.prototype.GetRepliesForTopic = function (TopicID, onSuccess, onFailed) {
                var web = this.ctx.get_web(), list = web.get_lists().getByTitle(this.discussionListName);
                var instance = this;
                var query = new SP.CamlQuery();
                query.set_viewXml('<View Scope=\'RecursiveAll\'><Query><Where><Eq><FieldRef Name=\'ParentItemID\'/><Value Type=\'Number\'>' + TopicID + '</Value></Eq></Where></Query><RowLimit>100</RowLimit></View>');

                var items = list.getItems(query);
                this.ctx.load(items);
                this.ctx.executeQueryAsync(function (s, a) {
                    instance.retrievedItems = items;
                    instance.OnRepliesRetrieved(onSuccess);
                }, function (s, a) {
                    onFailed(s, a);
                });
            };

            DiscussionDataService.prototype.OnTopicsRetrieved = function (Callback) {
                var iterator = this.retrievedItems.getEnumerator();
                var items = [];
                while (iterator.moveNext()) {
                    var item = iterator.get_current();
                    var d = new Model.DiscussionItem(item);
                    items.push(d);
                }
                Callback(items);
            };

            DiscussionDataService.prototype.OnRepliesRetrieved = function (Callback) {
                var iterator = this.retrievedItems.getEnumerator();
                var items = [];
                while (iterator.moveNext()) {
                    var item = iterator.get_current();
                    var d = new Model.ReplyItem(item);
                    items.push(d);
                }
                Callback(items);
            };
            return DiscussionDataService;
        })();
        Data.DiscussionDataService = DiscussionDataService;
    })(Portal.Data || (Portal.Data = {}));
    var Data = Portal.Data;
})(Portal || (Portal = {}));

Back on the ASPX page, I need to make sure all of the scripts I plan to use are loaded. I added CDN references for jQuery and Handlebars.js. I also added the necessary scripts to make use of the JavaScript Object Model.

<%@ Page Language="C#" inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" title="Untitled 1" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>
<%@ Register tagprefix="SharePoint" namespace="Microsoft.SharePoint.WebControls" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="WebPartPages" namespace="Microsoft.SharePoint.WebPartPages" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="wssuc" tagname="Welcome" src="~/_controltemplates/15/Welcome.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>

<head runat="server">
	<meta name="ProgId" content="SharePoint.WebPartPage.Document" />
	<meta name="WebPartPageExpansion" content="full" />
	<meta name="GENERATOR" content="Microsoft SharePoint" />
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=10"/>
	<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
	<script type="text/javascript" src="/_layouts/15/init.js"></script>
	<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
	<script type="text/javascript" src="/_layouts/15/sp.js"></script>
</head>
<body>	
	<div id="Discussions"></div>
	<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.2.min.js"></script>
	<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js"></script>
</body>
</html>

For simplicity, the generated javascript from TypeScript will be inserted into a script tag directly on this page. Next, the script is wired up to execute. The call is made to wait until the client context is ready and then execute the sharePointReady function defined in the code. This code creates a new instance of the Portal.Data.DiscussionDataService passing in the current context and the name of the discussion list to retrieve. The data service is used to get the list of topics and passes in a reference to the RenderDiscussion function.

<script language="text/javascript">
var Portal;
(function (Portal) {
      // Omitted for Brevetiy
})(Portal || (Portal = {}));

SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointReady);

var dataService;
function sharePointReady() {
    dataService = new Portal.Data.DiscussionDataService(SP.ClientContext.get_current(), "My Discussions");
    dataService.GetDiscussionTopics(RenderDiscussion, HandleError);
}

function RenderDiscussion(items) {
    var source = $("#discussionTopicTemplate").html();
    var template = Handlebars.compile(source, { noEscape: true });
    var html = template(items);
    $("#Discussions").html(html);
}

function RenderReplies(items) {
   //...
}

function HandleError(s, a) {
}

</script>

The only thing left is to create the handlebars template that is used to render the content.

	<script id="discussionTopicTemplate" type="text/x-handlebars-template">
	<h1>Conversations</h1>
	<div id="DiscussionList">
	{{#each this}}
		<h2>{{Subject}} (Replies: {{ReplyCount}})</h2>
		<div><div>{{Body}}<div><div>Created by {{Author}} on {{CreateDate}}</div></div>
	{{/each}}
	</div>
	</script>

Summary

This exercise really showed me the power that exists with TypeScript. I can see how in much larger applications, TypeScript can be a valuable tool, at least during development. If you include additional definitions from Definitely Typed in your project — working with libraries such as jQuery, KnockOut and many others becomes easier to manage. However, there is no definition library for SharePoint’s JavaScript Object Model. That would be a welcome addition!

The full source code:

<%@ Page Language="C#" inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" title="Untitled 1" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>
<%@ Register tagprefix="SharePoint" namespace="Microsoft.SharePoint.WebControls" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="WebPartPages" namespace="Microsoft.SharePoint.WebPartPages" assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register tagprefix="wssuc" tagname="Welcome" src="~/_controltemplates/15/Welcome.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>

<head runat="server">
	<meta name="ProgId" content="SharePoint.WebPartPage.Document" />
	<meta name="WebPartPageExpansion" content="full" />
	<meta name="GENERATOR" content="Microsoft SharePoint" />
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=10"/>
	<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
	<script type="text/javascript" src="/_layouts/15/init.js"></script>
	<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
	<script type="text/javascript" src="/_layouts/15/sp.js"></script>
</head>
<body>
	
	<div id="Discussions"></div>
	<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.2.min.js"></script>
	<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js"></script>
	<script type="text/javascript">
	var Portal;
	(function (Portal) {
		(function (Model) {
			var DiscussionItem = (function () {
				function DiscussionItem(contextItem) {
					this.TopicID = contextItem.get_id();
					this.Subject = contextItem.get_item("Title");
					this.Body = contextItem.get_item("Body");
					this.ReplyCount = contextItem.get_item("ItemChildCount");
					this.Author = contextItem.get_item("Author").get_lookupValue();
					this.CreateDate = new Date(contextItem.get_item("Created_x0020_Date"));
					this.IsQuestion = contextItem.get_item("IsQuestion");
					this.IsFeatured = contextItem.get_item("IsFeatured");
					this.IsAnswered = contextItem.get_item("IsAnswered");
				}
				return DiscussionItem;
			})();
			Model.DiscussionItem = DiscussionItem;

			var ReplyItem = (function () {
				function ReplyItem(contextItem) {
					this.TopicID = contextItem.get_item("ParentItemID");
					this.ReplyID = contextItem.get_id();
					this.Body = contextItem.get_item("Body");
					this.CreateDate = contextItem.get_item("Created");
					this.IsAnswer = contextItem.get_item("IsAnswer");
					this.Author = contextItem.get_item("Author");
				}
				return ReplyItem;
			})();
			Model.ReplyItem = ReplyItem;
		})(Portal.Model || (Portal.Model = {}));
		var Model = Portal.Model;

		(function (Data) {
			var DiscussionDataService = (function () {
				function DiscussionDataService(ctx, discussionListName) {
					this.ctx = ctx;
					this.discussionListName = discussionListName;
				}
				DiscussionDataService.prototype.GetDiscussionTopics = function (onSuccess, onFailed) {
					var web = this.ctx.get_web(), list = web.get_lists().getByTitle(this.discussionListName);
					var instance = this;
					var query = new SP.CamlQuery();
					query.set_viewXml("<View/>");

					var items = list.getItems(query);
					this.ctx.load(items);
					this.ctx.executeQueryAsync(function (s, a) {
						instance.retrievedItems = items;
						instance.OnTopicsRetrieved(onSuccess);
					}, function (s, a) {
						onFailed(s, a);
					});
				};

				DiscussionDataService.prototype.GetRepliesForTopic = function (TopicID, onSuccess, onFailed) {
					var web = this.ctx.get_web(), list = web.get_lists().getByTitle(this.discussionListName);
					var instance = this;
					var query = new SP.CamlQuery();
					query.set_viewXml('<View Scope=\'RecursiveAll\'><Query><Where><Eq><FieldRef Name=\'ParentItemID\'/><Value Type=\'Number\'>' + TopicID + '</Value></Eq></Where></Query><RowLimit>100</RowLimit></View>');

					var items = list.getItems(query);
					this.ctx.load(items);
					this.ctx.executeQueryAsync(function (s, a) {
						instance.retrievedItems = items;
						instance.OnRepliesRetrieved(onSuccess);
					}, function (s, a) {
						onFailed(s, a);
					});
				};

				DiscussionDataService.prototype.OnTopicsRetrieved = function (Callback) {
					var iterator = this.retrievedItems.getEnumerator();
					var items = [];
					while (iterator.moveNext()) {
						var item = iterator.get_current();
						var d = new Model.DiscussionItem(item);
						items.push(d);
					}
					Callback(items);
				};

				DiscussionDataService.prototype.OnRepliesRetrieved = function (Callback) {
					var iterator = this.retrievedItems.getEnumerator();
					var items = [];
					while (iterator.moveNext()) {
						var item = iterator.get_current();
						var d = new Model.ReplyItem(item);
						items.push(d);
					}
					Callback(items);
				};
				return DiscussionDataService;
			})();
			Data.DiscussionDataService = DiscussionDataService;
		})(iPortal.Data || (iPortal.Data = {}));
		var Data = Portal.Data;
	})(Portal || (Portal = {}));

	function RenderDiscussion(items) {
		var source   = $("#discussionTopicTemplate").html();
		var template = Handlebars.compile(source,{noEscape:true});
		var html = template(items);
		$("#Discussions").html(html);
	}

	function RenderReplies(items) {
		// not implemented yet
	}

	function HandleError(s, a) {
		// not implemented yet
	}

	SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointReady);
	
	var d;
	function sharePointReady(){
		d = new Portal.Data.DiscussionDataService(SP.ClientContext.get_current(),"My Discussions");
		d.GetDiscussionTopics(RenderDiscussion,HandleError);
	}

	</script>

	<script id="discussionTopicTemplate" type="text/x-handlebars-template">
	<h1>Conversations</h1>
	<div id="DiscussionList">
	{{#each this}}
		<h2>{{Subject}} (Replies: {{ReplyCount}})</h2>
		<div><div>{{Body}}<div><div>Created by {{Author}} on {{CreateDate}}</div></div>
	{{/each}}
	</div>
	</script>
</body>
</html>

Author: Chris Quick

I have been a developer of web based solutions since early 2001 delivering solutions to a wide array of organizations using ASP, ASP.NET and SharePoint. I was introduced to SharePoint in 2003 when the consulting firm I worked for at the time introduced it into the workplace. I began working with MOSS 2007 as soon as Microsoft released the RTM version in November 2006. The platform was implemented at the organization I worked for in 2007 and went live in March of that year. I was tasked with the administration and ongoing development of the platform. I currently work as a SharePoint Architect with Artis Consulting, developing solutions for a wide variety of business problems. The goal of this blog is to share my discoveries developing solutions with SharePoint. I welcome your comments and feedback to any post -- and I welcome suggestions for future topics.

4 thoughts on “Utilizing TypeScript with SharePoint 2013 JavaScript Object Model

  1. Hi Chris,

    I’m one of authors of https://sptypescript.codeplex.com/. Do you use typescript in real-world SharePoint projects? I’d like to help you to start using our definitions. My interest is to gather feedback and stories for sptypescript.

    • I have been looking at TypeScript as a way to streamline and improve solutions; however, I am not involved in any projects that are actively using it. As new projects come along, I will definitely look at TypeScript and the definitions you’ve helped author. From a quick look over them today, they seem to cover a lot of the gotcha points most developers experience when writing JavaScript solutions for SharePoint. I will forward the site to a couple of colleagues as well for their thoughts.

  2. Hello Chris,

    Thanks for the post, nice ideas.
    However, I’d recommend to take a look on SharePoint 2013 TypeScript Definitions project. It is all about high-quality (documented and strongly typed) TypeScript declarations for SharePoint 2013 JavaScript Object Model (CSOM) + lots of examples.

    Take a look on them here:
    https://sptypescript.codeplex.com/

    Cheers!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s