Second Life of a Hungarian SharePoint Geek

July 29, 2013

ECMAScript Client Object Model Internals – Creating custom client OM extensions

Filed under: ECMAScript Client Object Model, SP 2010 — Tags: , — Peter Holpar @ 18:26

About two years ago I published a sample about the extensibility of the Managed Client Object Model of SharePoint 2010. To understand the theory behind the code in the current post you should read that article and my other posts around the client OM first.

Since that I’ve got the question several times, if a similar extension would be available for the JavaScript / ECMAScript object model. Due to the increasing popularity of the JavaScript based solutions around SharePoint, this topic became more interesting in the past months.

The answer for the previous question is yes, it is definitely possible to extend the out-of-the-box client JS libraries, and it is not even so complicate as one might think. To tell the truth, I had more troubles with loading the .js files in the correct order (see the issue and the solution later) as with implementing the library extensions themself. In this post I show you an extension built on the previous sample. As a prerequisite, you should deploy and configure (in web.config) the server-side components from that post.

Next, I’ve created a new .js file called CustomScripts.js (see code below) and deployed it to 14\TEMPLATE\LAYOUTS\Custom.

  1. // registering our custom namespace
  2. Type.registerNamespace('SS');
  3.  
  4. // ———————————————-
  5. // SS.CustomRequestContext class
  6. // ———————————————-
  7. SS.CustomClientContext = function (serverRelativeUrl) {
  8.     SS.CustomClientContext.initializeBase(this, [(SP.ScriptUtility.isNullOrUndefined(serverRelativeUrl)) ? SP.PageContextInfo.get_webServerRelativeUrl() : serverRelativeUrl]);
  9. }
  10. SS.CustomClientContext.get_current = function () {
  11.     if (!SS.CustomClientContext.$1S_1) {
  12.         SS.CustomClientContext.$1S_1 = new SS.CustomClientContext(SP.PageContextInfo.get_webServerRelativeUrl());
  13.     }
  14.     return SS.CustomClientContext.$1S_1;
  15. }
  16. SS.CustomClientContext.prototype = {
  17.     get_customClientObject: function () {
  18.         var $v_0 = SS.CustomRequestContext.getCurrent(this).get_customClientObject();
  19.         return $v_0;
  20.     }
  21. }
  22.  
  23. // ———————————————-
  24. // SS.CustomRequestContext class
  25. // ———————————————-
  26. SS.CustomRequestContext = function (Context, ObjectPath) {
  27.     SS.CustomRequestContext.initializeBase(this, [Context, ObjectPath]);
  28. }
  29. SS.CustomRequestContext.getCurrent = function ($p0) {
  30.     var $v_0 = $p0.get_staticObjects()['ClientExtension$Server$SSCustomContext$Current'];
  31.     if ((!$v_0)) {
  32.         $v_0 = new SS.CustomRequestContext($p0, new SP.ObjectPathStaticProperty($p0, '{DF694817-22BA-4952-A1E9-84C6E69709A8}', 'Current'));
  33.         $p0.get_staticObjects()['ClientExtension$Server$SSCustomContext$Current'] = $v_0;
  34.     }
  35.     return ($v_0);
  36. }
  37. SS.CustomRequestContext.prototype = {
  38.     get_customClientObject: function () {
  39.         var $v_0 = ((this.get_objectData().get_clientObjectProperties()['CustomClientObject']));
  40.         if (SP.ScriptUtility.isUndefined($v_0)) {
  41.             $v_0 = new SS.CustomClientObject(this.get_context(), new SP.ObjectPathProperty(this.get_context(), this.get_path(), 'CustomClientObject'));
  42.             this.get_objectData().get_clientObjectProperties()['CustomClientObject'] = $v_0;
  43.         }
  44.         return $v_0;
  45.     }
  46. }
  47.  
  48. // ———————————————-
  49. // SS.CustomClientObject class
  50. // ———————————————-
  51. SS.CustomClientObject = function (Context, ObjectPath) {
  52.     SS.CustomClientObject.initializeBase(this, [Context, ObjectPath]);
  53. }
  54. SS.CustomClientObject.prototype = {
  55.     getMessage: function (name) {
  56.  
  57.         var $v_0;
  58.         var $v_1 = new SP.ClientActionInvokeMethod(this, 'GetMessage', [name]);
  59.         this.get_context().addQuery($v_1);
  60.         $v_0 = new SP.StringResult();
  61.         this.get_context().addQueryIdAndResultObject($v_1.get_id(), $v_0);
  62.         this.removeFromParentCollection();
  63.        
  64.         return $v_0;
  65.     }
  66. }
  67.  
  68. // registering our custom classes
  69. SS.CustomClientContext.registerClass('SS.CustomClientContext', SP.ClientContext);
  70. SS.CustomRequestContext.registerClass('SS.CustomRequestContext', SP.ClientObject);
  71. SS.CustomClientObject.registerClass('SS.CustomClientObject', SP.ClientObject);
  72.  
  73. // notify waiting jobs
  74. NotifyScriptLoadedAndExecuteWaitingJobs("CustomScripts.js");

If you understand the internals of the managed client OM from my former posts and had already a look at the source code in the standard SharePoint client OM .js files, the code above should be pretty straightforward. It’s nothing more than inheriting our custom JS classes from the standard SharePoint JS classes, and implementing properties and methods as it is done in the standard libraries (almost copy/paste).

For testing the new library, I’ve created a new web part page, and added a Content Editor Web Part (CEWP) with the code below:

  1. <script type="text/javascript">
  2.  
  3. // wait the standard client OM scripts to be loaded first
  4. ExecuteOrDelayUntilScriptLoaded(loadScript, 'sp.js');
  5.  
  6. // load our custom script next
  7. function loadScript() {
  8.     var script = document.createElement("script");
  9.     script.src = "/_layouts/Custom/CustomScripts.js";
  10.     script.type = "text/javascript";
  11.     document.getElementsByTagName("head")[0].appendChild(script);
  12. }
  13.  
  14. // when the script is loaded, execute the custom OM methods
  15. ExecuteOrDelayUntilScriptLoaded(startScript, 'CustomScripts.js');
  16.  
  17. var result;
  18.  
  19. function startScript() {
  20.   try {
  21.     var context = SS.CustomClientContext.get_current();
  22.     var obj = context.get_customClientObject();
  23.     result = obj.getMessage('Joe');
  24.     context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed));
  25.   }
  26.   catch (e) {
  27.     alert("Error: " + e);
  28.   }
  29. }
  30.  
  31. function failed(sender, args) {
  32.     alert("Operation failed: " + args.get_message());
  33. }
  34.  
  35. function success(sender, args) {
  36.     alert(result.get_value());
  37. }
  38.  
  39. </script>

The standard SharePoint client OM .js files (like SP.Runtime.js first and SP.js next) are loaded by the web part page automatically. These files must be loaded first. Our extension script (CustomScripts.js) is built upon objects in these standard files, so it must be loaded after loading the standard libraries is finished. Loading the files in the wrong order (the standard OM files must be completely loaded before loading our extension!) causes mysterious errors. I solved this requirement through the combination of ExecuteOrDelayUntilScriptLoaded methods and NotifyScriptLoadedAndExecuteWaitingJobs method (see at the end of CustomScripts.js above) and the custom loadScript method.

After this preparation, the client OM extension objects and methods can be used as their standard OM counterparts. Of course, in this case we must start the script with our custom context (SS.CustomClientContext), and not the standard SharePoint client object model context (SP.ClientContext).

The image below illustrates the expected output of the script:

image

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

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

Create a free website or blog at WordPress.com.

%d bloggers like this: