Skip to content

Commit 70fb2ab

Browse files
committed
fix of #1011
1 parent f3b11dc commit 70fb2ab

File tree

5 files changed

+165
-54
lines changed

5 files changed

+165
-54
lines changed

src/common/utilities/GeneralHelper.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,40 @@ export const toRelativeUrl = (absoluteUrl: string): string => {
360360

361361
return absoluteUrl.replace(/^(?:\/\/|[^/]+)*\//, '/');
362362
};
363+
364+
export function sortString(a: string, b: string, isDesc: boolean): number {
365+
const aProp = (a || '').toLowerCase();
366+
const bProp = (b || '').toLowerCase();
367+
368+
if (aProp < bProp) {
369+
return isDesc ? 1 : -1;
370+
}
371+
else if (aProp > bProp) {
372+
return isDesc ? -1 : 1;
373+
}
374+
375+
return 0;
376+
}
377+
378+
export function sortDate(a: string | number | Date, b: string | number | Date, isDesc: boolean): number {
379+
const aTime = dateToNumber(a);
380+
const bTime = dateToNumber(b);
381+
382+
return isDesc ? bTime - aTime : aTime - bTime;
383+
}
384+
385+
export function dateToNumber(date: string | number | Date): number {
386+
if (typeof date === 'number') {
387+
return date;
388+
}
389+
390+
let dateObj: Date;
391+
if (typeof date === 'string') {
392+
dateObj = new Date(date);
393+
}
394+
else {
395+
dateObj = date;
396+
}
397+
398+
return dateObj.getTime();
399+
}

src/controls/filePicker/OneDriveFilesTab/OneDriveFilesTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export class OneDriveFilesTab extends React.Component<IOneDriveFilesTabProps, IO
3535
const oneDriveFolderData: IFile = {
3636
isFolder: true,
3737
modified: null,
38+
modifiedDate: null,
3839
absoluteUrl: libraryAbsolutePath,
3940
name: libraryTitle,
4041
fileIcon: "",

src/controls/filePicker/controls/FileBrowser/FileBrowser.tsx

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
22
import { IFile, FilesQueryResult } from '../../../../services/FileBrowserService.types';
3-
import { GeneralHelper } from '../../../../common/utilities/GeneralHelper';
3+
import { GeneralHelper, sortDate, sortString } from '../../../../common/utilities/GeneralHelper';
44
import { LoadingState } from './IFileBrowserState';
55
import { TilesList } from '../TilesList/TilesList';
66
import { IFilePickerResult } from '../../FilePicker.types';
@@ -59,7 +59,7 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
5959
{
6060
key: 'column2',
6161
name: strings.NameField,
62-
fieldName: 'fileLeafRef',
62+
fieldName: 'name',
6363
minWidth: 210,
6464
isRowHeader: true,
6565
isResizable: true,
@@ -81,11 +81,11 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
8181
{
8282
key: 'column3',
8383
name: strings.ModifiedField,
84-
fieldName: 'dateModifiedValue',
84+
fieldName: 'modified',
8585
minWidth: 120,
8686
isResizable: true,
8787
onColumnClick: this._onColumnClick,
88-
data: 'number',
88+
data: 'date',
8989
onRender: (item: IFile) => {
9090
//const dateModified = moment(item.modified).format(strings.DateFormat);
9191
return <span>{item.modified}</span>;
@@ -108,7 +108,7 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
108108
{
109109
key: 'column5',
110110
name: strings.FileSizeField,
111-
fieldName: 'fileSizeRaw',
111+
fieldName: 'fileSize',
112112
minWidth: 70,
113113
maxWidth: 90,
114114
isResizable: true,
@@ -169,9 +169,9 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
169169
<div className={styles.scrollablePaneWrapper}>
170170
<ScrollablePane>
171171

172-
{
173-
this.state.selectedView !== 'tiles' ?
174-
(
172+
{
173+
this.state.selectedView !== 'tiles' ?
174+
(
175175
<DetailsList
176176
items={this.state.items}
177177
compact={this.state.selectedView === 'compact'}
@@ -187,17 +187,17 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
187187
onRenderRow={this._onRenderRow}
188188
onRenderMissingItem={() => { this._loadNextDataRequest(); return null; }}
189189
/>) :
190-
(<TilesList
191-
fileBrowserService={this.props.fileBrowserService}
192-
filePickerResult={this.state.filePickerResult}
193-
selection={this._selection}
194-
items={this.state.items}
195-
196-
onFolderOpen={this._handleOpenFolder}
197-
onFileSelected={this._itemSelectionChanged}
198-
onNextPageDataRequest={this._loadNextDataRequest}
199-
/>)
200-
}
190+
(<TilesList
191+
fileBrowserService={this.props.fileBrowserService}
192+
filePickerResult={this.state.filePickerResult}
193+
selection={this._selection}
194+
items={this.state.items}
195+
196+
onFolderOpen={this._handleOpenFolder}
197+
onFileSelected={this._itemSelectionChanged}
198+
onNextPageDataRequest={this._loadNextDataRequest}
199+
/>)
200+
}
201201
</ScrollablePane>
202202
</div>
203203
</div>
@@ -378,31 +378,56 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
378378
isSortedDescending = !isSortedDescending;
379379
}
380380

381-
// Sort the items.
382-
items = items!.concat([]).sort((a, b) => {
383-
const firstValue = a[column.fieldName || ''];
384-
const secondValue = b[column.fieldName || ''];
381+
const updatedColumns: IColumn[] = columns!.map(col => {
382+
col.isSorted = col.key === column.key;
385383

386-
if (isSortedDescending) {
387-
return firstValue > secondValue ? -1 : 1;
388-
} else {
389-
return firstValue > secondValue ? 1 : -1;
384+
if (col.isSorted) {
385+
col.isSortedDescending = isSortedDescending;
390386
}
387+
388+
return col;
391389
});
392390

393-
// Reset the items and columns to match the state.
394-
this.setState({
395-
items: items,
396-
columns: columns!.map(col => {
397-
col.isSorted = col.key === column.key;
391+
if (!this.state.nextPageQueryString) { // all items have been loaded to the client
392+
// Sort the items.
393+
items = items!.concat([]).sort((a, b) => {
394+
if (a.isFolder && !b.isFolder) {
395+
return 1;
396+
}
397+
else if (!a.isFolder && b.isFolder) {
398+
return -1;
399+
}
400+
let firstValue = a[column.fieldName] || '';
401+
let secondValue = b[column.fieldName] || '';
398402

399-
if (col.isSorted) {
400-
col.isSortedDescending = isSortedDescending;
403+
if (column.data === 'string') {
404+
return sortString(firstValue, secondValue, isSortedDescending);
405+
}
406+
else if (column.data === 'date') {
407+
return sortDate(firstValue, secondValue, isSortedDescending);
408+
}
409+
else if (column.data === 'number') {
410+
firstValue = parseFloat(firstValue);
411+
secondValue = parseFloat(secondValue);
401412
}
402413

403-
return col;
404-
})
405-
});
414+
return isSortedDescending ? secondValue - firstValue : firstValue - secondValue;
415+
});
416+
417+
// Reset the items and columns to match the state.
418+
this.setState({
419+
items: items,
420+
columns: updatedColumns
421+
});
422+
}
423+
else {
424+
this.setState({
425+
items: [],
426+
columns: updatedColumns
427+
}, () => {
428+
this._getListItems(false);
429+
});
430+
}
406431
}
407432

408433
/**
@@ -454,20 +479,20 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
454479
* Handles item click.
455480
*/
456481
private _handleItemInvoked = (item: IFile) => {
457-
// If a file is selected, open the library
458-
if (item.isFolder) {
459-
this._handleOpenFolder(item);
460-
} else {
461-
// Otherwise, remember it was selected
462-
this._itemSelectionChanged(item);
463-
}
464-
}
482+
// If a file is selected, open the library
483+
if (item.isFolder) {
484+
this._handleOpenFolder(item);
485+
} else {
486+
// Otherwise, remember it was selected
487+
this._itemSelectionChanged(item);
488+
}
489+
}
465490

466491
/**
467492
* Gets all files in a library with a matchihg path
468493
*/
469494
private async _getListItems(concatenateResults: boolean = false) {
470-
const { libraryUrl, folderPath, accepts } = this.props;
495+
const { libraryUrl, folderPath, accepts, fileBrowserService } = this.props;
471496
let { items, nextPageQueryString } = this.state;
472497

473498
let filesQueryResult: FilesQueryResult = { items: [], nextHref: null };
@@ -480,8 +505,18 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
480505
loadingState,
481506
nextPageQueryString
482507
});
508+
509+
let sortField: string | undefined = undefined;
510+
let isDesc: boolean | undefined = undefined;
511+
512+
const sortByCol = this.state.columns!.filter(c => c.isSorted)[0];
513+
if (sortByCol) {
514+
sortField = fileBrowserService.getSPFieldNameForFileProperty(sortByCol.fieldName);
515+
isDesc = !!sortByCol.isSortedDescending;
516+
}
517+
483518
// Load files in the folder
484-
filesQueryResult = await this.props.fileBrowserService.getListItems(libraryUrl, folderPath, accepts, nextPageQueryString);
519+
filesQueryResult = await this.props.fileBrowserService.getListItems(libraryUrl, folderPath, accepts, nextPageQueryString, sortField, isDesc);
485520
} catch (error) {
486521
filesQueryResult.items = null;
487522
console.error(error.message);
@@ -515,4 +550,8 @@ export class FileBrowser extends React.Component<IFileBrowserProps, IFileBrowser
515550
});
516551
}
517552
}
553+
554+
private _onClientSort(column: IColumn): void {
555+
556+
}
518557
}

src/services/FileBrowserService.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class FileBrowserService {
2424
* @param folderPath
2525
* @param acceptedFilesExtensions
2626
*/
27-
public getListItems = async (listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string): Promise<FilesQueryResult> => {
27+
public getListItems = async (listUrl: string, folderPath: string, acceptedFilesExtensions?: string[], nextPageQueryStringParams?: string, sortBy?: string, isDesc?: boolean): Promise<FilesQueryResult> => {
2828
let filesQueryResult: FilesQueryResult = { items: [], nextHref: null };
2929
try {
3030
let restApi = `${this.context.pageContext.web.absoluteUrl}/_api/web/GetList('${listUrl}')/RenderListDataAsStream`;
@@ -36,7 +36,7 @@ export class FileBrowserService {
3636
folderPath = null;
3737
}
3838

39-
filesQueryResult = await this._getListDataAsStream(restApi, folderPath, acceptedFilesExtensions);
39+
filesQueryResult = await this._getListDataAsStream(restApi, folderPath, acceptedFilesExtensions, sortBy, isDesc);
4040
} catch (error) {
4141
filesQueryResult.items = null;
4242
console.error(error.message);
@@ -123,21 +123,53 @@ export class FileBrowserService {
123123
}
124124
}
125125

126+
/**
127+
* Maps IFile property name to SharePoint item field name
128+
* @param filePropertyName File Property
129+
* @returns SharePoint Field Name
130+
*/
131+
public getSPFieldNameForFileProperty(filePropertyName: string): string {
132+
let fieldName = '';
133+
switch (filePropertyName) {
134+
case 'fileIcon':
135+
fieldName = 'DocIcon';
136+
break;
137+
case 'serverRelativeUrl':
138+
fieldName = 'FileRef';
139+
break;
140+
case 'modified':
141+
case 'modifiedDate':
142+
fieldName = 'Modified';
143+
break;
144+
case 'fileSize':
145+
fieldName = 'File_x0020_Size';
146+
break;
147+
case 'fileType':
148+
fieldName = 'File_x0020_Type';
149+
break;
150+
case 'modifiedBy':
151+
fieldName = 'Editor';
152+
break;
153+
}
154+
155+
return fieldName;
156+
}
157+
126158
/**
127159
* Executes query to load files with possible extension filtering
128160
* @param restApi
129161
* @param folderPath
130162
* @param acceptedFilesExtensions
131163
*/
132-
protected _getListDataAsStream = async (restApi: string, folderPath: string, acceptedFilesExtensions?: string[]): Promise<FilesQueryResult> => {
164+
protected _getListDataAsStream = async (restApi: string, folderPath: string, acceptedFilesExtensions?: string[], sortBy?: string, isDesc?: boolean): Promise<FilesQueryResult> => {
133165
let filesQueryResult: FilesQueryResult = { items: [], nextHref: null };
134166
try {
135167
const body = {
136168
parameters: {
137169
AllowMultipleValueFilterForTaxonomyFields: true,
138170
// ContextInfo (1), ListData (2), ListSchema (4), ViewMetadata (1024), EnableMediaTAUrls (4096), ParentInfo (8192)
139171
RenderOptions: 1 | 2 | 4 | 1024 | 4096 | 8192,
140-
ViewXml: this.getFilesCamlQueryViewXml(acceptedFilesExtensions)
172+
ViewXml: this.getFilesCamlQueryViewXml(acceptedFilesExtensions, sortBy || 'FileLeafRef', !!isDesc)
141173
}
142174
};
143175
if (folderPath) {
@@ -195,7 +227,7 @@ export class FileBrowserService {
195227
/**
196228
* Generates Files CamlQuery ViewXml
197229
*/
198-
protected getFilesCamlQueryViewXml = (accepts: string[]) => {
230+
protected getFilesCamlQueryViewXml = (accepts: string[], sortBy: string, isDesc: boolean) => {
199231
const fileFilter: string = this.getFileTypeFilter(accepts);
200232
let queryCondition = fileFilter && fileFilter != "" ?
201233
`<Query>
@@ -217,8 +249,8 @@ export class FileBrowserService {
217249
</In>
218250
</Or>
219251
</Where>
220-
<OrderBy><FieldRef Name="FileLeafRef" /></OrderBy>
221-
</Query>` : `<Query><OrderBy><FieldRef Name="FileLeafRef" /></OrderBy></Query>`;
252+
<OrderBy><FieldRef Name="${sortBy}" Ascending="${isDesc ? 'False' : 'True'}" /></OrderBy>
253+
</Query>` : `<Query><OrderBy><FieldRef Name="${sortBy}" Ascending="${isDesc ? 'False' : 'True'}" /></OrderBy></Query>`;
222254

223255
// Add files types condiiton
224256
const viewXml = `<View>
@@ -264,6 +296,7 @@ export class FileBrowserService {
264296
fileIcon: fileItem.DocIcon,
265297
serverRelativeUrl: fileItem.FileRef,
266298
modified: modified,
299+
modifiedDate: new Date(fileItem.Modified),
267300
fileSize: fileItem.File_x0020_Size,
268301
fileType: fileItem.File_x0020_Type,
269302
modifiedBy: fileItem.Editor![0]!.title,

src/services/FileBrowserService.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface IFile {
66
serverRelativeUrl: string;
77
isFolder: boolean;
88
modified: string;
9+
modifiedDate: Date;
910
modifiedBy?: string;
1011

1112

0 commit comments

Comments
 (0)