Merge pull request #1541 from sharepointknight/master
Add cascading lookup support
This commit is contained in:
commit
976b458fb2
|
@ -49,7 +49,9 @@ The web part allows configuring which list to use and if a form for adding a new
|
|||
| 1.0.1 | February 22, 2019 | Updated to SPFx 1.7.1 and dependencies, Added Turkish translation, Added RichText Mode and Tinymce Editor |
|
||||
| 1.0.2 | October 14, 2019 | Updated to SPFx 1.9.1 and dependencies |
|
||||
| 1.0.3 | July 7, 2020 | Updated to SPFx 1.10.0 and dependencies. Fixed required field validation (Harsha Vardhini) |
|
||||
| 1.0.4 | September 12, 2020| Added support for User, UserMulti, Taxonomy, and TaxonomyMulti field types (Ryan Schouten) |
|
||||
| 1.0.4 | September 12, 2020| Added support for User, UserMulti, Taxonomy, and TaxonomyMulti field types |
|
||||
| 1.0.5 | September 26, 2020| Fix date handling problems and redirect after edit |
|
||||
| 1.0.6 | October 8, 2020 | Added support for cascading lookup fields |
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "react-form-webpart",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
export class SPHelper {
|
||||
public static LookupValueToString(value: any | Array<any>): string {
|
||||
return value.map((item) => { return `${item.key};#${item.text}`; }).join(";#");
|
||||
}
|
||||
public static LookupValueFromString(value: string): Array<any> {
|
||||
if (value == null) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
const splitArray = value.split(';#');
|
||||
let values = splitArray.filter((item, idx) => (idx % 2 === 0))
|
||||
.map((comp, idx) => {
|
||||
return { key: Number(comp), text: (splitArray.length >= idx * 2 + 1) ? splitArray[idx * 2 + 1] : '' };
|
||||
});
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
|||
|
||||
export interface IListFormService {
|
||||
getFieldSchemasForForm: (webUrl: string, listUrl: string, formType: ControlMode) => Promise<IFieldSchema[]>;
|
||||
getLookupfieldOptions: (fieldSchema: any, webUrl: string) => Promise<any[]>;
|
||||
getLookupfieldsOnList: (listUrl: string, webUrl: string, formType: ControlMode) => Promise<any[]>;
|
||||
getDataForForm: (webUrl: string, listUrl: string, itemId: number, formType: ControlMode) => Promise<any>;
|
||||
getExtraFieldData(data: any, fieldSchema: any, ctx: IWebPartContext, siteUrl: string);
|
||||
updateItem: (webUrl: string, listUrl: string, itemId: number,
|
||||
|
|
|
@ -25,7 +25,7 @@ export class ListFormService implements IListFormService {
|
|||
* @param formType The type of form (Display, New, Edit)
|
||||
* @returns Promise object represents the array of field schema for all relevant fields for this list form.
|
||||
*/
|
||||
public getFieldSchemasForForm(webUrl: string, listUrl: string, formType: ControlMode): Promise<IFieldSchema[]> {
|
||||
public async getFieldSchemasForForm(webUrl: string, listUrl: string, formType: ControlMode): Promise<IFieldSchema[]> {
|
||||
return new Promise<IFieldSchema[]>((resolve, reject) => {
|
||||
const httpClientOptions: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
|
@ -63,7 +63,46 @@ export class ListFormService implements IListFormService {
|
|||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Retrieves the options for a lookup field
|
||||
*
|
||||
* @param fieldSchema The field schema for the lookup field.
|
||||
* @param webUrl The absolute Url to the SharePoint site.
|
||||
* @returns Promise representing an object containing all the field values for the lookup field.
|
||||
*/
|
||||
public async getLookupfieldOptions(fieldSchema: any, webUrl: string): Promise<any[]> {
|
||||
const endpoint = `${webUrl}/_api/Web/lists/getbyid('${fieldSchema.LookupListId}')/items?$orderby=${fieldSchema.LookupFieldName}`;
|
||||
|
||||
try {
|
||||
let resp: SPHttpClientResponse = await this.spHttpClient.get(endpoint, SPHttpClient.configurations.v1);
|
||||
if (resp.ok) {
|
||||
let json = await resp.json();
|
||||
return json.value.map((x) => {
|
||||
return { LookupId: x.ID, LookupValue: x[fieldSchema.LookupFieldName], x };
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return [];
|
||||
|
||||
}
|
||||
/**
|
||||
* Retrieves the options for a lookup field
|
||||
*
|
||||
* @param fieldSchema The field schema for the lookup field.
|
||||
* @param webUrl The absolute Url to the SharePoint site.
|
||||
* @returns Promise representing an object containing all the field values for the lookup field.
|
||||
*/
|
||||
public async getLookupfieldsOnList(listUrl: string, webUrl: string, formType: ControlMode): Promise<any[]> {
|
||||
let fields = await this.getFieldSchemasForForm(webUrl, listUrl, formType);
|
||||
fields = fields.filter((x) => {
|
||||
return x.FieldType.indexOf("Lookup") === 0;
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
/**
|
||||
* Retrieves the data for a specified SharePoint list form.
|
||||
*
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
|
|||
this.listService = new ListService(this.context.spHttpClient);
|
||||
//Polyfill array find
|
||||
if (!Array.prototype["find"]) {
|
||||
Array.prototype["find"] = function (predicate) {
|
||||
Array.prototype["find"] = function (predicate, argument) {
|
||||
if (this == null) {
|
||||
throw new TypeError('Array.prototype.find called on null or undefined');
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
|
|||
}
|
||||
var list = Object(this);
|
||||
var length = list.length >>> 0;
|
||||
var thisArg = arguments[1];
|
||||
var thisArg = argument;
|
||||
var value;
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { ListFormService } from '../../../common/services/ListFormService';
|
|||
import { ISPPeopleSearchService } from '../../../common/services/ISPPeopleSearchService';
|
||||
import { SPPeopleSearchService } from '../../../common/services/SPPeopleSearchService';
|
||||
import { GroupService } from '../../../common/services/GroupService';
|
||||
import { SPHelper } from '../../../common/SPHelper';
|
||||
|
||||
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
|
||||
import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/Button';
|
||||
|
@ -244,6 +245,29 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
|
|||
listUrl,
|
||||
formType,
|
||||
);
|
||||
let lookupCount = 0;
|
||||
for (let i = 0; i < fieldsSchema.length; i++) {
|
||||
if (fieldsSchema[i].FieldType === "Lookup" || fieldsSchema[i].FieldType === "LookupMulti") {
|
||||
fieldsSchema[i].Choices = await this.listFormService.getLookupfieldOptions(fieldsSchema[i], this.props.webUrl);
|
||||
lookupCount += 1;
|
||||
if (lookupCount > 1) {
|
||||
let lookups = await this.listFormService.getLookupfieldsOnList(fieldsSchema[i]["LookupListUrl"], this.props.webUrl, formType);
|
||||
for (let j = 0; j < lookups.length; j++) {
|
||||
let parent = fieldsSchema.filter((x) => {
|
||||
return x.LookupListId === lookups[j].LookupListId;
|
||||
});
|
||||
|
||||
for (let k = 0; k < parent.length; k++) {
|
||||
if (parent[k]["Dependent"] == null) {
|
||||
parent[k]["Dependent"] = { Field: fieldsSchema[i], ValueField: lookups[j].InternalName };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let userfields = fieldsSchema.filter((x) => {
|
||||
return x.FieldType === "User" || x.FieldType === "UserMulti";
|
||||
});
|
||||
|
@ -281,9 +305,19 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
|
|||
let dataObj = await this.listFormService.getDataForForm(this.props.webUrl, listUrl, id, formType);
|
||||
const schema = this.state.fieldsSchema;
|
||||
dataObj = await this.listFormService.getExtraFieldData(dataObj, schema, this.props.context, this.props.webUrl);
|
||||
for (let i = 0; i < schema.length; i++) {
|
||||
if (schema[i]["Dependent"] != null) {
|
||||
let updateField = schema[i]["Dependent"].Field.InternalName;
|
||||
let fieldsSchema = schema;
|
||||
let dependee = fieldsSchema.filter((x) => {
|
||||
return x.InternalName === updateField;
|
||||
});
|
||||
dependee[0]["DependerValue"] = { Value: dataObj[schema[i].InternalName], Field: schema[i]["Dependent"].ValueField };
|
||||
}
|
||||
}
|
||||
// We shallow clone here, so that changing values on dataObj object fields won't be changing in originalData too
|
||||
const dataObjOriginal = { ...dataObj };
|
||||
this.setState({ ...this.state, data: dataObj, originalData: dataObjOriginal, isLoadingData: false });
|
||||
this.setState({ ...this.state, data: dataObj, fieldsSchema: schema, originalData: dataObjOriginal, isLoadingData: false });
|
||||
} catch (error) {
|
||||
const errorText = `${strings.ErrorLoadingData}${id}: ${error}`;
|
||||
this.setState({ ...this.state, data: {}, isLoadingData: false, errors: [...this.state.errors, errorText] });
|
||||
|
@ -324,20 +358,75 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
|
|||
);
|
||||
}
|
||||
else {
|
||||
this.setState((prevState, props) => {
|
||||
return {
|
||||
...prevState,
|
||||
data: { ...prevState.data, [fieldName]: newValue },
|
||||
fieldErrors: {
|
||||
...prevState.fieldErrors,
|
||||
[fieldName]:
|
||||
(prevState.fieldsSchema.filter((item) => item.InternalName === fieldName)[0].Required) && !newValue
|
||||
? strings.RequiredValueMessage
|
||||
: ''
|
||||
// Check for if any other fields are dependent on this one
|
||||
if (schema["Dependent"] != null) {
|
||||
let dependee = [];
|
||||
let fieldsSchema = this.state.fieldsSchema;
|
||||
let updateField = schema["Dependent"].Field.InternalName;
|
||||
let valueField = schema["Dependent"].ValueField;
|
||||
let dependentValue = newValue;
|
||||
do {
|
||||
dependee = fieldsSchema.filter((x) => {
|
||||
return x.InternalName === updateField;
|
||||
});
|
||||
dependee[0]["DependerValue"] = { Value: dependentValue, Field: valueField };
|
||||
if (dependee.length > 0 && dependee[0]["Dependent"] != null) {
|
||||
//Need to remove invalid options from dependent value
|
||||
let tempVal = SPHelper.LookupValueFromString(this.state.data[dependee[0].InternalName]);
|
||||
let depend = SPHelper.LookupValueFromString(dependentValue);
|
||||
let choices = dependee[0].Choices;
|
||||
|
||||
let values = depend.map((item) => item.key);
|
||||
choices = choices.filter((x) => {
|
||||
let matches = values.filter((itm) => { return x.x[`${valueField}Id`] == itm; });
|
||||
return matches.length > 0;
|
||||
});
|
||||
|
||||
tempVal = tempVal.filter((x) => {
|
||||
return choices.find((y) => { return y.LookupId == x.key; }) != null;
|
||||
});
|
||||
|
||||
dependentValue = SPHelper.LookupValueToString(tempVal);
|
||||
updateField = dependee[0]["Dependent"].Field.InternalName;
|
||||
valueField = dependee[0]["Dependent"].ValueField;
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
else {
|
||||
updateField = null;
|
||||
}
|
||||
} while (updateField != null);
|
||||
|
||||
this.setState((prevState, props) => {
|
||||
return {
|
||||
...prevState,
|
||||
data: { ...prevState.data, [fieldName]: newValue },
|
||||
fieldsSchema,
|
||||
fieldErrors: {
|
||||
...prevState.fieldErrors,
|
||||
[fieldName]:
|
||||
(prevState.fieldsSchema.filter((item) => item.InternalName === fieldName)[0].Required) && !newValue
|
||||
? strings.RequiredValueMessage
|
||||
: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
else {
|
||||
this.setState((prevState, props) => {
|
||||
return {
|
||||
...prevState,
|
||||
data: { ...prevState.data, [fieldName]: newValue },
|
||||
fieldErrors: {
|
||||
...prevState.fieldErrors,
|
||||
[fieldName]:
|
||||
(prevState.fieldsSchema.filter((item) => item.InternalName === fieldName)[0].Required) && !newValue
|
||||
? strings.RequiredValueMessage
|
||||
: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,15 +2,31 @@ import * as React from 'react';
|
|||
import { ISPFormFieldProps } from './SPFormField';
|
||||
import { Dropdown, IDropdownProps, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { css } from 'office-ui-fabric-react/lib/Utilities';
|
||||
import { SPHelper } from '../../../../common/SPHelper';
|
||||
|
||||
import * as strings from 'FormFieldStrings';
|
||||
import styles from './SPFormField.module.scss';
|
||||
|
||||
const SPFieldLookupEdit: React.SFC<ISPFormFieldProps> = (props) => {
|
||||
let options = props.fieldSchema.Choices.map((option) => ({ key: option.LookupId, text: option.LookupValue }));
|
||||
let choices = props.fieldSchema.Choices;
|
||||
if (props.fieldSchema["DependerValue"] != null) {
|
||||
let parentField = props.fieldSchema["DependerValue"].Field;
|
||||
let parentValue = props.fieldSchema["DependerValue"].Value;
|
||||
const splitArray = parentValue.split(';#');
|
||||
let values = splitArray.filter((item, idx) => (idx % 2 === 0));
|
||||
let parentId = Number(parentValue.split(";#")[0]);
|
||||
if (values.length > 0) {
|
||||
choices = choices.filter((x) => {
|
||||
let matches = values.filter((itm) => { return x.x[`${parentField}Id`] == itm; });
|
||||
return matches.length > 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
let options = choices.map((option) => ({ key: option.LookupId, text: option.LookupValue }));
|
||||
if (props.fieldSchema.FieldType !== 'LookupMulti') {
|
||||
if (!props.required) { options = [{ key: 0, text: strings.LookupEmptyOptionText }].concat(options); }
|
||||
const value = props.value ? Number(props.value.split(';#')[0]) : 0;
|
||||
|
||||
return <Dropdown
|
||||
className={css(styles.dropDownFormField, 'ard-lookupFormField')}
|
||||
options={options}
|
||||
|
@ -21,8 +37,11 @@ const SPFieldLookupEdit: React.SFC<ISPFormFieldProps> = (props) => {
|
|||
let values = [];
|
||||
if (props.value) {
|
||||
const splitArray = props.value.split(';#');
|
||||
values = splitArray.filter((item, idx) => (idx % 2 === 0))
|
||||
.map((comp, idx) => ({ key: Number(comp), text: (splitArray.length > idx + 1) ? splitArray[idx + 1] : '' }));
|
||||
values = SPHelper.LookupValueFromString(props.value);
|
||||
//Need to remove invalid options for better cascading
|
||||
values = values.filter((x) => {
|
||||
return options.filter((option) => option.key == x.key).length > 0;
|
||||
});
|
||||
}
|
||||
return <Dropdown
|
||||
className={css(styles.dropDownFormField, 'ard-lookupMultiFormField')}
|
||||
|
@ -42,7 +61,7 @@ function getUpdatedValue(oldValues: Array<{ key: number, text: string }>, change
|
|||
} else {
|
||||
newValues = oldValues.filter((item) => item.key !== changedItem.key);
|
||||
}
|
||||
return newValues.reduce((valStr, item) => valStr + `${item.key};#${item.text}`, '');
|
||||
return SPHelper.LookupValueToString(newValues);
|
||||
}
|
||||
|
||||
export default SPFieldLookupEdit;
|
||||
|
|
|
@ -17,7 +17,7 @@ const SPFieldTaxonomyEdit: React.SFC<ISPFormFieldProps> = (props) => {
|
|||
context = props.context;
|
||||
termsetId = props.fieldSchema.TermSetId;
|
||||
allowMultipleSelections = props.fieldSchema.AllowMultipleValues;
|
||||
if (props.value != null) {
|
||||
if (props.value != null && props.value != "") {
|
||||
terms = [];
|
||||
let multiparts = props.value.split(";");
|
||||
multiparts.forEach((x) => {
|
||||
|
|
Loading…
Reference in New Issue