"<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>"
"<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>"
// #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);
}
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]
});
<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
});
roboCAML.BatchCMD({
batchCMD: "New",
valuePairs: [["PersonnelLookup", 1, "ModuleNotes", "ModuleNotes", "Description", "Googly Glop"], ["ListUID", 3]] //Static Column Name, Value
});
The last operation within roboCAML.BatchCMD is Update:<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>
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 } ] });
<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>
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({ 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.ViewFields(["Title", "Description", "ProjectName", "RelatedID"]);
<ViewFields><FieldRef Name='RelatedID' /><FieldRef Name='ProjectName' /><FieldRef Name='Description' /><FieldRef Name='Title' /></ViewFields>
It’s just that simple… Really!
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?
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.
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.
<!-- 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>
<!-- 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>