UNPKG

spservices

Version:

Work with SharePoint's Web Services using jQuery

416 lines (371 loc) 23 kB
define([ 'jquery', '../utils/constants', '../core/SPServices.utils', //--------------------------- // We don't need local variables for these dependencies // because they are added to the jQuery namespace. '../core/SPServices.core' ], function ( $, constants, utils ) { "use strict"; // Function to set up cascading dropdowns on a SharePoint form // (Newform.aspx, EditForm.aspx, or any other customized form.) $.fn.SPServices.SPCascadeDropdowns = function (options) { var opt = $.extend({}, { relationshipWebURL: "", // [Optional] The name of the Web (site) which contains the relationships list relationshipList: "", // The name of the list which contains the parent/child relationships relationshipListParentColumn: "", // The internal name of the parent column in the relationship list relationshipListChildColumn: "", // The internal name of the child column in the relationship list relationshipListSortColumn: "", // [Optional] If specified, sort the options in the dropdown by this column, // otherwise the options are sorted by relationshipListChildColumn parentColumn: "", // The display name of the parent column in the form childColumn: "", // The display name of the child column in the form listName: $().SPServices.SPListNameFromUrl(), // The list the form is working with. This is useful if the form is not in the list context. CAMLQuery: "", // [Optional] For power users, this CAML fragment will be Anded with the default query on the relationshipList CAMLQueryOptions: "<QueryOptions><IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns></QueryOptions>", // [Optional] For power users, ability to specify Query Options promptText: "", // [DEPRECATED] Text to use as prompt. If included, {0} will be replaced with the value of childColumn. Original value "Choose {0}..." noneText: "(None)", // [Optional] Text to use for the (None) selection. Provided for non-English language support. simpleChild: false, // [Optional] If set to true and childColumn is a complex dropdown, convert it to a simple dropdown selectSingleOption: false, // [Optional] If set to true and there is only a single child option, select it matchOnId: false, // By default, we match on the lookup's text value. If matchOnId is true, we'll match on the lookup id instead. completefunc: null, // Function to call on completion of rendering the change. debug: false // If true, show error messages;if false, run silent }, options); var thisParentSetUp = false; var thisFunction = "SPServices.SPCascadeDropdowns"; // Find the parent column's select (dropdown) var parentSelect = $().SPServices.SPDropdownCtl({ displayName: opt.parentColumn }); if (parentSelect.Obj.html() === null && opt.debug) { utils.errBox(thisFunction, "parentColumn: " + opt.parentColumn, constants.TXTColumnNotFound); return; } // Find the child column's select (dropdown) var childSelect = $().SPServices.SPDropdownCtl({ displayName: opt.childColumn }); if (childSelect.Obj.html() === null && opt.debug) { utils.errBox(thisFunction, "childColumn: " + opt.childColumn, constants.TXTColumnNotFound); return; } // If requested and the childColumn is a complex dropdown, convert to a simple dropdown if (opt.simpleChild === true && childSelect.Type === constants.dropdownType.complex) { $().SPServices.SPComplexToSimpleDropdown({ listName: opt.listName, columnName: opt.childColumn }); // Set the childSelect to reference the new simple dropdown childSelect = $().SPServices.SPDropdownCtl({ displayName: opt.childColumn }); } var childColumnRequired, childColumnStatic; // Get information about the childColumn from the current list $().SPServices({ operation: "GetList", async: false, cacheXML: true, listName: opt.listName, completefunc: function (xData) { $(xData.responseXML).find("Fields").each(function () { $(this).find("Field[DisplayName='" + opt.childColumn + "']").each(function () { // Determine whether childColumn is Required childColumnRequired = ($(this).attr("Required") === "TRUE"); childColumnStatic = $(this).attr("StaticName"); // Stop looking; we're done return false; }); }); } }); // Save data about each child column on the parent var childColumn = { opt: opt, childSelect: childSelect, childColumnStatic: childColumnStatic, childColumnRequired: childColumnRequired }; var childColumns = parentSelect.Obj.data("SPCascadeDropdownsChildColumns"); // If this is the first child for this parent, then create the data object to hold the settings if (typeof childColumns === "undefined") { parentSelect.Obj.data("SPCascadeDropdownsChildColumns", [childColumn]); // If we already have a data object for this parent, then add the setting for this child to it } else { childColumns.push(childColumn); parentSelect.Obj.data("SPCascadeDropdownsChildColumns", childColumns); thisParentSetUp = true; } // We only need to bind to the event(s) if we haven't already done so if (!thisParentSetUp) { switch (parentSelect.Type) { // Plain old select case constants.dropdownType.simple: parentSelect.Obj.bind("change", function () { cascadeDropdown(parentSelect); }); break; // Input / Select hybrid case constants.dropdownType.complex: // Bind to any change on the hidden input element parentSelect.optHid.bind("propertychange", function () { cascadeDropdown(parentSelect); }); break; // Multi-select hybrid case constants.dropdownType.multiSelect: // Handle the dblclick on the candidate select $(parentSelect.master.candidateControl).bind("dblclick", function () { cascadeDropdown(parentSelect); }); // Handle the dblclick on the selected values $(parentSelect.master.resultControl).bind("dblclick", function () { cascadeDropdown(parentSelect); }); // Handle button clicks $(parentSelect.master.addControl).bind("click", function () { cascadeDropdown(parentSelect); }); $(parentSelect.master.removeControl).bind("click", function () { cascadeDropdown(parentSelect); }); break; default: break; } } // Fire the change to set the initially allowable values cascadeDropdown(parentSelect); }; // End $.fn.SPServices.SPCascadeDropdowns function cascadeDropdown(parentSelect) { var choices = ""; var parentSelectSelected; var childSelectSelected = null; var newMultiLookupPickerdata; var numChildOptions; var firstChildOptionId; var firstChildOptionValue; // Filter each child column var childColumns = parentSelect.Obj.data("SPCascadeDropdownsChildColumns"); $(childColumns).each(function () { // Break out the data objects for this child column var i; var opt = this.opt; var childSelect = this.childSelect; var childColumnStatic = this.childColumnStatic; var childColumnRequired = this.childColumnRequired; // Get the parent column selection(s) parentSelectSelected = utils.getDropdownSelected(parentSelect, opt.matchOnId); // If the selection hasn't changed, then there's nothing to do right now. This is useful to reduce // the number of Web Service calls when the parentSelect.Type = constants.dropdownType.complex or constants.dropdownType.multiSelect, as there are multiple propertychanges // which don't require any action. The attribute will be unique per child column in case there are // multiple children for a given parent. var allParentSelections = parentSelectSelected.join(constants.spDelim); if (parentSelect.Obj.data("SPCascadeDropdown_Selected_" + childColumnStatic) === allParentSelections) { return; } parentSelect.Obj.data("SPCascadeDropdown_Selected_" + childColumnStatic, allParentSelections); // Get the current child column selection(s) childSelectSelected = utils.getDropdownSelected(childSelect, true); // When the parent column's selected option changes, get the matching items from the relationship list // Get the list items which match the current selection var sortColumn = (opt.relationshipListSortColumn.length > 0) ? opt.relationshipListSortColumn : opt.relationshipListChildColumn; var camlQuery = "<Query><OrderBy><FieldRef Name='" + sortColumn + "'/></OrderBy><Where><And>"; if (opt.CAMLQuery.length > 0) { camlQuery += "<And>"; } // Build up the criteria for inclusion if (parentSelectSelected.length === 0) { // Handle the case where no values are selected in multi-selects camlQuery += "<Eq><FieldRef Name='" + opt.relationshipListParentColumn + "'/><Value Type='Text'></Value></Eq>"; } else if (parentSelectSelected.length === 1) { // Only one value is selected camlQuery += "<Eq><FieldRef Name='" + opt.relationshipListParentColumn + (opt.matchOnId ? "' LookupId='True'/><Value Type='Integer'>" : "'/><Value Type='Text'>") + utils.escapeColumnValue(parentSelectSelected[0]) + "</Value></Eq>"; } else { var compound = (parentSelectSelected.length > 2); for (i = 0; i < (parentSelectSelected.length - 1); i++) { camlQuery += "<Or>"; } for (i = 0; i < parentSelectSelected.length; i++) { camlQuery += "<Eq><FieldRef Name='" + opt.relationshipListParentColumn + (opt.matchOnId ? "' LookupId='True'/><Value Type='Integer'>" : "'/><Value Type='Text'>") + utils.escapeColumnValue(parentSelectSelected[i]) + "</Value></Eq>"; if (i > 0 && (i < (parentSelectSelected.length - 1)) && compound) { camlQuery += "</Or>"; } } camlQuery += "</Or>"; } if (opt.CAMLQuery.length > 0) { camlQuery += opt.CAMLQuery + "</And>"; } // Make sure we don't get any items which don't have the child value camlQuery += "<IsNotNull><FieldRef Name='" + opt.relationshipListChildColumn + "' /></IsNotNull>"; camlQuery += "</And></Where></Query>"; $().SPServices({ operation: "GetListItems", // Force sync so that we have the right values for the child column onchange trigger async: false, webURL: opt.relationshipWebURL, listName: opt.relationshipList, // Filter based on the currently selected parent column's value CAMLQuery: camlQuery, // Only get the parent and child columns CAMLViewFields: "<ViewFields><FieldRef Name='" + opt.relationshipListParentColumn + "' /><FieldRef Name='" + opt.relationshipListChildColumn + "' /></ViewFields>", // Override the default view rowlimit and get all appropriate rows CAMLRowLimit: 0, // Even though setting IncludeMandatoryColumns to FALSE doesn't work as the docs describe, it fixes a bug in GetListItems with mandatory multi-selects CAMLQueryOptions: opt.CAMLQueryOptions, completefunc: function (xData) { // Handle errors $(xData.responseXML).find("errorstring").each(function () { var thisFunction = "SPServices.SPCascadeDropdowns"; var errorText = $(this).text(); if (opt.debug && errorText === "One or more field types are not installed properly. Go to the list settings page to delete these fields.") { utils.errBox(thisFunction, "relationshipListParentColumn: " + opt.relationshipListParentColumn + " or " + "relationshipListChildColumn: " + opt.relationshipListChildColumn, "Not found in relationshipList " + opt.relationshipList); } else if (opt.debug && errorText === "Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).") { utils.errBox(thisFunction, "relationshipList: " + opt.relationshipList, "List not found"); } }); // Add an explanatory prompt switch (childSelect.Type) { case constants.dropdownType.simple: // Remove all of the existing options childSelect.Obj[0].innerHTML = ""; // $(childSelect.Obj).find("option").remove(); // If the column is required or the promptText option is empty, don't add the prompt text if (!childColumnRequired && (opt.promptText.length > 0)) { childSelect.Obj.append("<option value='0'>" + opt.promptText.replace(/\{0\}/g, opt.childColumn) + "</option>"); } else if (!childColumnRequired) { childSelect.Obj.append("<option value='0'>" + opt.noneText + "</option>"); } break; case constants.dropdownType.complex: // If the column is required, don't add the "(None)" option choices = childColumnRequired ? "" : opt.noneText + "|0"; childSelect.Obj.val(""); break; case constants.dropdownType.multiSelect: // Remove all of the existing options $(childSelect.master.candidateControl).find("option").remove(); newMultiLookupPickerdata = ""; break; default: break; } // Get the count of items returned and save it so that we can select if it's a single option // The item count is stored thus: <rs:data ItemCount="1"> numChildOptions = parseFloat($(xData.responseXML).SPFilterNode("rs:data").attr("ItemCount")); // Add an option for each child item $(xData.responseXML).SPFilterNode("z:row").each(function () { var thisOption = {}; // If relationshipListChildColumn is a Lookup column, then the ID should be for the Lookup value, // else the ID of the relationshipList item var thisValue = $(this).attr("ows_" + opt.relationshipListChildColumn); if (typeof thisValue !== "undefined" && thisValue.indexOf(constants.spDelim) > 0) { thisOption = new utils.SplitIndex(thisValue); } else { thisOption.id = $(this).attr("ows_ID"); thisOption.value = thisValue; } // If the relationshipListChildColumn is a calculated column, then the value isn't preceded by the ID, // but by the datatype. In this case, thisOption.id should be the ID of the relationshipList item. // e.g., float;#12345.67 if (isNaN(thisOption.id)) { thisOption.id = $(this).attr("ows_ID"); } // Save the id and value for the first child option in case we need to select it (selectSingleOption option is true) firstChildOptionId = thisOption.id; firstChildOptionValue = thisOption.value; switch (childSelect.Type) { case constants.dropdownType.simple: var selected = ($(this).attr("ows_ID") === childSelectSelected[0]) ? " selected='selected'" : ""; childSelect.Obj.append("<option" + selected + " value='" + thisOption.id + "'>" + thisOption.value + "</option>"); break; case constants.dropdownType.complex: if (thisOption.id === childSelectSelected[0]) { childSelect.Obj.val(thisOption.value); } choices = choices + ((choices.length > 0) ? "|" : "") + thisOption.value + "|" + thisOption.id; break; case constants.dropdownType.multiSelect: $(childSelect.master.candidateControl).append("<option value='" + thisOption.id + "'>" + thisOption.value + "</option>"); newMultiLookupPickerdata += thisOption.id + "|t" + thisOption.value + "|t |t |t"; break; default: break; } }); switch (childSelect.Type) { case constants.dropdownType.simple: childSelect.Obj.trigger("change"); // If there is only one option and the selectSingleOption option is true, then select it if (numChildOptions === 1 && opt.selectSingleOption === true) { $(childSelect.Obj).find("option[value!='0']:first").attr("selected", "selected"); } break; case constants.dropdownType.complex: // Set the allowable choices childSelect.Obj.attr("choices", choices); // If there is only one option and the selectSingleOption option is true, then select it if (numChildOptions === 1 && opt.selectSingleOption === true) { // Set the input element value $(childSelect.Obj).val(firstChildOptionValue); // Set the value of the optHid input element childSelect.optHid.val(firstChildOptionId); } // If there's no selection, then remove the value in the associated hidden input element (optHid) if (childSelect.Obj.val() === "") { childSelect.optHid.val(""); } break; case constants.dropdownType.multiSelect: // Clear the master childSelect.master.data = ""; childSelect.MultiLookupPickerdata.val(newMultiLookupPickerdata); // Clear any prior selections that are no longer valid or aren't selected $(childSelect.master.resultControl).find("option").each(function () { var thisSelected = $(this); thisSelected.prop("selected", true); $(childSelect.master.candidateControl).find("option[value='" + thisSelected.val() + "']").each(function () { thisSelected.prop("selected", false); }); }); GipRemoveSelectedItems(childSelect.master); // Hide any options in the candidate list which are already selected $(childSelect.master.candidateControl).find("option").each(function () { var thisSelected = $(this); $(childSelect.master.resultControl).find("option[value='" + thisSelected.val() + "']").each(function () { thisSelected.remove(); }); }); GipAddSelectedItems(childSelect.master); // Set master.data to the newly allowable values childSelect.master.data = GipGetGroupData(newMultiLookupPickerdata); // Trigger a dblclick so that the child will be cascaded if it is a multiselect. $(childSelect.master.candidateControl).trigger("dblclick"); break; default: break; } } }); // If present, call completefunc when all else is done if (opt.completefunc !== null) { opt.completefunc(); } }); // $(childColumns).each(function() } // End cascadeDropdown return $; });