Sharepoint CAML Query to avoid Calendar overlap booking - sharepoint

I am using Calendar List for some room bookings everything is fine but only struggling with Overlapp or double booking of a room on the same day and time.
I am trying to figure out by passing the data entered by the user in the form to CAML Query and check whether this entry is existing or not
If the entry is already existing Cancel Booking or if not proceed.
For example: if From: 10:00 AM To 11:00 AM is already booked.
the overlapping scenarios may be:
if user enters in the form From: 10:00 AM To 10:30 AM
From: 9:00 AM To 11:00 AM..
I am assuming The date is same and only time matters for me.
How to get the CAML query if the date is same and only time varies and check the user input From & To time with in the range exists in already Booked items.
using query something below but not validating for all scenarios
<Where><And><Geq><FieldRef Name='EventDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>" + strSPEventDateFormat + "</Value></Geq><And><Leq><FieldRef Name='EndDate' /><Value IncludeTimeValue='TRUE' Type='DateTime'>" + strSPEndDateFormat + "</Value></Leq><Eq><FieldRef Name='Room' /><Value Type='Lookup'>" + strCheckRoomAvail + "</Value></Eq></And></And></Where>
Please help me on this
Thanks in advance

Write CAML to get all rooms conflicting with the START Time. Start time must not be between start and end times selected by user
AND
write CAML to get all rooms conflicting with the END Time. END time must not be between start and end times selected by user
If the room selected by user is not in the above results returned by CAML then you can go ahead and book the room.
q.Query = "<Where><And><Geq><FieldRef Name=""StartTime"" /><Value IncludeTimeValue=""TRUE"" Type=""DateTime"">" + _
Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(startTime) + _
"</Value></Geq><Leq><FieldRef Name=""StartTime"" /><Value IncludeTimeValue=""TRUE"" Type=""DateTime"">" + _
Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(endTime) + _
"</Value></Leq></And></Where><OrderBy><FieldRef Name=""ID"" Ascending=""True"" /></OrderBy>"
q1.Query = "<Where><And><Geq><FieldRef Name=""EndTime"" /><Value IncludeTimeValue=""TRUE"" Type=""DateTime"">" + _
Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(startTime) + _
"</Value></Geq><Leq><FieldRef Name=""EndTime"" /><Value IncludeTimeValue=""TRUE"" Type=""DateTime"">" + _
Utilities.SPUtility.CreateISO8601DateTimeFromSystemDateTime(endTime) + _
"</Value></Leq></And></Where><OrderBy><FieldRef Name=""ID"" Ascending=""True"" /></OrderBy>"

This thread is quite old, but i just came across the same problem. So here is my working solution, may it help some one.
Note: For time relatet search-caml you need the IncludeTimeValue='TRUE' parameter of the
/**
* {Boolean} formModeEdit To determine if this form is an New or Edit form
*/
function checkDoubleBooking(formModeEdit, listId, currentItemId, startDate, endDate) {
var results = [];
var liHtml = "";
var checkOverlappingQueryArray = [];
//Construct caml query
checkOverlappingQueryArray.push("<And>");
checkOverlappingQueryArray.push("<Leq>");
checkOverlappingQueryArray.push("<FieldRef Name='EventDate' /><Value Type='DateTime' IncludeTimeValue='TRUE'>" + moment(endDate).format() + "</Value>");
checkOverlappingQueryArray.push("</Leq>");
checkOverlappingQueryArray.push("<Geq>");
checkOverlappingQueryArray.push("<FieldRef Name='EndDate' /><Value Type='DateTime' IncludeTimeValue='TRUE'>" + moment(startDate).format() + "</Value>");
checkOverlappingQueryArray.push("</Geq>");
checkOverlappingQueryArray.push("</And>");
if (formModeEdit) {
checkOverlappingQueryArray.unshift("<And>");
checkOverlappingQueryArray.push("<Neq>");
checkOverlappingQueryArray.push("<FieldRef Name='ID' /><Value Type='Integer'>" + currentItemId + "</Value>");
checkOverlappingQueryArray.push("</Neq>");
checkOverlappingQueryArray.push("</And>");
}
checkOverlappingQueryArray.unshift("<Where>");
checkOverlappingQueryArray.push("</Where>");
checkOverlappingQueryArray.unshift("<Query>");
checkOverlappingQueryArray.push("</Query>");
jQuery().SPServices({
operation: "GetListItems",
async: false,
listName: listId,
CAMLViewFields: "<ViewFields>" +
"<FieldRef Name='ID' />" +
"<FieldRef Name='Title' />" +
"<FieldRef Name='EventDate' />" +
"<FieldRef Name='EndDate' />" +
"<FieldRef Name='PeoplePickerField1' />" +
"</ViewFields>",
CAMLQuery: checkOverlappingQuery,
CAMLQueryOptions: "<QueryOptions>" +
"<ExpandUserField>True</ExpandUserField>" + //Expand People Picker values
"</QueryOptions>",
CAMLRowLimit: 10, // Override the default view rowlimit
completefunc: function(xData, Status) {
$(xData.responseXML).SPFilterNode("z:row").each(function() {
countOverlappingEvents++;
results.push({
title: $(this).attr("ows_Title"),
eventDate: $(this).attr("ows_EventDate"),
endDate: $(this).attr("ows_EndDate"),
peoplePickerField1: userToJsonObject($(this).attr("ows_PeoplePickerField1"))
});
}
}
});
return results;
}
function userToJsonObject(userObj) {
if (userObj.length === 0) {
return null;
} else {
var thisUser = userObj.split(";#");
var thisUserExpanded = thisUser[1].split(",#")
if (thisUserExpanded.length == 1) {
return {
userId: thisUser[0],
userName: thisUser[1]
}
} else {
return {
userId: thisUser[0],
userName: thisUserExpanded[0].replace(/(,,)/g, ","),
loginName: thisUserExpanded[1].replace(/(,,)/g, ","),
email: thisUserExpanded[2].replace(/(,,)/g, ","),
sipAddress: thisUserExpanded[3].replace(/(,,)/g, ","),
title: thisUserExpanded[4].replace(/(,,)/g, ",")
}
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment-with-locales.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2014.02/jquery.SPServices-2014.02.min.js"></script>

Related

Find item in SharePoint list on a HyperLink field

I've looked at as many examples as possible and tried different combinations to what looks like a simple problem - I want to check if an O365 modern list item exists based on a URL (Hyperlink) field. My CAML is as below but returns no results - the list item URL field is a full url and the url variable is a text field containing the full URL also. I've also tried Eq and Contains.
"<View>
<Query>
<Where>
<BeginsWith>
<FieldRef Name='URL'/><Value Type='URL'>" + url + "
</Value>
</BeginsWith>
</Where>
</Query>
</View>"
Thanks,
John.
Full code
var OnePlaceURL = TenantBaseURL + "/sites/" + "oneplacesolutions";
var clientSiteRelativePath = "/sites/" + alias; //Even though the list has the full URL the CAML query has to give a relative path - oddity of SP URL field
var registerList = "DMS Clients";
try
{
using (var context = new ClientContext(OnePlaceURL))
{
context.Credentials = SPOCredentials;
Web web = context.Web;
context.Load(context.Web, w => w.Url);
context.ExecuteQueryRetry();
AddLogMessage(MessageLevel.Info, "AddSiteToOnePlaceSolutionsCentralRegister - loaded web" + context.Web.Url);
var targetList = context.Web.Lists.GetByTitle(registerList);
context.Load(targetList);
context.Load(targetList.Fields);
context.Load(targetList.GetItemById(1));
context.ExecuteQueryRetry();
AddLogMessage(MessageLevel.Info, "AddSiteToOnePlaceSolutionsCentralRegister - loaded list and fields, itemCount:" + targetList.ItemCount);
CamlQuery query = new CamlQuery();
query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='URL'/><Value Type='URL'>" + url + "</Value></Eq></Where></Query></View>";
AddLogMessage(MessageLevel.Info, "ViewXML:" + query.ViewXml);
var itemList = targetList.GetItems(query);
context.Load(itemList); // loading all the fields
context.ExecuteQueryRetry();
As I test,if we add external web site url in list,we could use caml like this:
<View><Query><Where><Eq><FieldRef Name='link' /><Value Type='URL'>http://www.google.com</Value></Eq></Where></Query></View>
If we add some internal site url in list,we need to create the caml like this:
<View><Query><Where><Eq><FieldRef Name='link' /><Value Type='URL'>/sites/dev/subsite</Value></Eq></Where></Query></View>
We do not need to add our tenant url.
More reference:
https://social.msdn.microsoft.com/Forums/office/en-US/2dc874ed-3a3d-4f26-b932-afb25c0142c8/caml-query-on-url-field?forum=sharepointdevelopmentlegacy
Updated:
<script type="text/javascript">
SP.SOD.executeOrDelayUntilScriptLoaded(ValidateForm,"sp.js");
function ValidateForm(){
var clientContext = new SP.ClientContext();
var list = clientContext.get_web().get_lists().getByTitle("test3");
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml(
"<View><Query> <Where><Eq><FieldRef Name='link' /><Value Type='Url'>/sites/dev/subsite</Value></Eq></Where></Query></View>");
var items = list.getItems(camlQuery);
clientContext.load(items);
clientContext.executeQueryAsync(Function.createDelegate(this,onSuccess),Function.createDelegate(this, onFail));
function onSuccess(){
var ListEnumerator = items.getEnumerator();
while (ListEnumerator.moveNext()) {
var currentItem = ListEnumerator.get_current();
var link= currentItem.get_item("link");
console.log(link);
}
}
function onFail(sender,args){
console.log(args.get_message());
}
}
</script>

Unable to get all the folders and files (under all level) inside a main folder using CAML

I have the following CAML query inside my sharepoint online remote event receiver where i am trying to get all the files and folders that are directly and indirectly added to the folder named FolderA:-
camlQuery6.ViewXml = "<View Scope=\"RecursiveAll\"><Query><Where><Eq><FieldRef Name=\"FileDirRef\" /><Value Type=\"Text\">" + context.Web.ServerRelativeUrl + "/ArchDocs/FolderA"</Value></Eq></Where></Query></View>";
ListItemCollection collListItem6 = context.Web.GetList(context.Web.ServerRelativeUrl + "/ArchDocs").GetItems(camlQuery6);
context.Load(collListItem6, items => items.Include(
item => item.Id,
item=>item["FileDirRef"],
item => item["Title"],
item => item["DealStage"]));
the above CAML will only return the main folder under /sites/projects/ArchDocs/FolderA, but will not return any of the sub-folders and files.. so can anyone advice how i need to modify the CAML, or CAML does not support this?
Use below solution:
var _List = context.Web.Lists.GetByTitle("MyDoc3");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = #"<View Scope='RecursiveAll'>
<Query>
</Query>
</View>";
Folder folder = context.Web.GetFolderByServerRelativeUrl("/sites/lee/MyDoc3/ParentFolder");
context.Load(folder);
context.ExecuteQuery();
camlQuery.FolderServerRelativeUrl = folder.ServerRelativeUrl;
ListItemCollection listItems = _List.GetItems(camlQuery);
context.Load(listItems, items => items.Include(
item => item.Id,
item => item["FileDirRef"],
item => item["FileRef"],
item => item["Title"]));
context.ExecuteQuery();
The query is only returning items directly in that folder because your CAML is set to only return items where the FileDirRef (or Folder Path) is equal to /sites/projects/ArchDocs/FolderA, anything that is in a subfolder would have a different FileDirRef, such as /sites/projects/ArchDocs/FolderA/SubFolder1, which would not be equal to the path you specified. What you want to do, is instead of the <Eq> operator element, you want to use <BeginsWith>.
Note, you also appear to have syntax error with your string concatenation context.Web.ServerRelativeUrl + "/ArchDocs/FolderA"</Value>
Give this a try:
camlQuery6.ViewXml = "<View Scope=\"RecursiveAll\"><Query><Where><BeginsWith><FieldRef Name=\"FileDirRef\" /><Value Type=\"Text\">" + context.Web.ServerRelativeUrl + "/ArchDocs/FolderA" + "</Value></BeginsWith></Where></Query></View>";
ListItemCollection collListItem6 = context.Web.GetList(context.Web.ServerRelativeUrl + "/ArchDocs").GetItems(camlQuery6);
context.Load(collListItem6, items => items.Include(
item => item.Id,
item=>item["FileDirRef"],
item => item["Title"],
item => item["DealStage"]));

caml Query Returning Last Item in List

I have a CamlQuery which I set as follows:
function getPartID(partName) {
var clientContext = new SP.ClientContext('/sites/HepatoChemKitCustomizationForms/');
var oList = clientContext.get_web().get_lists().getByTitle('PartsTable');
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name="Part_x0020_Name" /><Value Type="Text">' + partName + '</Value></Eq></Where></Query></View>');
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(Function.createDelegate(this, this.onPartQuerySucceeded), Function.createDelegate(this, this.onQueryFailed));
}
function onPartQuerySucceeded(sender, args) {
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var oListItem = listItemEnumerator.get_current();
partID = (oListItem.get_item('Part_x0020_ID'));
}
}
'Part Name' is an existing field in the table on which I'm running the query, it is a text field, and the variable partName contains a string which is included in this field in the table.
However, the query returns the last item in the table instead of this one (one of the first items, not that it matters).
I've tried deleting the last item to see if there was something specific about that one but it happened again with the new last item.
I'm new to caml so I don't know what it going wrong with this.
Any help would be appreciated.
UPDATE:
I think I found the problem. When I ran alert (partName + " " + oListItem.get_item('Part_x0020_Name')); it shows this:
I don't know why the string is being built with a carriage return though. I use the following code to build the string:
(catalystArray[i] + "/" + baseArray[j]);
If the object from catalystArray does have a carriage return tacked onto it, how would I remove it?
UPDATE:
Solved it. Dumb mistake, but it took me all day to find.
catalystArray[i].replace(/[\n\r]+/g, '');
baseArray[i].replace(/[\n\r]+/g, '');
(catalystArray[i] + "/" + baseArray[j]);
When setting the View XML of a CAML Query object, you should include the outer <View> and <Query> elements in your XML.
If the XML is not in a format recognized by SharePoint, it'll perform a default query that will (usually) return all items. That is likely what is happening in your case, with the code enumerating through all the items in the results. As a consequence, only the last item's values are stored to the variables you expected.

SharePoint List UserCustomAction, In whole sitecollection

I'm getting this to work if I choose a specific list to add the action to. Is there an easy way to enable this custom action on all document libraries in the whole sitecollection?
Code sample:
function createUserCustomActionList() {
var cmd = "<CommandUIExtension><CommandUIDefinitions><CommandUIDefinition Location=\"Ribbon.Documents.Manage.Controls._children\">" +
"<Button Id=\"DiaryAction.Button\" TemplateAlias=\"o1\" Command=\"DiaryCommand\" CommandType=\"General\" LabelText=\"Dela flera\" Image32by32=\"https://eolusvind.sharepoint.com/sites/intranet/_layouts/15/1033/Images/formatmap32x32.png?rev=23\"" +
" Image32by32Top=\"-271\" Image32by32Left=\"-109\" />" +
"</CommandUIDefinition></CommandUIDefinitions><CommandUIHandlers>" +
"<CommandUIHandler Command =\"DiaryCommand\" CommandAction=\"javascript:alert('Hej');\" EnabledScript=\"javascript:SP.ListOperation.Selection.getSelectedItems().length > 1;\" />" +
"</CommandUIHandlers></CommandUIExtension>";
var ctx = new SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle('Dokument');
var uca = list.get_userCustomActions();
var oUserCustomAction = uca.add();
oUserCustomAction.set_location('CommandUI.Ribbon.ListView');
oUserCustomAction.set_commandUIExtension(cmd);
oUserCustomAction.set_sequence(100);
oUserCustomAction.set_title('Dela flera');
oUserCustomAction.update();
ctx.load(list, 'Title' ,'UserCustomActions');
ctx.executeQueryAsync(function () {
alert('Custom action created for ' + list.get_title())
}, function (sender, args) {
alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
});
}
I would suggest the following approach for registering custom button in Documents libraries across site collection:
use RegistrationType set to 2(ContentType) and RegistrationId set to 0x0101 (for Document content type) to register custom action via content type
use site scope user custom action to apply changes across all site
collection
Example
var cmd = "<CommandUIExtension>" +
"<CommandUIDefinitions>" +
"<CommandUIDefinition Location=\"Ribbon.Documents.Manage.Controls._children\">" +
"<Button Id=\"ClickAction.Button\" TemplateAlias=\"o1\" Command=\"ViewCustomPropertiesCommand\" CommandType=\"General\" LabelText=\"View Custom Properties\" Image32by32=\"/_layouts/15/1033/Images/formatmap32x32.png?rev=23\" Image32by32Top=\"-1\" Image32by32Left=\"-171\" />" +
"</CommandUIDefinition></CommandUIDefinitions><CommandUIHandlers>" +
"<CommandUIHandler Command =\"ViewCustomPropertiesCommand\" CommandAction=\"javascript:console.log('View Custom Properties');\" EnabledScript=\"javascript:SP.ListOperation.Selection.getSelectedItems().length > 0;\" />" +
"</CommandUIHandlers>" +
"</CommandUIExtension>";
var ctx = new SP.ClientContext.get_current();
var customAction = ctx.get_site().get_userCustomActions().add(); //1. apply via site scope user custom action
customAction.set_location('CommandUI.Ribbon.ListView');
customAction.set_commandUIExtension(cmd);
customAction.set_sequence(112);
customAction.set_registrationType(2); //2.set to ContentType
customAction.set_registrationId("0x0101"); //2.set Document content type
//customAction.set_title('Document custom button');
customAction.update();
ctx.executeQueryAsync(function () {
console.log('Button has been registered');
}, function (sender, args) {
console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
});

SharePoint REST API membership

I have a column which type is SPGroup in a list. I want to get items via SharePoint REST API, and I only need the items which SPGroup contains the current user. So what's the URL? Thank you for your advice!
You can use the $expand parameter in your REST query to include related entities of data in your query, but since the SharePoint REST service implements the OData protocol, the $filter method can only be invoked on the top level item, thus making it impossible to use on an expanded field.
Depending on how many items your list contains, I'd suggest that you either try to filter it on something other than the current user, or fetch everything and filters the result in your code instead.
A REST query would look something like this:
http://sp/_api/web/lists/getbytitle('MyList')/items?$select=Id,Title,Members/Id&$expand=Members where Members is the SPGroup to be expanded.
In each item that is returned, you will get something like
<content type="application/xml">
<m:properties>
<d:Id m:type="Edm.Int32">60</d:Id>
</m:properties>
</content>
with the Id value of the member. From this, you should be able to write some custom code to filter out only the items that contain the current logged in user.
You can still do what you're looking to do with good old fashioned SOAP with less of headache. I use this method to get Tasks assigned to a user's account or a group they're a member of.
function getAssignedToMe(){
var viewFields = '<ViewFields><FieldRef Name="ID" /><FieldRef Name="Title" /><FieldRef Name="Created" /><FieldRef Name="FileRef" />'
+'<FieldRef Name="AssignedTo" /><FieldRef Name="Status" /></ViewFields>';
// filter by AssignedTo is current user ID or assigned to the current user's group;
var camlQuery = '' +
'<Query>' +
'<Where>' +
'<And>' +
'<Or>' +
'<Membership Type="CurrentUserGroups"><FieldRef Name="AssignedTo"/></Membership>' +
'<Eq><FieldRef Name="AssignedTo"/><Value Type="Integer"><UserID/></Value></Eq>' +
'</Or>' +
'<And>' +
'<Neq><FieldRef Name="Status"/><Value Type="Text">Completed</Value></Neq>' +
'<Geq><FieldRef Name="Created" /><Value Type="DateTime"><Today OffsetDays="-60" /></Value></Geq>' +
'</And>' +
'</And>' +
'</Where>' +
'<OrderBy><FieldRef Name="Created" Ascending="TRUE"/></OrderBy>' +
'</Query>';
getListItems('/mysite', 'Tasks', viewFields, camlQuery, callback, 50);
// transform the returned XML to JSON
function callback(xmlDoc){
var data = [];
$(xmlDoc).find('*').filter(function () {
return this.nodeName.toLowerCase() == 'z:row';
}).each(function (i, el) {
// do something with the data
var id = parseInt($(el).attr('ows_ID'));
data.push({
Id: id
});
/* ... */
});
};
};
function getListItems(siteUrl, listName, viewFields, query, callback, rowLimit) {
if (rowLimit === void 0) { rowLimit = 25; }
var packet = '<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' +
'<soap:Body>' +
'<GetListItems xmlns="http://schemas.microsoft.com/sharepoint/soap/">' +
'<listName>' + listName + '</listName>' +
'<query>' + query + '</query>' +
'<viewFields>' + viewFields + '</viewFields>' +
'<rowLimit>' + rowLimit + '</rowLimit>' +
'</GetListItems>' +
'</soap:Body>' +
'</soap:Envelope>';
var $jqXhr = $.ajax({
url: siteUrl + '/_vti_bin/lists.asmx',
type: 'POST',
dataType: 'xml',
data: packet,
headers: {
"SOAPAction": "http://schemas.microsoft.com/sharepoint/soap/GetListItems",
"Content-Type": "text/xml; charset=utf-8"
}
}).done(function (xmlDoc, status, error) {
callback(xmlDoc);
}).fail(function (jqXhr, status, error) {
callback(null, status + ': ' + error);
});
};

Resources