Showing posts with label roboCAML. Show all posts
Showing posts with label roboCAML. Show all posts

Thursday, March 21, 2013

RowLimit and CAML Query

I keep forgetting this, so here's a little note for me:

"<View Scope='RecursiveAll'><Query><Where><Eq><FieldRef Name='Author'/><Value Type='Integer'><UserID/></Value></Eq></Where><OrderBy><FieldRef Name='Created' Ascending='False' /></OrderBy></Query><RowLimit>1</RowLimit></View>"

There you have it... A valid usage of RowLimit within a CAML Query.

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



Sunday, February 12, 2012

Subtle differences using ViewFields vs. Include with Client Object Model

I’ve been diving more and more into Client OM/JSOM, in order to get my roboCAML codeplex project up to snuff. Of course, I’m going to be hacking away at examples and trying to make sense of it all. The first thing I needed was to visualize the data I’m working with; muy importante para mi.  Hacking away in the console, I was able to find where all of the awesome data is hidden. So the stage is set...

Using a standard function, I’m successfully retrieving data from SharePoint.  So far, so good...  Here’s the code I’m using:

function queryList() {
//...-->SNIP<--...//

var viewXml = roboCAML.Query({
listName: "Child",
closeCaml: "ClientOM",
//ViewFields: ["MultiSelectLookup", "MyDollars", "DateAndTime"],
config: [
{
filter: "&&",
op: "=",
staticName: "MultiSelectLookup",
value: 4,
lookupId: true
},
{
filter: "And",//I really like “&&”, but added this to show flexibility
op: "=",
staticName: "FSObjType",
value: 0
},
{
op: "IsNull",
staticName: "MyDollars"
}
]
});

$("#QueryOutput").val( viewXml );

try {
//Get the current client context
this.spContext = new SP.ClientContext.get_current();

//Create local vars.
var targetList = spContext.get_web().get_lists().getByTitle( listName ),
camlQuery = new SP.CamlQuery();

//Set CAML Query.
camlQuery.set_viewXml( viewXml );
//Cache listItems var for use later.
this.listItems = targetList.getItems( camlQuery );
//Load and execute queries.
spContext.load( listItems /*'Include(Author,MultiSelectLookup,MyDollars)'*/ );
spContext.executeQueryAsync(ReadListItemSucceeded, ReadListItemFailed);
}
catch( e ) {
console.log( e );
}
}

This works as expected.  Just for clarity my  query above looks like this:

<View>
<ViewFields>
<FieldRef Name='DateAndTime' />
<FieldRef Name='MyDollars' />
<FieldRef Name='MultiSelectLookup' />
</ViewFields>
<Query>
<Where>
<And>
<Eq>
<FieldRef Name='MultiSelectLookup' LookupId='True' />
<Value Type='LookupMulti'>4</Value>
</Eq>
<And>
<Eq>
<FieldRef Name='FSObjType' />
<Value Type='Lookup'>0</Value>
</Eq>
<IsNull>
<FieldRef Name='MyDollars' />
</IsNull>
</And>
</And>
</Where>
</Query>
</View>

When the executeQueryAsync is successful, it’ll call the ReadListItemSucceeded function. Pretty straightforward, nothing magical...  What is magical, is finding a method that will display all of the JSON that has been returned. I can’t find it... I did *fix* that though. In the ReadListItemSucceeded function, I have a simple for loop, that expands and logs all of these properties.  Here’s what that looks like:

function ReadListItemSucceeded(sender, args) {

for( var i=0; i<listItems.$2_1.length; i++ ) {
console.dir( listItems.$2_1[ i ].$4_0.$1F_0.$m_dict );
}
//...-->SNIP<--...//
}

***Please note: console.dir() didn’t make it into Internet Explorer until v9***


This code will iterate through all of the JSON that is returned and display nicely in the console all of the data for each item.  Here’s an example of 1 item that was returned in my JSON.

{
DateAndTime : Wed Feb 22 09:00:00 EST 2012,
MyDollars : null,
MultiSelectLookup : [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],
_ModerationStatus : 0,
_Level : 1,
ID : 24,
UniqueId : eeda73be-0e32-4d90-a461-2237ca2b1081,
owshiddenversion : 2,
FSObjType : "0",
Created : Mon Jan 30 15:23:58 EST 2012,
Modified : Sun Feb 12 14:01:49 EST 2012,
FileRef : "/URL/Lists/GrandChild/24_.000"
}

Notice all of the extra columns?  What’s up with that?  It’s not as verbose as the Web Services, but I didn’t ask for all of this data, you know?


What’s the fix for the hefty <ViewFields>?


Use Include(‘ColumnName’) within your .load() call.  If you look above at my code sample, it’s commented out.  I don’t know what exactly happens under the hood that makes the difference, but here’s a sample result from my console.dir() iteration.

{
Author : [object Object],
MultiSelectLookup : [object Object],[object Object],[object Object],[object Object],[object Object],[object Object],
MyDollars : null
}

Now, I’m getting exactly what I want without any overhead data.  Because of this behavior, I’m very much thinking of adding another method to roboCAML, to make this easy to use.


And if you were wondering... Using <ViewFields> and Include together do not mix! You will promptly receive an error.


image


To get around this, my updated CAML looks like this:

<View>
<Query>
<Where>
<And>
<Eq>
<FieldRef Name='MultiSelectLookup' LookupId='True' />
<Value Type='LookupMulti'>4</Value>
</Eq>
<And>
<Eq>
<FieldRef Name='FSObjType' />
<Value Type='Lookup'>0</Value>
</Eq>
<IsNull>
<FieldRef Name='MyDollars' />
</IsNull>
</And>
</And>
</Where>
</Query>
</View>

ProTip: If you hate writing CAML, use roboCAML... It’s free and has attractive qualities to the opposite sex.

Wednesday, February 8, 2012

Understanding IncludeMandatoryColumns and SharePoint’s Web Services…

For a while now this has baffled me and several other people I know whom use SharePoint’s web services religiously.  The problem starts with incorrect documentation and since it has never been updated, new people to SharePoint continue to have issues with <IncludeMandatoryColumns>.  This post should give some clarity into what to expect when using this…

<IncludeMandatoryColumns>False</IncludeMandatoryColumns>

When setting this to option to: False; you’d expect a whole bunch of columns to not show up in the results of the XML.  That’s just, well: FALSE.  The only thing that changes in your data set is the Title column.  Nothing more, nothing less…  Here’s a sample data set that was retrieved using SPServices (which btw, you should use for all of your web service interactivities).
<!-- Mandatory Columns = False -->
<rs:data ItemCount="1">
   <z:row ows_MyDollars='567890.000000000' ows_Boolean='1' ows_MultiSelectLookup='' ows_ID='404' ows__ModerationStatus='0' ows__Level='1' ows_UniqueId='404;#{2541C25E-1D9A-4480-8F97-570B64077E37}' ows_owshiddenversion='3' ows_FSObjType='404;#0' ows_Created='2012-02-06 13:55:17' ows_PermMask='0x7fffffffffffffff' ows_Modified='2012-02-06 16:17:24' ows_FileRef='404;#mySite/Lists/GrandChild/TestFolder/TestSubFolder/MovedItem' />
</rs:data>

Did you notice the owsHiddenVersion in the XML?  According to the documentation, it should not be there.  For posterity, here’s a screenshot of the parameters passed to SPServices. If you are keen, you’ll notice my <ViewFields> do not contain the Title column:

IncludeMandatoryColumns_False



<IncludeMandatoryColumns>True</IncludeMandatoryColumns>

So being coy and after a few trial and errors, I decided that <IncludeMandatoryColumns> had to be related to the required fields within the list.  Since Title was the only required field, it seemed like a logical step.  Within this list, I have a column called Hyperlink.  I decided to make that required.  Notice in my <ViewFields>, Hyperlink is not present nor is Title for that matter.
<!-- Mandatory Columns w/ 2 columns required -->
<rs:data ItemCount="1">
   <z:row ows_MyDollars='567890.000000000' ows_Boolean='1' ows_MultiSelectLookup='' ows_ID='404' ows__ModerationStatus='0' ows__Level='1' ows_Title='InSubFolder' ows_UniqueId='404;#{2541C25E-1D9A-4480-8F97-570B64077E37}' ows_owshiddenversion='4' ows_FSObjType='404;#0' ows_Created='2012-02-06 13:55:17' ows_PermMask='0x7fffffffffffffff' ows_Modified='2012-02-07 20:12:48' ows_FileRef='404;#mySite/Lists/GrandChild/TestFolder/TestSubFolder/MovedItem' />
</rs:data>

IncludeMandatoryColumns_True



Is that ows_Title that I see?  Why yes, YES IT IS! As a matter of fact, it’s the only thing that’s different from the two sets of XML.

What have we learned?


<IncludeMandatoryColumns>True</IncludeMandatoryColumns> += Title. If Title is already in your <ViewFields>, then you’ll get nothing new. Yes, I know ID is in my <ViewFields> in the examples above.  Nothing changes except Title.

The documentation on MSDN is invaluable, however, it must be used with caution.  I’ve been running into a few whammies, gotchas, kablooey your code is busticated situations while building roboCAML.  The SP Namespace is another beast altogether, but that’s not what this post is about… yet.