Tuesday, February 12, 2013

Setting field values using CSOM client side - Another look

Last night after hitting publish, I enjoyed a long ride home on the metro...  I was able to catch up on some of my reading.  It's a relaxing part of my day and sometimes exciting because I get to grind away on concepts that I'm working on.  Last night did not disappoint.

spUtils - setColumnVal


As I said in the previous post, I've already tackled this problem, however, I didn't really like the implementation.  So, here's my bright idea... Toggle the library back to use setColumnVal and see what the XML looks like under the hood.  Doing just that, here's what's produced( I've snipped this for brevity ):


<Method Name="SetFieldValue" Id="26" ObjectPathId="21">
  <Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
<Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
</Parameter>
</Method>


So based on that, it's easy to see the people picker XML has to be an array of objects.  Let's give that a shot now using this code mixed with parseAndSetFieldValue.

spUtils - parseAndSetFieldValue revisited


spUtils.updateListItems({
listName: "spUtils",
updates : {
1 : {
"Title" : spUtils.isoDate(),
"AssignedTo" : [
{
LookupValue: "DEV\\Administrator",
LookupId: -1
},
{
LookupValue: "DEV\\spUser",
LookupId: -1
}
]
}
},
success: function( data, ctx ) {
debugger;
}
});

Using the code above produces this XML ( snipped as well for brevity ):

<Method Name="ParseAndSetFieldValue" Id="44" ObjectPathId="21">
  <Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="Dictionary">
<Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
<Object Type="Dictionary">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
</Object>
</Parameter>
  </Parameters>
</Method>


It's remarkably close to the XML that actually works.  The only thing that is different is the Object Type.  Sadly, this is all that it takes for this to FAIL.  Yep, that's right...  Trying to be smarter than the average bear, let's give it another shake.  This time, I'm going to take some code out of the setColumnVal method and drop it into an array.  Take a look at this:

spUtils.updateListItems({
listName: "spUtils",
updates : {
1 : {
"Title" : spUtils.isoDate(),
"AssignedTo" : [  SP.FieldUserValue.fromUser("DEV\\Administrator"),
  SP.FieldUserValue.fromUser("DEV\\spUser")
]
}
},
success: function( data, ctx ) {
debugger;
}
});


This in turn produces XML that *should* work!

<Method Name="ParseAndSetFieldValue" Id="44" ObjectPathId="21">
  <Parameters>
  <Parameter Type="String">AssignedTo</Parameter>
  <Parameter Type="Array">
  <Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
  <Property Name="LookupValue" Type="String">DEV\Administrator</Property>
<Property Name="LookupId" Type="Number">-1</Property>
  </Object>
  <Object TypeId="{c956ab54-16bd-4c18-89d2-996f57282a6f}">
<Property Name="LookupValue" Type="String">DEV\spUser</Property>
<Property Name="LookupId" Type="Number">-1</Property>
  </Object>
  </Parameter>
  </Parameters>
</Method>


The only difference this time is the Method Name attribute.  Sadly, even this FAILS! I was going to continue with using numbers, but with this being a show stopper, I'm convinced I've researched this thoroughly enough.  This may be different in SP2013, it simply doesn't work in SP2010, therefore unreliable.

What's next?


Since I need the context of the list item to set its values when using the .update() method, it's not feasible to change what I have currently.  To set lookups and people picker values in CSOM, you have to use the code I've already written.  Guess it's time I start documenting the API, eh?

Monday, February 11, 2013

Setting field values using CSOM client side

A rather long time ago, I wrote some code to handle creation and updates of items when using Client Object Model.  This bit of code works very well however it's always made me feel like the API I've built had warts. Take a look for yourself.  This method allows for anyone to modify Lookup values as well as People Picker values.  Using this, you can also set multiple values without a problem.  That's the good...  The ugly is for this to work, it's necessary to tell spUtils the column type.  This ended up looking something like this:


/***************************************
Test 26 ~ updateListItems - Updates item's lookup and people picker column
***************************************/
spUtils.updateListItems({
listName : "Project Tasks",
updates : {
111 : { // the key is the item ID
"RelatedProject{L}" : spUtils.isoDate(),
"AssignedTo{P}" : 1,

     "Title" : "Hello, World!"
}
},
success : function() { debugger; }
});


// End codez


Notice the appended characters that represent the column type. This is fine but at the end of the day not very user friendly...  I've always thought I could do better.  This blog post will explore what I've found inside the SP Namespace and what my options are to fix it.

SP.Debug.js to the Rescue?

When looking at a listItem object in my debugger, I found a rather coy method.  It literally screamed at me: "Put me in the game coach!"... Totally looked over this one when building CRUD into spUtils.  With that said, I've made the change under the hood to use this method instead.

parseAndSetFieldValue: function(fieldName, value_) {ULS5Vl:;
        this.get_fieldValues()[fieldName] = value_;
        var $v_0 = new SP.ClientActionInvokeMethod(this, 'ParseAndSetFieldValue', [ fieldName, value_ ]);
        if ((this.get_context())) {
            this.get_context().addQuery($v_0);
        }
},


For posterity, here's the raw bits of the function call. Nothing too exciting here since it's really a wrapper for the ClientActionInvokeMethod. Come to think of it, what isn't a wrapper for ClientActionInvokeMethod inside SP.js?

Is parseAndSetFieldValue really up to snuff?

For me it was truly a magical moment coming across this method.  As of matter of fact, I made a note of it when I did to come back someday and put the spotlight directly on it...  As of a result, here are my findings in raw format ( I've highlighted the important pieces of XML ):


<!-- Test 1
[ "AssignedTo", ["DEV\\Administrator", "DEV\\spuser"] ]
XML sent to Server using Array of strings
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:10:13Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="String">DEV\Administrator</Object>
<Object Type="String">DEV\spuser</Object>
</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="17" ObjectPathId="9">
<Parameters>
<Parameter Type="String">RelatedProject</Parameter>
<Parameter Type="Number">2</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="18" ObjectPathId="9" />
<Query Id="19" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
<Property Name="RelatedProject" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid look-up value. A look-up field contains invalid data. Please check the value and try again. 


<!-- Test 2 
[ "AssignedTo", "DEV\\Administrator, DEV\\spuser" ]
XML sent to Server using comma separated values inside a string.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:16:15Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator; DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid data has been used to update the list item. The field you are trying to update may be read only. 

<!-- Test 3
[ "AssignedTo", "DEV\\Administrator;#DEV\\spuser" ]
XML sent to Server using the old semi-colon bang delimiter.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:28:42Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator;#DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid look-up value.  A look-up field contains invalid data. Please check the value and try again. 

<!-- Test 4 
[ "AssignedTo", "DEV\\Administrator; DEV\\spuser" ]
XML sent to Server using text similar to typing into the control manually.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:32:14Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="String">DEV\Administrator; DEV\spuser</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Request failed. Invalid data has been used to update the list item. The field you are trying to update may be read only. 


<!-- Test 5
[ "AssignedTo", 1 ]
XML sent to Server using a single user id.  Works as a string as well.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:37:00Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Number">1</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Holy shit! It works....


<!-- Test 6
[ "AssignedTo", [ 1, 17 ] ]
XML sent to Server using an array of numbers.
-->

<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Method Name="ParseAndSetFieldValue" Id="11" ObjectPathId="9">
<Parameters>
<Parameter Type="String">Title</Parameter>
<Parameter Type="String">2013-02-11T20:40:36Z</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="12" ObjectPathId="9" />
<Query Id="13" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
</Properties>
</Query>
</Query>
<Method Name="ParseAndSetFieldValue" Id="14" ObjectPathId="9">
<Parameters>
<Parameter Type="String">AssignedTo</Parameter>
<Parameter Type="Array">
<Object Type="Number">17</Object>
<Object Type="Number">1</Object>
</Parameter>
</Parameters>
</Method>
<Method Name="Update" Id="15" ObjectPathId="9" />
<Query Id="16" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties>
<Property Name="Title" ScalarProperty="true" />
<Property Name="AssignedTo" ScalarProperty="true" />
</Properties>
</Query>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">Project Tasks</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItemById">
<Parameters>
<Parameter Type="Number">42</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>

Result:
Well, it doesn't fail... However the column is set to null.


Well that just sucks now doesn't it?  Using that method, I can ONLY use the user ID AND ONLY one user ID.  What a waste of time.  The method should really be called: parseValuesAndWasteMatthewsTime().

What about GetList?

I could query the current site, cache all of the list information and then figure out the column types on the fly.
While I know I could pull this off, I really don't think it's a great option.  CSOM is asynchronous in nature, which means I'd have to nest everything AFTER the initial call to get the list information.  That would be worse than what is already there...

What's next?

Since this inefficiency cannot be overcome in SP2010, I'm going to press on with the API I currently have because it simply works...  I'll continue to look for better ways to do this, as this is primarily why I've never documented spUtils.  I figured the API would change once I found a different way and didn't want to deal with the overhead of dealing with that.

Well since I feel like I've given the SP Namespace a fair shake, it's time to clean up the code and start vetting it for usage within SP2013.  Look for much more capabilities to be baked into this coming soon!

Example: spUtils.startWorkflow();  // This will not use web services to accomplish this. I'm trying to rely on SP.js for everything.

Happy coding!

Friday, February 8, 2013

Debugging CSOM (Client Object Model) Client-Side

If you didn't get the pleasure of attending @xenoxg's session at the FEDSPUG last night, you definitely missed some very noteworthy information.  Definitely check it out if you can...

Xenox asked a simple question last night: How many people in the room use Fiddler?
I quickly raised my hand because I have used this many times as well as many other people in the room.

After he showed us the network traffic that was produced from his application, I thought to myself: When was the last time I used Fiddler?  I couldn't answer that question...  I've used this amazing tool, but for some reason I didn't know the last time I actually used it.  The main reason for this is because I live in browsers every waking moment...  The tooling has become quite good on the front-end of the house and I've actually replaced my need of Fiddler with the tools baked into them.

Behold!

Internet Explorer Developer Tools: Network

Setup a Network Capture

IE Dev Tools Network Tab
If you are unfamiliar with the developer tools, just press F12 when you have IE open.  If you have IE9+ (as you should!), you'll see the lovely Network tab.  It's extremely simple to use and gives me all the info that I need.  All you have to do is press the "Start Capturing" button and then render your page.  Here's an example of the types of information you'll typically see.




Since this is a post about CSOM, the relevant line to us is already highlighted.  Notice the Javascript makes a call to http://pathToServer/_vti_bin/client.svc/ProcessQuery.  Once you've found this in the captured network traffic, you simply highlight that particular line and click "Go to detailed view".  This button is located directly underneath the Network tab.

Viewing the Server Response


I'm not going to pretend to know all of what this stuff means, but it is nice to look through it and learn more about how this all is glued together for us.  The two tabs here that I use the most are "Request Body" and "Response Body".  If you are big into the now deprecated web services, you can think of the "Request Body" as your SOAP envelope.  In fact under the hood, it's actually still sending XML to the server:

<!-- START CODEZ -->
<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="14.0.0.0" LibraryVersion="14.0.4762.1000" ApplicationName="Javascript Library">
<Actions>
<ObjectPath Id="1" ObjectPathId="0" />
<ObjectPath Id="3" ObjectPathId="2" />
<ObjectPath Id="5" ObjectPathId="4" />
<ObjectPath Id="7" ObjectPathId="6" />
<ObjectIdentityQuery Id="8" ObjectPathId="6" />
<ObjectPath Id="10" ObjectPathId="9" />
<Query Id="11" ObjectPathId="9">
<Query SelectAllProperties="false">
<Properties />
</Query>
<ChildItemQuery SelectAllProperties="false">
<Properties>
<Property Name="ID" SelectAll="true" />
<Property Name="FileRef" SelectAll="true" />
<Property Name="FSObjType" SelectAll="true" />
<Property Name="FolderChildCount" SelectAll="true" />
<Property Name="ItemChildCount" SelectAll="true" />
<Property Name="Title" SelectAll="true" />
<Property Name="Something01" SelectAll="true" />
<Property Name="Something02" SelectAll="true" />
<Property Name="Something03" SelectAll="true" />
</Properties>
</ChildItemQuery>
</Query>
</Actions>
<ObjectPaths>
<StaticProperty Id="0" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current" />
<Property Id="2" ParentId="0" Name="Web" />
<Property Id="4" ParentId="2" Name="Lists" />
<Method Id="6" ParentId="4" Name="GetByTitle">
<Parameters>
<Parameter Type="String">My List Name</Parameter>
</Parameters>
</Method>
<Method Id="9" ParentId="6" Name="GetItems">
<Parameters>
<Parameter TypeId="{3d248d7b-fc86-40a3-aa97-02a75d69fb8a}">
<Property Name="FolderServerRelativeUrl" Type="String">/Lists/SomeListName</Property>
<Property Name="DatesInUtc" Type="Boolean">true</Property>
<Property Name="ViewXml" Type="String">&lt;View&gt;&lt;Query&gt;&lt;OrderBy&gt;&lt;FieldRef Name='Something03' Ascending='True' /&gt;&lt;/OrderBy&gt;&lt;/Query&gt;&lt;/View&gt;</Property>
<Property Name="ListItemCollectionPosition" Type="Null" />
</Parameter>
</Parameters>
</Method>
</ObjectPaths>
</Request>
<!-- END CODEZ -->

The "Response Body" comes to us in the form of JSON:

// START CODEZ
[
{
"SchemaVersion":"14.0.0.0","LibraryVersion":"14.0.4762.1000","ErrorInfo":null
},
1,
{
"IsNull":false
},
3,
{
"IsNull":false
},
5,
{
"IsNull":false
},
7,
{
"IsNull":false
},
8,
{
"_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:90a9005d-7dcd-4784-9ed0-b4f722941dc0:list:f832844d-5ed1-4e39-98f3-d39f5e37ea3b"
},
10,
{
"IsNull":false
},
11,
{
"_ObjectType_" : "SP.ListItemCollection",
"_Child_Items_": [
{
"_ObjectType_":"SP.ListItem","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:90a9005d-7dcd-4784-9ed0-b4f722941dc0:list:f832844d-5ed1-4e39-98f3-d39f5e37ea3b:item:43,1","_ObjectVersion_":"3","ID":43,"FileRef":"\u002fLists\u002fSomethingNav\u002fTest3","FSObjType":"1","FolderChildCount":"5","ItemChildCount":"0","Title":"Test3","Something01":null,"Something02":null,"Something03":1
},
{
"_ObjectType_":"SP.ListItem","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:90a9005d-7dcd-4784-9ed0-b4f722941dc0:list:f832844d-5ed1-4e39-98f3-d39f5e37ea3b:item:1,1","_ObjectVersion_":"3","ID":1,"FileRef":"\u002fLists\u002fSomethingNav\u002fTest","FSObjType":"1","FolderChildCount":"4","ItemChildCount":"0","Title":"Test","Something01":null,"Something02":null,"Something03":2
},
{
"_ObjectType_":"SP.ListItem","_ObjectIdentity_":"740c6a0b-85e2-48a0-a494-e0f1759d4aa7:web:90a9005d-7dcd-4784-9ed0-b4f722941dc0:list:f832844d-5ed1-4e39-98f3-d39f5e37ea3b:item:22,1","_ObjectVersion_":"3","ID":22,"FileRef":"\u002fLists\u002fSomethingNav\u002fTest2","FSObjType":"1","FolderChildCount":"5","ItemChildCount":"0","Title":"Test2","Something01":null,"Something02":null,"Something03":3
}
]
}

// END CODEZ

Making Sense of it All

Let's get one thing clear, if you can make sense of this "Response Body" and why it's formulated the way that is it, you deserve a raise.  This portion of JSON that starts to make sense is directly after the 11.  Knowing what your JSON looks like can make it real easy to not only debug your scripts, but also definitively prove your script is communicating with the server correctly.  With this at your fingertips, there's no real need for me to use another piece of software.  And that's why I couldn't remember the last time I used Fiddler.  My browser(s) do everything I was using Fiddler for.  I'm definitely not saying it's a replacement, however for my needs, the browser suffices.  

Happy coding!

Thursday, November 29, 2012

Binding Event Handlers to SharePoint Content Types (Sandbox)

Fiddling around in SharePoint's Sandbox land can and will test your sanity.  There are things that are the way they are and you just have to deal with it.  All in all, a learning experience indeed...

Here's some key takeaways from my current project:
I opted not to bind this to the Content Type because I didn't want the Event Handler's behavior to persist on any other list.  I doubt the CT will be reused, but if it is, I know my code will not run erroneously.

Once that is added to your list, then you can dive into the properties and detect the Content Type GUID for the item that was just added.  A simple example:

       public override void ItemAdded(SPItemEventProperties properties)
       {
           base.ItemAdded(properties);
           if (properties.ListItem.ContentTypeId.Parent.ToString().ToUpper() == Constants.ctGuid.ToUpper())
           {
            // Snipped
           }
This is only a small part of what I'm in the middle of building.  Hopefully, I can abstract more out and show off some of the innards.

Cheers!

***Update***
Shortly after posting this @SharePointAlex and I started to discuss binding Event Handlers to lists.  Based on my tests, I've confirmed that using a Site-scoped solution does in fact fire the Event Handler on all lists in your web.  Using the same technique but having the solution Web-scoped will only bind the Event Handler to the list you declare.  Thanks Alex for making dig a bit deeper to further get better documentation.

Monday, November 26, 2012

CEWP Usage in SharePoint

While I absolutely loathe the term "Best Practices", it's time to dive into a topic that really hasn't been talked about but should have by now.  I'm *not* going to label this post as a "Best Practice", although I'd like to, but label it more like: this is what I do and why...

Adding Script to Pages

It's well known that most of the front-end developers favor using CEWP's to inject script into our pages.  Many well known webparts use this technique.  This is great!  We can add functionality to my pages with a small amount of work and without using SharePoint Designer.

Page Weight

What happens when you add scripts to your page using a CEWP?  This is a very much a misnomer because you really aren't adding scripts and instead are adding HTML.  CEWP's only interpret HTML and as such, they DO NOT cache any of the HTML added to the page.  To take advantage of caching these resources, you must use write the HTML correctly.  Adding <script> tags with a src attribute will tell the browser to cache your JavaScript files.  If you avoid doing this, your HTML will have to be sent to your browser each time, causing your page to load slower than it really has to.  Using a script tag correctly to call your .js file, your script will be cached by the browser and will not have to be downloaded for each page load.

Considerations

Reading over an excellent write up about page weight got me to thinking about what I've seen blogged in the SharePoint space.  Most solutions have you connect a pseudo .js file to a CEWP.  In actuality, this isn't a JavaScript file at all.  It's a file that contains HTML.  So that's quite confusing, not to mention, adds to your page weight.  The post linked above says to keep your inlined CSS or JavaScript to < 1 ~ 4 KB.  

Here's my list of considerations when adding a new CEWP to my page.
  • What will this script potentially do over the life of this application?
  • Do I think this could grow over 1 KB?
  • Does the page weight even matter for this application?
  • Will I gain anything by caching these resources?

What I Do

Mostly, I create a .html file wherever I store my assets.  I write HTML in that file.  Generally speaking it's <link> tags and <script> tags that reference my resources.  Doing so, will allow these files to be cached by the browser.  Page weight and load times are very important to me, so this is generally what I do.  However, I will write CSS and JavaScript in my .html file if I know there will be a very small amount of HTML going into that file.

Using this technique, I feel less confused overall.  When I open a .js file, I'm actually looking at JavaScript and not CSS or HTML or both.  Maybe it'll do the same for you, who knows?!?  

This is what I do though.