diff --git a/src/common/utilities/GeneralHelper.ts b/src/common/utilities/GeneralHelper.ts index a5f9c7d6d..2433a37e8 100644 --- a/src/common/utilities/GeneralHelper.ts +++ b/src/common/utilities/GeneralHelper.ts @@ -360,3 +360,40 @@ export const toRelativeUrl = (absoluteUrl: string): string => { return absoluteUrl.replace(/^(?:\/\/|[^/]+)*\//, '/'); }; + +export function sortString(a: string, b: string, isDesc: boolean): number { + const aProp = (a || '').toLowerCase(); + const bProp = (b || '').toLowerCase(); + + if (aProp < bProp) { + return isDesc ? 1 : -1; + } + else if (aProp > bProp) { + return isDesc ? -1 : 1; + } + + return 0; +} + +export function sortDate(a: string | number | Date, b: string | number | Date, isDesc: boolean): number { + const aTime = dateToNumber(a); + const bTime = dateToNumber(b); + + return isDesc ? bTime - aTime : aTime - bTime; +} + +export function dateToNumber(date: string | number | Date): number { + if (typeof date === 'number') { + return date; + } + + let dateObj: Date; + if (typeof date === 'string') { + dateObj = new Date(date); + } + else { + dateObj = date; + } + + return dateObj.getTime(); +} diff --git a/src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx b/src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx index ebeb512fa..92dcba936 100644 --- a/src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx +++ b/src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx @@ -35,6 +35,7 @@ export class OneDriveFilesTab extends React.Component { //const dateModified = moment(item.modified).format(strings.DateFormat); return {item.modified}; @@ -108,7 +108,7 @@ export class FileBrowser extends React.Component - { - this.state.selectedView !== 'tiles' ? - ( + { + this.state.selectedView !== 'tiles' ? + ( { this._loadNextDataRequest(); return null; }} />) : - () - } + () + } @@ -378,31 +378,56 @@ export class FileBrowser extends React.Component { - const firstValue = a[column.fieldName || '']; - const secondValue = b[column.fieldName || '']; + const updatedColumns: IColumn[] = columns!.map(col => { + col.isSorted = col.key === column.key; - if (isSortedDescending) { - return firstValue > secondValue ? -1 : 1; - } else { - return firstValue > secondValue ? 1 : -1; + if (col.isSorted) { + col.isSortedDescending = isSortedDescending; } + + return col; }); - // Reset the items and columns to match the state. - this.setState({ - items: items, - columns: columns!.map(col => { - col.isSorted = col.key === column.key; + if (!this.state.nextPageQueryString) { // all items have been loaded to the client + // Sort the items. + items = items!.concat([]).sort((a, b) => { + if (a.isFolder && !b.isFolder) { + return 1; + } + else if (!a.isFolder && b.isFolder) { + return -1; + } + let firstValue = a[column.fieldName] || ''; + let secondValue = b[column.fieldName] || ''; - if (col.isSorted) { - col.isSortedDescending = isSortedDescending; + if (column.data === 'string') { + return sortString(firstValue, secondValue, isSortedDescending); + } + else if (column.data === 'date') { + return sortDate(firstValue, secondValue, isSortedDescending); + } + else if (column.data === 'number') { + firstValue = parseFloat(firstValue); + secondValue = parseFloat(secondValue); } - return col; - }) - }); + return isSortedDescending ? secondValue - firstValue : firstValue - secondValue; + }); + + // Reset the items and columns to match the state. + this.setState({ + items: items, + columns: updatedColumns + }); + } + else { + this.setState({ + items: [], + columns: updatedColumns + }, () => { + this._getListItems(false); + }); + } } /** @@ -454,20 +479,20 @@ export class FileBrowser extends React.Component { - // If a file is selected, open the library - if (item.isFolder) { - this._handleOpenFolder(item); - } else { - // Otherwise, remember it was selected - this._itemSelectionChanged(item); - } - } + // If a file is selected, open the library + if (item.isFolder) { + this._handleOpenFolder(item); + } else { + // Otherwise, remember it was selected + this._itemSelectionChanged(item); + } + } /** * Gets all files in a library with a matchihg path */ private async _getListItems(concatenateResults: boolean = false) { - const { libraryUrl, folderPath, accepts } = this.props; + const { libraryUrl, folderPath, accepts, fileBrowserService } = this.props; let { items, nextPageQueryString } = this.state; let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; @@ -480,8 +505,18 @@ export class FileBrowser extends React.Component c.isSorted)[0]; + if (sortByCol) { + sortField = fileBrowserService.getSPFieldNameForFileProperty(sortByCol.fieldName); + isDesc = !!sortByCol.isSortedDescending; + } + // Load files in the folder - filesQueryResult = await this.props.fileBrowserService.getListItems(libraryUrl, folderPath, accepts, nextPageQueryString); + filesQueryResult = await this.props.fileBrowserService.getListItems(libraryUrl, folderPath, accepts, nextPageQueryString, sortField, isDesc); } catch (error) { filesQueryResult.items = null; console.error(error.message); @@ -515,4 +550,8 @@ export class FileBrowser extends React.Component => { + public getListItems = async (listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string, sortBy?: string, isDesc?: boolean): Promise => { let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; try { let restApi = `${this.context.pageContext.web.absoluteUrl}/_api/web/GetList('${listUrl}')/RenderListDataAsStream`; @@ -36,7 +36,7 @@ export class FileBrowserService { folderPath = null; } - filesQueryResult = await this._getListDataAsStream(restApi, folderPath, acceptedFilesExtensions); + filesQueryResult = await this._getListDataAsStream(restApi, folderPath, acceptedFilesExtensions, sortBy, isDesc); } catch (error) { filesQueryResult.items = null; console.error(error.message); @@ -123,13 +123,45 @@ export class FileBrowserService { } } + /** + * Maps IFile property name to SharePoint item field name + * @param filePropertyName File Property + * @returns SharePoint Field Name + */ + public getSPFieldNameForFileProperty(filePropertyName: string): string { + let fieldName = ''; + switch (filePropertyName) { + case 'fileIcon': + fieldName = 'DocIcon'; + break; + case 'serverRelativeUrl': + fieldName = 'FileRef'; + break; + case 'modified': + case 'modifiedDate': + fieldName = 'Modified'; + break; + case 'fileSize': + fieldName = 'File_x0020_Size'; + break; + case 'fileType': + fieldName = 'File_x0020_Type'; + break; + case 'modifiedBy': + fieldName = 'Editor'; + break; + } + + return fieldName; + } + /** * Executes query to load files with possible extension filtering * @param restApi * @param folderPath * @param acceptedFilesExtensions */ - protected _getListDataAsStream = async (restApi: string, folderPath: string, acceptedFilesExtensions?: string[]): Promise => { + protected _getListDataAsStream = async (restApi: string, folderPath: string, acceptedFilesExtensions?: string[], sortBy?: string, isDesc?: boolean): Promise => { let filesQueryResult: FilesQueryResult = { items: [], nextHref: null }; try { const body = { @@ -137,7 +169,7 @@ export class FileBrowserService { AllowMultipleValueFilterForTaxonomyFields: true, // ContextInfo (1), ListData (2), ListSchema (4), ViewMetadata (1024), EnableMediaTAUrls (4096), ParentInfo (8192) RenderOptions: 1 | 2 | 4 | 1024 | 4096 | 8192, - ViewXml: this.getFilesCamlQueryViewXml(acceptedFilesExtensions) + ViewXml: this.getFilesCamlQueryViewXml(acceptedFilesExtensions, sortBy || 'FileLeafRef', !!isDesc) } }; if (folderPath) { @@ -195,7 +227,7 @@ export class FileBrowserService { /** * Generates Files CamlQuery ViewXml */ - protected getFilesCamlQueryViewXml = (accepts: string[]) => { + protected getFilesCamlQueryViewXml = (accepts: string[], sortBy: string, isDesc: boolean) => { const fileFilter: string = this.getFileTypeFilter(accepts); let queryCondition = fileFilter && fileFilter != "" ? ` @@ -217,8 +249,8 @@ export class FileBrowserService { - - ` : ``; + + ` : ``; // Add files types condiiton const viewXml = ` @@ -264,6 +296,7 @@ export class FileBrowserService { fileIcon: fileItem.DocIcon, serverRelativeUrl: fileItem.FileRef, modified: modified, + modifiedDate: new Date(fileItem.Modified), fileSize: fileItem.File_x0020_Size, fileType: fileItem.File_x0020_Type, modifiedBy: fileItem.Editor![0]!.title, diff --git a/src/services/FileBrowserService.types.ts b/src/services/FileBrowserService.types.ts index db146dd19..fbfdcddf2 100644 --- a/src/services/FileBrowserService.types.ts +++ b/src/services/FileBrowserService.types.ts @@ -6,6 +6,7 @@ export interface IFile { serverRelativeUrl: string; isFolder: boolean; modified: string; + modifiedDate: Date; modifiedBy?: string;