sp-dev-fx-property-controls icon indicating copy to clipboard operation
sp-dev-fx-property-controls copied to clipboard

Nested CollectionField Data

Open walleford opened this issue 3 years ago • 21 comments

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.

walleford avatar May 20 '22 21:05 walleford

Thank you for reporting this issue. We will be triaging your incoming issue as soon as possible.

ghost avatar May 20 '22 21:05 ghost

Maybe it's possible with my new TreeCollectionField Control PR #456 , also see Issue #451

edarroudi avatar May 30 '22 08:05 edarroudi

@edarroudi Is this available for use?

walleford avatar May 31 '22 14:05 walleford

@walleford not right, PR has to be looked at and then finished. Can't give you a timeline on that.

edarroudi avatar Jun 01 '22 10:06 edarroudi

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 avatar Jun 01 '22 13:06 walleford

@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 avatar Jul 07 '22 11:07 IRRDC

@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!

walleford avatar Jul 07 '22 13:07 walleford

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 avatar Jul 07 '22 13:07 IRRDC

@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.

walleford avatar Jul 07 '22 13:07 walleford

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.

IRRDC avatar Jul 07 '22 13:07 IRRDC

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.

walleford avatar Jul 07 '22 13:07 walleford

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 avatar Jul 07 '22 13:07 IRRDC

@IRRDC wow, thanks. This should help me almost finish lol. Now to the tsx and actually rendering everything correctly

walleford avatar Jul 07 '22 13:07 walleford

@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?

walleford avatar Jul 07 '22 14:07 walleford

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.

IRRDC avatar Jul 07 '22 14:07 IRRDC

image It worked, and just how I wanted it to. Thank you for the help

walleford avatar Jul 07 '22 14:07 walleford

@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 avatar Jul 12 '22 18:07 walleford

@walleford I'm just using onChanged: (items) => { onUpdate(field.id, items); }, in the nested PropertyFieldCollectionDataHost.

IRRDC avatar Jul 13 '22 06:07 IRRDC

@IRRDC thank you for all of the help, I am going to close this now because that solved the issue.

walleford avatar Jul 15 '22 14:07 walleford

@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 avatar Sep 23 '22 18:09 walleford

@walleford I'm sorry but I don't see what is wrong with your code.

IRRDC avatar Sep 26 '22 06:09 IRRDC

@walleford - I assume the question was more or less answered and the issue could be closed.

AJIXuMuK avatar Feb 12 '23 18:02 AJIXuMuK

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.

ghost avatar Feb 19 '23 19:02 ghost

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.

ghost avatar Feb 26 '23 20:02 ghost