sp-dev-fx-property-controls
sp-dev-fx-property-controls copied to clipboard
Nested CollectionField Data
Category
- [X] Question
Version
Please specify what version of the library you are using: [ 3.6.0 ]
Expected / Desired Behavior / Question
If you are reporting an issue please describe the expected behavior. If you are suggesting an enhancement please describe thoroughly the enhancement, how it can be achieved, and expected benefit. If you are asking a question, ask away!
Hello,
I am trying to nest a collection data inside another collection data that renders only when one of the fields in the original collection data is checked. Is there a way to do this? I have this below but it does not work so far:
PropertyFieldCollectionData("gridItems", {
key: "gridItemsFieldId",
label: "Grid Data",
panelHeader: "Grid Data Panel",
manageBtnLabel: "Manage grid data",
value: this.properties.gridItems,
fields: [
{
id: "Title",
title: "Item Title",
type: CustomCollectionFieldType.string,
required: true,
},
{
id: "Description",
title: "Item Description",
type: CustomCollectionFieldType.string,
},
{
id: "Hyperlink",
title: "Link to Open",
type: CustomCollectionFieldType.url,
required: true,
},
{
id: "isDropdown",
title: "Does this need a dropdown?",
type: CustomCollectionFieldType.boolean,
required: false,
},
{
id: "dropData",
title: "Manage Drop Down Data",
type: CustomCollectionFieldType.custom,
isVisible: this.dropDataVisible,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(ICustomCollectionField, {
key: "dropItemsFieldId",
label: "Dropdown Data",
panelHeader: "Drop Data Panel",
manageBtnLabel: "Manage drop down data",
value: this.properties.gridItems[0].dropData,
fields: [
{
id: "dropText",
title: "Item Title",
type: CustomCollectionFieldType.string,
required: true,
},
{
id: "dropLink",
title: "Text Link",
type: CustomCollectionFieldType.url,
},
]
}))
}
},
{
id: "backgroundColor",
title: "Pick background color:",
type: CustomCollectionFieldType.dropdown,
options: this.backgroundOptions,
required: true
},
{
id: "filePicker",
title: "Select File",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FilePicker, {
key: itemId,
context: this.context,
buttonLabel: "Select File",
onChange: (filePickerResult: IFilePickerResult[]) => {
console.log('changing....', field);
onUpdate(field.id, filePickerResult[0]);
this.context.propertyPane.refresh();
this.render();
},
onSave:
async (filePickerResult: IFilePickerResult[]) => {
for (const filePicked of filePickerResult) {
if (filePicked.fileAbsoluteUrl == null) {
filePicked.downloadFileContent().then(async r => {
let fileresult = await this.sp.web.getFolderByServerRelativePath(`${this.context.pageContext.site.serverRelativeUrl}/SiteAssets/SitePages`).files.addChunked(filePicked.fileName, r);
this.properties.gridItems[0].filePickerResult = filePicked;
this.properties.gridItems[0].filePickerResult.fileAbsoluteUrl = `${this.context.pageContext.site.absoluteUrl}/SiteAssets/SitePages/${fileresult.data.Name}`;
this.context.propertyPane.refresh();
this.render();
});
} else {
console.log('saving....', filePicked);
onUpdate(field.id, filePicked);
this.context.propertyPane.refresh();
this.render();
}
}
},
hideLocalUploadTab: false,
hideLocalMultipleUploadTab: true,
hideLinkUploadTab: false,
})
);
},
required: true
},
],
disabled: false,
},
I would really appreciate any help with this! Thank you.
Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.
Maybe it's possible with my new TreeCollectionField Control PR #456 , also see Issue #451
@edarroudi Is this available for use?
@walleford not right, PR has to be looked at and then finished. Can't give you a timeline on that.
I see @edarroudi until then do you happen to know of a way to conditionally render a field within the collection field data? I have tried Boolean && {field element} as well as using (…{} : {}) for determine whether something is true and rendering an object based on that.
@walleford I just managed to create several levels of nested PropertyFieldCollectionDataHost. I don't know if it is what you are looking for but maybe it helps you along:
I have a filterConfig property that has a listConfig array sub property. The filterConfig property is the target of the PropertyFieldCollectionData on the top level and one of the fields is a custom render of a PropertyFieldCollectionDataHost to configure the listConfig sub property (and in that there is another sub property ... (not shown))
{ id: 'listConfig', type: CustomCollectionFieldType.custom, isVisible: (field: ICustomCollectionField, items: any[]) => { return items && items.filter(i => i.inputType === FilterControlTypes.DropDown).length > 0; // Whole column is only visible if at least one row needs it. }, onCustomRender: (field, value, onUpdate, item: IFilterConfig, rowUniqueId, onCustomFieldValidation) => { return ( React.createElement(PropertyFieldCollectionDataHost, { onChanged: (items) => { onUpdate(field.id, items); }, disabled: (item.inputType !== FilterControlTypes.DropDown && item.inputType !== FilterControlTypes.MultiDropDown), label: '', value: **item.listConfig**, key: 'listConfigSubControl', fields: [ { id: 'listId', title: strings.PropertyPaneList, type: CustomCollectionFieldType.custom, required: false, onCustomRender: (**subField, subValue, subOnUpdate, subItem: IListConfig, subRowUniqueId, subOnCustomFieldValidation**) => { return ( React.createElement('div',... )) ); } }, ] }) ); }
By using different names for the parameters of onCustomRender on each level of the nesting I can still access the values of the row item from each previous level.
@IRRDC Thank you for that, it is really appreciated. This is what I have gotten to so far,
{
id: "dropData",
title: "Manage Drop Data",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FieldCollectionData, {
key: 'dropDataFieldId',
label: "Drop Data",
panelHeader: "Drop Data Panel",
manageBtnLabel: "Manage drop data",
fields: [
{
id: "Title",
title: "Item Title",
type: CustomCollectionFieldType.string,
},
{
id: "Link",
title: "Item Description",
type: CustomCollectionFieldType.url,
},
],
value: this.properties.gridItems ? this.properties.gridItems[0].dropData : null,
onChanged: (changedDropData: ICustomCollectionField[]) => {
console.log('changing....', field);
onUpdate(field.id, changedDropData[0]);
this.context.propertyPane.refresh();
this.render();
}
})
)
}
},
However, I can't seem to get the value of the custom rendered field collection data to work properly, I don't really know what to set it as. I am also wanting to us isVisible to make it available based on whether another field within the top level collection data is checked or not, but haven't been able to get that to work either. Let me know if you have any suggestions!
Do you specifically want to access the first item here? this.properties.gridItems[0].dropData The gridItems collection will only have updated values once the control closes. To access the gridItem that matches the current row you can refer to the "item" property of the surrounding collection (just rename the inner "item" as I did in my badly formatted example to "subitem" to distinguish them). That surrounding "item" instance will have the (as yet unsaved) current value of the topmost collection.
@IRRDC so when you used subItem: IListConfig, this is allowing the nested fields to reference their parent row? What I sent you above is a single field within the collection data, that is custom rendering another collection data group. Just want to make sure I am tracking what you are saying correctly.
Yes, you are nesting to custom renders, each with a signature of (field, value, onUpdate, item, itemId, onError). If you change the parameter names in the signature of the nested custom renders you can refer to the parent field, value, etc. The "item" parameter of the parent contains the parent row value that is currently being edited.
I see, thank you for that. Would this also theoretically work for referencing another field in the parent collection?
{
id: "isDropdown",
title: "Does this need a dropdown?",
type: CustomCollectionFieldType.boolean,
required: false,
},
{
id: "backgroundColor",
title: "Pick background color:",
type: CustomCollectionFieldType.dropdown,
options: this.backgroundOptions
},
{
id: "dropData",
title: "Manage Drop Data",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FieldCollectionData, {
key: 'dropDataFieldId',
label: "Drop Data",
panelHeader: "Drop Data Panel",
manageBtnLabel: "Manage drop data",
fields: [
{
id: "Title",
title: "Item Title",
type: CustomCollectionFieldType.string,
},
{
id: "Link",
title: "Item Description",
type: CustomCollectionFieldType.url,
},
],
value: item.dropData,
onChanged: (changedDropData: ICustomCollectionField[]) => {
console.log('changing....', field);
onUpdate(field.id, changedDropData[0]);
this.context.propertyPane.refresh();
this.render();
}
})
)
}
},
I want dropData to reference isDropdown to determine whether or not it should render, or be available I guess.
Yes. In your code snippet you have to change
onCustomRender: (field, value, onUpdate, item, itemId, onError)
to
onCustomRender: (field, value, onUpdate, subItem, itemId, onError)
and
value: item.dropData
to
value: subItem
since in the context of the custom render "subItem" is your parent row's "dropData" property. Within the custom render you can access "item.isDropdown" (not "subItem") to get the current value of that control.
@IRRDC wow, thanks. This should help me almost finish lol. Now to the tsx and actually rendering everything correctly
@IRRDC However, when I use it this way:
value: subitem,
disabled: item.isDropdown ? false : true,
I am getting an error on item.isDropdown saying it can't be found. If the parent isn't a "Custom collection field" rather a regular PropertyFieldCollectionData, will item reference it properly?
Sorry, I was thinking in too many nested layers just then. The code I'm working on right now is messing with my head. You don't need to rename item, set value: item.dropData, and disabled: item.isDropdown ? false : true.
It worked, and just how I wanted it to. Thank you for the help
@IRRDC One last question for you, how are you getting the nested items to persist after clicking add and save? I am using this onChanged function (which works in a non-nested field) but it isn't working for this nested fieldcollectiondata. I would appreciate any help you can give.
onChanged: (fieldCollectionData: ICustomCollectionField[]) => {
console.log('changing....', field);
onUpdate(field.id, fieldCollectionData[0]);
this.context.propertyPane.refresh();
this.render();
@walleford I'm just using onChanged: (items) => { onUpdate(field.id, items); }, in the nested PropertyFieldCollectionDataHost.
@IRRDC thank you for all of the help, I am going to close this now because that solved the issue.
@IRRDC hello again... I had to rewrite this code... long story short, backups didn't happen so I lost it. I am pretty sure I wrote it the exact same way, but it isn't persisting after I click add and save and I am not sure why, do you see anything wrong with this?
{
id: "dropData",
title: "Manage Drop Data",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate, item, itemId, onError) => {
return (
React.createElement(FieldCollectionData, {
key: 'dropDataFieldId',
panelHeader: "Drop Data Panel",
manageBtnLabel: "Manage drop data",
disabled: item.needsDropdown ? false : true,
fields: [
{
id: "Title",
title: "Item Title",
type: CustomCollectionFieldType.string,
},
{
id: "Link",
title: "Item Hyperlink",
type: CustomCollectionFieldType.url,
},
],
value: item.dropData,
onChanged: (items) => { onUpdate(field.id, items); },
})
)
}
},
@walleford I'm sorry but I don't see what is wrong with your code.
@walleford - I assume the question was more or less answered and the issue could be closed.
This issue has been automatically marked as stale because it has marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within next 7 days of this comment. Thank you for your contributions to SharePoint Developer activities.
Closing issue due no response from original author. If this issue is still occurring, please open a new issue with additional details. Notice that if you have included another related issue as additional comment on this, please open that also as separate issue, so that we can track it independently.