Showing posts with label Scripting. Show all posts
Showing posts with label Scripting. Show all posts

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!

Monday, April 2, 2012

Modifying the default language in InfoPath

This will be a quick exercise on how to change the language you can write within InfoPath 2007...

Click on Form Options
Crack open your favorite form builder and then click on Tools.  From that menu, click on Programming on the left...  You should already know where I'm going with this.  Let's change that default (C#) to something nonsensical, unstructured and putrid; yet everyone seems to be jumping on the bandwagon: JavaScript or in this case JScript. Whatev...







(¸¸.♥➷♥•*¨)¸.•´¸.•*¨) ¸.•*¨)
(¸.•´(¸. ¸.•´¸.•*¨) ¸.♥➷•*¨)
─▀██▀─▄███▄─▀██─██▀██▀▀▀█─
──██─███─███─██─██─██▄█──conquers
──██─▀██▄██▀─▀█▄█▀─██▀█──all ♥➷♥
─▄██▄▄█▀▀▀─────▀──▄██▄▄▄█
¸.•´¸.•*¨) ¸♥.•*¨) (¸.•´¸♥➷♥¸.•´♥¸.•´♥¸.•*¨)♥.•*¨)¸.•*♥¸


Form Options



















(¸¸.♥➷♥•*¨)¸.•´¸.•*¨) ¸.•*¨)
(¸.•´(¸. ¸.•´¸.•*¨) ¸.♥➷•*¨)
─▀██▀─▄███▄─▀██─██▀██▀▀▀█─
──██─███─███─██─██─██▄█──conquers
──██─▀██▄██▀─▀█▄█▀─██▀█──all ♥➷♥
─▄██▄▄█▀▀▀─────▀──▄██▄▄▄█
¸.•´¸.•*¨) ¸♥.•*¨) (¸.•´¸♥➷♥¸.•´♥¸.•´♥¸.•*¨)♥.•*¨)¸.•*♥¸




I've decided from now on any InfoPath post will need something extra for me, so I can start to

░░░░░░░░░░░░▄▄
░░░░░░░░░░░█░░█
░░░░░░░░░░░█░░█
░░░░░░░░░░█░░░█
░░░░░░░░░█░░░░█
███████▄▄█░░░░░██████▄
▓▓▓▓▓▓█░░░░░░░░░░░░░░█
▓▓▓▓▓▓█░░░░░░░░░░░░░░█
▓▓▓▓▓▓█░░░░░░░░░░░░░░█
▓▓▓▓▓▓█░░░░░░░░░░░░░░█
▓▓▓▓▓▓█░░░░░░░░░░░░░░█
▓▓▓▓▓▓█████░░░░░░░░░█       
██████▀░░░░▀▀██████▀

it...

Want to see what damage I can do?  Stay tuned...

Wednesday, March 21, 2012

Add Notepad++ to the context menu

This little regedit keeps coming in handy.  Thought I’d add it here so it’s easy for me to find and the other 2 of you that read this blog.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\Open with Notepad++\command]
@="\"C:\\Program Files (x86)\\Notepad++\\notepad++.exe\" \"%1\""

ContextMenuSave that into a file with the extension .reg and double click it to add to the registry.  You’ll now see Open with Notepad++ in the context menu.


Pretty awesome, eh?  You can add whatever else you like to your context menu as well.  As always though be cautious when modifying the registry.


I’ve seen people wreck, destroy, obliterate their machine fiddling with things they shouldn’t have.  With that said, have fun doing it!




This post was inspired by: http://www.sevenforums.com/software/29942-notepad-context-menu.html

Friday, March 2, 2012

roboCAML v0.4 has been released!

RoboCAMLYou may have seen me tweet about roboCAML the last few days.  If you were scratching your head about what it actually is, don’t feel bad.  No one knew…  It’s a jQuery module, *not* a plugin, I’ve built specifically to handle the tedious task of manually building CAML or worse hard coding CAML within your scripts.  An added benefit, is the ability to create dynamic CAML queries on the fly.

Currently
roboCAML has a depends on jQuery, but very well may become a  pure JavaScript module without any external dependencies.  It's hard to beat the $.ajax function within jQuery, but I'm willing to change based on feedback.  It’s all about the community, you know?

What does it do?

roboCAML takes away the pain of scripts that look like this:
     // #CAMLToggle doesn't exist, but this is here in case we want to give the user the ability to AND
     // or OR the PartNum
  var camlToggle = $("#CAMLToggle").val() ? $("#CAMLToggle").val() : "Or", 
   ddlSelected = false, 
   PartCat = [], 
   PartNum = [], 
   thisFieldRef = "", 
   camlQuery = "";
  
  $( "select.PartCat" ).each(function() {
   // Each select on the page has the PartCat class
   // the title attribute is also the name of the field for the CAML
   ctlTitle = $(this).attr("title");
   PartCat[ctlTitle] = ctlTitle;
   
   if ( ctlTitle ) != 0 ) {
    ddlSelected = true;
   }
  });
  
//First example of CAML engine
  i = 0;
  for (index in PartCat) {
   //console.log("Select title: " + i);
   //console.log("Select Val: " + PartCat[i]);
   if (PartCat[index] > 0) {
    thisFieldRef = "<Eq><FieldRef Name='" + index + "' LookupId='True' /><Value Type='Number'>" + PartCat[index] + "</Value></Eq>";
    if (i <= 1) {
     camlQuery += thisFieldRef;
    }
    if (i == 1) {
     camlQuery = "<And>" + camlQuery + "</And>";
    }
    if (i > 1) {
     camlQuery = "<And>" + camlQuery + thisFieldRef + "</And>";
    }
    i++;
   }
  }
//Show example of other CAML Engine
  
  $("input.PartNum:checked").each(function(index) {
   // If this isn't the first PartNum, we'll "wrap" the array with the camlToggle
   if(index > 0) {
    PartNum.unshift("<" + camlToggle + ">");
   }
   PartNum.push("<Eq><FieldRef Name='PartNum' LookupId='True' /><Value Type='Number'>" + $(this).attr("alt") + "</Value></Eq>");
   // If this isn't the first PartNum, we'll "wrap" the array with the camlToggle
   if(index > 0) {
    PartNum.push("</" + camlToggle + ">");
   }
  });
  
  // .join() defaults to commas, .join("") does the same thing. We'll join with a space, then replace
  // the spaces that fall between tags
  camlQuery += PartNum.join(" ").replace(/> </gi,"><");
  
  if ( $("input.PartNum:checked").length > 0 && ddlSelected ) {
   // If we have DDLs and PartNums, we'll <And> the two groups together, otherwise we won't
   camlQuery = "<And>" + camlQuery + "</And>";
  }
  camlQuery = "<Query><Where>" + camlQuery + "</Where><OrderBy><FieldRef Name='Title' Ascending='True' /></OrderBy></Query>";
  GetListItems(camlQuery);
 }


Within this script are two different ways to dynamically build CAML.  Each have their merits, but why do I have to think about setting up my CAML correctly and debugging it if I am having issues?  After being tasked to build a few of the complex scripts, sometimes several in a week, I had enough...  Time to roll up the sleeves and code a solution.  This is why I’ve built roboCAML, so now lets see what it does.

roboCAML In Action

roboCAML will assist in generating a string of useful CAML for use when making your Web Service or Client Object Model calls.  There are currently 5 methods available in roboCAML.

roboCAML.BatchCMD

So, now let’s look at roboCAML.BatchCMD method.  This method is a little tricky because there are three distinct actions you can take when using a batch.  Each requiring a different set of parameters.  In example 2.1, we’ll look at Deleting:
Example 2.1:
roboCAML.BatchCMD({
   batchCMD: 'Delete',
   IDs: [1,2,3]
});

This call accepts an array of ID’s and the “Delete” command.  The output will be:
<Batch OnError='Continue'><Method ID='3' Cmd='Delete'><Field Name='ID'>3</Field></Method><Method ID='2' Cmd='Delete'><Field Name='ID'>2</Field></Method><Method ID='1' Cmd='Delete'><Field Name='ID'>1</Field></Method></Batch> 
Now onto the New operation.  This example accepts the command for the batch.  What is different here from the Delete operation is, we can now pass in a valuePairs parameter.  This parameter accepts an array of arrays.  You’ll notice each array follows a certain pattern.  First the Static Name is provided and then the value.
Example: 2.2:
roboCAML.BatchCMD({ 
 batchCMD: "New",  
 valuePairs: [["PersonnelLookup", 1]]  //Static Column Name, Value
});

A more complex array would look like this:
roboCAML.BatchCMD({ 
 batchCMD: "New",  
 valuePairs: [["PersonnelLookup", 1, "ModuleNotes", "ModuleNotes", "Description", "Googly Glop"], ["ListUID", 3]]  //Static Column Name, Value
});

Which in turn produces:
<Batch OnError='Continue'><Method ID='1' Cmd='New'><Field Name='PersonnelLookup'>1</Field><Field Name='ModuleNotes'>ModuleNotes</Field><Field Name='Description'>Googly Glop</Field></Method><Method ID='2' Cmd='New'><Field Name='ListUID'>3</Field></Method></Batch> 
The last operation within roboCAML.BatchCMD is Update:

Example 2.3
roboCAML.BatchCMD({
 updates: [
  {
   //Notice batchCMD isn't present...
   //Static Column Name, Value
   valuePairs: ["Title", "Numero Tres", "PercentComplete", 1, "Boolean", 0, "ID", 3]
  },
  {
   //Defaults to Update anyway. No need to pass it.
   batchCMD: "Update",
   valuePairs: ["ID", 4, "Title", "Item4", "Boolean", 0]
  },
  {
   batchCMD: "New",
   valuePairs: ["Title", "Something New", "PercentComplete", 1]
  },
  {
   batchCMD: "Delete",
   ID: 6
  }
 ]
});

The output of the call above will generate:
<Batch OnError='Continue'>
 <Method ID='1' Cmd='Update'>
  <Field Name='Title'>Numero Tres</Field>
  <Field Name='PercentComplete'>1</Field>
  <Field Name='Boolean'>0</Field>
  <Field Name='ID'>3</Field>
 </Method>
 <Method ID='2' Cmd='Update'>
  <Field Name='ID'>4</Field>
  <Field Name='Title'>Item4</Field>
  <Field Name='Boolean'>0</Field>
 </Method>
 <Method ID='3' Cmd='New'>
  <Field Name='Title'>Something New</Field>
  <Field Name='PercentComplete'>1</Field>
 </Method>
 <Method ID='4' Cmd='Delete'>
  <Field Name='ID'>6</Field>
 </Method>
</Batch>

What’s very interesting with using the updates property of roboCAML.BatchCMD is the batch that is generated can be chocked full of all your different operations. Delete, New and Update all within one Web Service call. That’s #bada55.

roboCAML.OrderBy


And if you weren’t impressed by any of the above, maybe this will persuade you… It’s another snazzy way to build CAML on the fly. Here’s how to use roboCAML.OrderBy:

roboCAML.OrderBy({
 MyColumn: false,
 ID: true
});
Note: The value of each staticName can be a boolean or a string... The output of the call above will look like this:
<OrderBy><FieldRef Name='MyColumn' Ascending='False' /><FieldRef Name='ID' Ascending='True' /></OrderBy>

roboCAML.Query

Probably the most interesting method roboCAML has to offer.  There are a plethora of options within this method.  The best way to learn how to use this would be to read the docs or use the live demo.  Let’s look over a sample query you can use with roboCAML.
roboCAML.Query({
 listName: 'Calendar', 
 closeCaml: "Clientom",
 ViewFields: ["ID", "Created", "Title"],
 OrderBy: {
  ID: true
 },
 QueryOptions: {
  IncludeMandatoryColumns: false
 },
 config: [
  {
   filter: "&&",
   op: "*",
   staticName: "Title",
   value: "Daily"
  },
  {
   filter: "&&",
   op: "^",
   staticName: "Title",
   value: "Deleted"
  },
  {
   op: "!=",
   staticName: "ID",
   value: 3
  }
 ]
});


Since the CAML is not hardcoded, you can now easily generate whatever options needed to retrieve information from SharePoint.  Just like above in the “CAML Engines”, stuff an array full of info and do something with it...  I’m thinking of building a demo soon that will serve as a real world example of why this is useful for front-end development.


In case you were wondering, this is the output from heavy lifting done from roboCAML:



<View>
 <ViewFields>
  <FieldRef Name='Title' />
  <FieldRef Name='Created' />
  <FieldRef Name='ID' />
 </ViewFields>
 <Query>
  <Where>
   <And>
    <Contains>
     <FieldRef Name='Title' />
     <Value Type='Text'>Daily</Value>
    </Contains>
    <And>
     <BeginsWith>
      <FieldRef Name='Title' />
      <Value Type='Text'>Deleted</Value>
     </BeginsWith>
     <Neq>
      <FieldRef Name='ID' />
      <Value Type='Counter'>3</Value>
     </Neq>
    </And>
   </And>
  </Where>
  <OrderBy>
   <FieldRef Name='ID' Ascending='True' />
  </OrderBy>
 </Query>
 <IncludeMandatoryColumns>False</IncludeMandatoryColumns>
</View>

roboCAML.QueryOptions

This method will assist you in building queries and other various CAML fragments that you may need.  I haven’t found a comprehensive list that details all of the options available within this node.  The closest I’ve come to full documentation was on the Lists.GetListItems Method page within MSDN.  As I find/test/evaluate each new option I find, I’ll piecemeal them into the project. For now, the documentation can be found on the roboCAML project page.

roboCAML.ViewFields

As you could guess (if you are familiar with CAML), this does exactly what you would expect. Let’s take a look at roboCAML.ViewFields:


roboCAML.ViewFields(["Title", "Description", "ProjectName", "RelatedID"]);

This method accept an array of Static Names. The output will be:
<ViewFields><FieldRef Name='RelatedID' /><FieldRef Name='ProjectName' /><FieldRef Name='Description' /><FieldRef Name='Title' /></ViewFields> 
It’s just that simple… Really!

There you have it, roboCAML in a nutshell.  I’m missing some key parts that you *should* be able to do when creating CAML queries for SharePoint. I plan on adding them very soon!  Support for <Membership />, <Joins> (if it’s possible, haven’t tried yet...), and <ProjectedFields> are on top of my list as well as nested CAML fragments (Thanks Jim Bob!).  If you can think of anything you’d like to see added, feel free to ask