0

I'm new to SPFX / React Development, So I thought I'd start with a simple project!

I want to add a Resource Quick Access webpart on my Modern Sharepoint Page where users can quickly access SharePoint/OneDrive/Team files etc... I am using the MSGraphClient with UI Fabric

I'm getting this error

(property) BaseWebPart.properties: AlphaResources This property is the pointer to the custom property bag of the web part. @readonly No overload matches this call. The last overload gave the following error. Argument of type '{ description: any; context: WebPartContext; }' is not assignable to parameter of type 'Attributes'. Object literal may only specify known properties, and 'description' does not exist in type 'Attributes'.ts(2769) index.d.ts(238, 14): The last overload is declared here.

I really don't understand why, I haven't had this in previous test web parts

So I could really do with your help.

in the webpart.ts I have

import * as React from 'react';
import * as ReactDom from 'react-dom';

import { Version } from '@microsoft/sp-core-library';
import {
        IPropertyPaneConfiguration,
        PropertyPaneTextField,
        PropertyPaneLabel,
        PropertyPaneToggle,
        PropertyPaneDropdown,
        PropertyPaneCheckbox,
        PropertyPaneLink,
        PropertyPaneSlider
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';

import * as strings from 'AlphaResourcesWebPartStrings';
import AlphaResources  from './components/AlphaResources';
import IAlphaResourcesProps from './components/AlphaResources';


export default class AlphaResourcesWebPart extends BaseClientSideWebPart<IAlphaResourcesProps> {

public render(): void {
 const element: React.ReactElement<IAlphaResourcesProps> = React.createElement(
  AlphaResources,
  {
    description: this.properties.description, 
    context: this.context 
  }
);

ReactDom.render(element, this.domElement);
}

protected onDispose(): void {
     ReactDom.unmountComponentAtNode(this.domElement);
}

protected get dataVersion(): Version {
  return Version.parse('1.0');
}

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
  return {
    pages: [
      {
        header: {
          description: strings.PropertyPaneDescription
        },
       groups: [
         {
           groupName: strings.BasicGroupName,
           groupFields: [
             PropertyPaneTextField('description', {
               label: strings.DescriptionFieldLabel
             })
           ]
         }
       ]
     }
   ]
 };
}
}

in the AlphaResources.tsx (code based on example [https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist][1])

import * as React from 'react';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { MSGraphClient } from '@microsoft/sp-http';
import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Fabric } from 'office-ui-fabric-react/lib/Fabric';
import { Announced } from 'office-ui-fabric-react/lib/Announced';
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling';
import { Pivot, PivotItem } from 'office-ui-fabric-react/lib/Pivot';
import { Nav, INavLink } from 'office-ui-fabric-react/lib/Nav';
import { Link } from 'office-ui-fabric-react/lib/Link';

const classNames = mergeStyleSets({
    fileIconHeaderIcon: {
    padding: 0,
    fontSize: '16px'
},
fileIconCell: {
    textAlign: 'center',
    selectors: {
    '&:before': {
    content: '.',
    display: 'inline-block',
    verticalAlign: 'middle',
    height: '100%',
    width: '0px',
    visibility: 'hidden'
}
}
},
fileIconImg: {
  verticalAlign: 'middle',
  maxHeight: '16px',
  maxWidth: '16px'
},
  controlWrapper: {
    display: 'flex',
    flexWrap: 'wrap'
  },
  exampleToggle: {
    display: 'inline-block',
    marginBottom: '10px',
    marginRight: '30px'
  },
  selectionDetails: {
    marginBottom: '20px'
  }
});
const controlStyles = {
  root: {
    margin: '0 30px 20px 0',
    maxWidth: '300px'
  }
};

export interface IAlphaResourcesProps {
  description: string;
  context: WebPartContext;
}

export interface IAlphaResourcesState {
  columns: IColumn[];
  items: IDocument[];
  selectionDetails: string;
  isModalSelection: boolean;
  isCompactMode: boolean;
  announcedMessage?: string;
}

export interface IDocument {
  key: string;
  name: string;
  value: string;
  iconName: string;
  webUrl:string;
  fileType: string;
  modifiedBy: string;
  dateModified: string;
  dateModifiedValue: number;
  fileSize: string;
  fileSizeRaw: number;
  contentType:string;
  parentId:string;
  parentDriveId:string;
}


export default class AlphaResources extends React.Component<{}, IAlphaResourcesState> {
  private _selection: Selection;
  private _allItems: IDocument[];

  constructor(props: {}) {
    super(props);

    var default_dir_id = "dir-id";
    var default_drive_id = 'drive-id';

    this._allItems = _getDocuments();

    const columns: IColumn[] = [
      {
        key: 'column1',
        name: 'File Type',
        className: classNames.fileIconCell,
        iconClassName: classNames.fileIconHeaderIcon,
        ariaLabel: 'Column operations for File type, Press to sort on File type',
        iconName: 'Page',
        isIconOnly: true,
        fieldName: 'name',
        minWidth: 16,
        maxWidth: 16,
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <img src={item.iconName} className={classNames.fileIconImg} alt={item.fileType + ' file icon'} />;
        }
      },
      {
        key: 'column2',
        name: 'Name',
        fieldName: 'name',
        minWidth: 210,
        maxWidth: 350,
        isRowHeader: true,
        isResizable: true,
        isSorted: true,
        isSortedDescending: false,
        sortAscendingAriaLabel: 'Sorted A to Z',
        sortDescendingAriaLabel: 'Sorted Z to A',
        onColumnClick: this._onColumnClick,
        data: 'string',
        isPadded: true
      },
      {
        key: 'column3',
        name: 'Date Modified',
        fieldName: 'dateModifiedValue',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        onColumnClick: this._onColumnClick,
        data: 'number',
        onRender: (item: IDocument) => {
          return <span>{item.dateModified}</span>;
        },
        isPadded: true
      },
      {
        key: 'column4',
        name: 'Modified By',
        fieldName: 'modifiedBy',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        isCollapsible: true,
        data: 'string',
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <span>{item.modifiedBy}</span>;
        },
        isPadded: true
      },
      {
        key: 'column5',
        name: 'File Size',
        fieldName: 'fileSizeRaw',
        minWidth: 70,
        maxWidth: 90,
        isResizable: true,
        isCollapsible: true,
        data: 'number',
        onColumnClick: this._onColumnClick,
        onRender: (item: IDocument) => {
          return <span>{item.fileSize}</span>;
        }
      }
    ];

    this._selection = new Selection({
      onSelectionChanged: () => {
        this.setState({
          selectionDetails: this._getSelectionDetails()
        });
      }
    });

    this.state = {
      items: this._allItems,
      columns: columns,
      selectionDetails: this._getSelectionDetails(),
      isModalSelection: false,
      isCompactMode: false,
      announcedMessage: undefined,
    };
  }


  public render() : JSX.Element {
    const {columns, isCompactMode, items, selectionDetails, isModalSelection, announcedMessage } = this.state;

    return (
      <Fabric>
<Pivot onLinkClick={this.onLinkClick}>
         <PivotItem headerText="Alpha Resources">
         <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
        </PivotItem>
         <PivotItem headerText="My OneDrive">
           <Nav
             onLinkClick={this.onNavClick}
             styles={{
               root: {
                 width: 208,
                 height: 350,
                 boxSizing: 'border-box',
                 border: '1px solid #eee',
                 overflowY: 'auto'
               }
             }}
             groups={[
               {
                 links: [
                   {
                     name: 'My Files',
                     url:'#',
                     key: 'key1',
                   },
                   {
                     name: 'Shared',
                     url: '#',
                     key: 'key2',
                   },
                   {
                     name: 'Recent',
                     url: '#',
                     key: 'key3',
                   }
                 ]
               }
             ]}
           />
        <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
      </PivotItem>
      <PivotItem headerText="Team Resources">
      <div className={classNames.controlWrapper}>
          <TextField label="Filter by name:" onChange={this._onChangeText} styles={controlStyles} />
          <Announced message={`Number of items after filter applied: ${items.length}.`} />
        </div>
        <DetailsList
            items={items}
            compact={isCompactMode}
            columns={columns}
            selectionMode={SelectionMode.none}
            getKey={this._getKey}
            setKey="none"
            layoutMode={DetailsListLayoutMode.justified}
            isHeaderVisible={true}
            onItemInvoked={this._onItemInvoked}
          />
      </PivotItem>
      </Pivot>
      </Fabric>
    );
  }
  public onLinkClick(item: PivotItem): void {
    alert(item.props.headerText);
  }
  public onNavClick(ev: React.MouseEvent<HTMLElement>, item?: INavLink) {
    if (item) {
      alert(item.name);
    }
  }
  public componentDidUpdate(previousProps: any, previousState: IAlphaResourcesState) {
    if (previousState.isModalSelection !== this.state.isModalSelection && !this.state.isModalSelection) {
      this._selection.setAllSelected(false);
    }
  }

  private _getKey(item: any, index?: number): string {
    return item.key;
  }

  private _onChangeCompactMode = (ev: React.MouseEvent<HTMLElement>, checked: boolean): void => {
    this.setState({ isCompactMode: checked });
  };

  private _onChangeModalSelection = (ev: React.MouseEvent<HTMLElement>, checked: boolean): void => {
    this.setState({ isModalSelection: checked });
  };

  private _onChangeText = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text: string): void => {
    this.setState({
      items: text ? this._allItems.filter(i => i.name.toLowerCase().indexOf(text) > -1) : this._allItems
    });
  };

  private _onItemInvoked(item: any): void {
    alert(`Item invoked: ${item.name}`);
  }

  private _getSelectionDetails(): string {
    const selectionCount = this._selection.getSelectedCount();

    switch (selectionCount) {
      case 0:
        return 'No items selected';
      case 1:
        return '1 item selected: ' + (this._selection.getSelection()[0] as IDocument).name;
      default:
        return `${selectionCount} items selected`;
    }
  }

  private _onColumnClick = (ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
    const { columns, items } = this.state;
    const newColumns: IColumn[] = columns.slice();
    const currColumn: IColumn = newColumns.filter(currCol => column.key === currCol.key)[0];
    newColumns.forEach((newCol: IColumn) => {
      if (newCol === currColumn) {
        currColumn.isSortedDescending = !currColumn.isSortedDescending;
        currColumn.isSorted = true;
        this.setState({
          announcedMessage: `${currColumn.name} is sorted ${currColumn.isSortedDescending ? 'descending' : 'ascending'}`
        });
      } else {
        newCol.isSorted = false;
        newCol.isSortedDescending = true;
      }
    });
    const newItems = _copyAndSort(items, currColumn.fieldName!, currColumn.isSortedDescending);
    this.setState({
      columns: newColumns,
      items: newItems
    });
  };
}

function _copyAndSort<T>(items: T[], columnKey: string, isSortedDescending?: boolean): T[] {
  const key = columnKey as keyof T;
  return items.slice(0).sort((a: T, b: T) => ((isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1));
}

function _getDocuments(drive_type: any, dir_id: any, drive_id: any, action: any, item_id: any) {

  let graphUrl :string;
  const items: IDocument[] = [];

  if (drive_type == 'me' && !dir_id) {
    graphUrl = "/me/drive/root/children";
  }
  if (drive_type == 'shared' && !dir_id) {
    graphUrl = "/me/drive/sharedWithMe";
  }
  if (drive_type == 'recent' && !dir_id) {
    graphUrl = "/me/drive/recent";
  }
  if (drive_type == 'me' && dir_id) {
    graphUrl = "/me/" + dir_id + "/drive/root/children";
  }
  if (drive_type == 'shared' && dir_id && drive_id) {
    graphUrl = "/drives/" + drive_id + "/items/" + dir_id + "/children";
  }
  if (drive_type == 'recent' && dir_id) {
    graphUrl = "/drives/" + dir_id;
  }
  if (drive_type == 'groups' || drive_type == 'teams' && dir_id) {
    graphUrl = "/groups/" + dir_id + "/drive/root/children";
  }
//trigger if download required
  if(drive_type == 'me' && action=='download' && item_id){
    graphUrl="/me/drive/items/"+item_id+"/content";
  }

  //console.log(graphUrl)

    this.props.context.msGraphClientFactory
    .getClient()
    .then((client: MSGraphClient): void => {
      client
        .api(graphUrl)
        .version("v1.0")
        .get((error, response: any, rawResponse?: any) => {

          if (error) {
            console.error(error);
            return;
          }
          if (response) {
            //console.log(response);
            //var driveItems: Array<IDocument> = new Array<IDocument>();
            var contentType;
            response.value.map((item: any) => {
              if(item.folder){
                contentType = 'folder';
              }else{
                contentType = 'file';
              }
              items.push({
                key:item.id,
                name:item.name,
                value:item.name,
                iconName:'',
                fileType: '',
                webUrl:item.webUrl,
                fileSize: item.size,
                fileSizeRaw: item.size,
                dateModified: item.lastModifiedDateTime,
                modifiedBy: item.lastModifiedBy.user.displayName,
                dateModifiedValue: item.lastModifiedDateTime,
                contentType:contentType,
                parentId:item.parentReference.id,
                parentDriveId:item.parentReference.driveId,
              });
            });
            return items
            // Update the component state accordingly to the result  
            //console.log(items);
          }
        });
    });
}

1 Answer 1

1

In your AlphaRessourcesWebPart you trying to create en element of AlphaRessources and giving it properties. The Problem is, that your AlphaRessources Class is not accepting Properties other than {}.

Here's your code from AlphaResources.tsx:

export default class AlphaResources extends React.Component<{}, IAlphaResourcesState> {
      private _selection: Selection;
      private _allItems: IDocument[];

      constructor(props: {}) {
        super(props);
    ...}

...}

to give properties to your component it should be like the following:

export default class AlphaResources extends React.Component<IAlphaResourcesProps, IAlphaResourcesState> {
  private _selection: Selection;
  private _allItems: IDocument[];

  constructor(props: IAlphaResourcesProps) {
    super(props);
  ...}
...}
8
  • Thanks for this, I have tried it and it now error on element in webpart.ts : Type 'CElement<IAlphaResourcesProps, AlphaResources>' is not assignable to type 'ReactElement<AlphaResources, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)>) | (new (props: any) => Component<...>)>'. Types of property 'props' are incompatible. Type 'IAlphaResourcesProps' is missing the following properties from type 'AlphaResources': _selection, _allItems, render, onLinkClick, and 14 more.ts(2322)
    – AAT
    Jan 20, 2020 at 13:31
  • delete the typedefinition on your const element in your render method. React.createElement is returning a CElement Object
    – MP_89
    Jan 20, 2020 at 14:33
  • Really sorry, I don't follow.
    – AAT
    Jan 20, 2020 at 14:52
  • sorry I messed up something myself. The signature of your rendering method shouldn't be void. It shoul be public render(): React.ReactElement<IAlphaRessourcesProps>
    – MP_89
    Jan 20, 2020 at 15:05
  • Sorry no Joy, so what should the completed say: public render(): void { const element: React.ReactElement<IAlphaResourcesProps> = React.createElement( AlphaResources, { description: this.properties.description, context: this.context } ); ReactDom.render(element, this.domElement); }
    – AAT
    Jan 20, 2020 at 16:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.