Skip to content

Commit 3cdd379

Browse files
authored
Merge pull request #711 from aaclage/Feature/V1ListViewDragDrop
New feature extension ListView > dragDropFiles V1
2 parents 00de01f + e34828c commit 3cdd379

File tree

6 files changed

+156
-6
lines changed

6 files changed

+156
-6
lines changed
16.9 KB
Loading

docs/documentation/docs/controls/ListView.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ This control renders a list view for the given set of items.
88

99
![ListView control with grouping](../assets/ListView-grouping.png)
1010

11+
**List view control with drag and drop applied**
12+
13+
![ListView control with grouping](../assets/ListView-DragDrop.png)
14+
1115
## How to use this control in your solutions
1216

1317
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency.
@@ -30,7 +34,9 @@ import { ListView, IViewField, SelectionMode, GroupOrder, IGrouping } from "@pnp
3034
showFilter={true}
3135
defaultFilter="John"
3236
filterPlaceHolder="Search..."
33-
groupByFields={groupByFields} />
37+
groupByFields={groupByFields}
38+
dragDropFiles={true}
39+
onDrop={this._getDropFiles} />
3440
```
3541
- The control provides full text filtering through all the columns. If you want to exectue filtering on the specified columns, you can use syntax : `<ColumndName>`:`<FilterValue>`. Use `':'` as a separator between column name and value. Control support both `'fieldName'` and `'name'` properties of IColumn interface.
3642

@@ -61,6 +67,16 @@ const groupByFields: IGrouping[] = [
6167
!!! note "Extend ListView with a ContextualMenu"
6268
To extend the `ListView` control with a [ContextualMenu](https://developer.microsoft.com/en-us/fabric#/components/contextualmenu) refer to [ListView.ContextualMenu](./ListView.ContextualMenu.md).
6369

70+
- With the `onDrop` handler you can define a method that returns files that where drag and drop by user in the list view:
71+
72+
```typescript
73+
private _getDropFiles = (files) => {
74+
for (var i = 0; i < files.length; i++) {
75+
console.log(files[i].name);
76+
}
77+
}
78+
```
79+
6480
## Implementation
6581

6682
The ListView control can be configured with the following properties:
@@ -78,6 +94,8 @@ The ListView control can be configured with the following properties:
7894
| filterPlaceHolder | string | no | Specify the placeholder for the filter text box. Default 'Search' |
7995
| showFilter | boolean | no | Specify if the filter text box should be rendered. |
8096
| defaultFilter | string | no | Specify the initial filter to be applied to the list. |
97+
| onDrop | file | no | Event handler returns files from drag and drop. |
98+
| stickyHeader | boolean | no | Specifies if the header of the `ListView`, including search box, is sticky |
8199

82100
The `IViewField` has the following implementation:
83101

src/controls/listView/IListView.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ export enum GroupOrder {
99
}
1010

1111
export interface IListViewProps {
12+
13+
/**
14+
* Specify if drag and drop option is selected.
15+
**/
16+
dragDropFiles?: boolean;
17+
/**
18+
* Handler to return the files from drag and drop.
19+
**/
20+
onDrop?: any;
1221
/**
1322
* Specify the name of the file URL path which will be used to show the file icon.
1423
*/
@@ -73,6 +82,8 @@ export interface IListViewState {
7382
columns?: IColumn[];
7483

7584
groups?: IGroup[];
85+
86+
dragStatus?: boolean;
7687
}
7788

7889
export interface IGrouping {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.DragDropArea {
2+
position: relative;
3+
min-height: 150px;
4+
}
5+
6+
.DragDropAreaBorder {
7+
border: dashed grey 1px;
8+
background-color: rgba(255,255,255,.6);
9+
position: absolute;
10+
top: 0;
11+
bottom: 0;
12+
left: 0;
13+
right: 0;
14+
z-index: 1;
15+
}
16+
17+
.DragDropAreaZone {
18+
position: absolute;
19+
top: 35%;
20+
right: 0;
21+
left: 0;
22+
text-align: center;
23+
color: grey;
24+
font-size: 34px;
25+
}

src/controls/listView/ListView.tsx

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import styles from './ListView.DragDrop.module.scss';
23
import { DetailsList, DetailsListLayoutMode, Selection, SelectionMode, IGroup } from 'office-ui-fabric-react/lib/DetailsList';
34
import { IListViewProps, IListViewState, IViewField, IGrouping, GroupOrder } from './IListView';
45
import { IColumn, IGroupRenderProps } from 'office-ui-fabric-react/lib/components/DetailsList';
@@ -10,6 +11,7 @@ import * as telemetry from '../../common/telemetry';
1011
import filter = require('lodash/filter');
1112
import { SearchBox } from 'office-ui-fabric-react/lib/SearchBox';
1213
import { Guid } from '@microsoft/sp-core-library';
14+
import { Icon } from 'office-ui-fabric-react/lib/Icon';
1315

1416
/**
1517
* File type icon component
@@ -19,6 +21,11 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
1921
private originalItems: any[];
2022
private originalGroups: IGroup[];
2123
private originalColumns: IColumn[];
24+
private dragCounter = 0;
25+
private dropArea = null;
26+
private dropRef = element => {
27+
this.dropArea = element;
28+
};
2229

2330
constructor(props: IListViewProps) {
2431
super(props);
@@ -34,7 +41,8 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
3441
// Initialize state
3542
this.state = {
3643
items: [],
37-
filterValue: this.props.defaultFilter
44+
filterValue: this.props.defaultFilter,
45+
dragStatus: false
3846
};
3947

4048
if (this.props.selection) {
@@ -53,6 +61,17 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
5361
this._processProperties();
5462
}
5563

64+
public componentWillUnmount(): void {
65+
const { dragDropFiles } = this.props;
66+
if (dragDropFiles) {
67+
let divDropArea = this.dropArea;
68+
divDropArea.removeEventListener('dragenter', this.handleonDragEnter);
69+
divDropArea.removeEventListener('dragleave', this.handleonDragLeave);
70+
divDropArea.removeEventListener('dragover', this.handleonDragOver);
71+
divDropArea.removeEventListener('drop', this.handleonDrop);
72+
}
73+
}
74+
5675
/**
5776
* Lifecycle hook when component did update after state or property changes
5877
* @param prevProps
@@ -174,7 +193,7 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
174193
* Process all the component properties
175194
*/
176195
private _processProperties() {
177-
const { items, iconFieldName, viewFields, groupByFields, showFilter } = this.props;
196+
const { dragDropFiles, items, iconFieldName, viewFields, groupByFields, showFilter } = this.props;
178197

179198
let tempState: IListViewState = cloneDeep(this.state);
180199
let columns: IColumn[] = null;
@@ -225,6 +244,15 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
225244
// Update the current component state with the new values
226245
this.setState(tempState);
227246
}
247+
248+
// Add EventListeners for drag zone area
249+
if (dragDropFiles) {
250+
let divDropArea = this.dropArea;
251+
divDropArea.addEventListener('dragenter', this.handleonDragEnter);
252+
divDropArea.addEventListener('dragleave', this.handleonDragLeave);
253+
divDropArea.addEventListener('dragover', this.handleonDragOver);
254+
divDropArea.addEventListener('drop', this.handleonDrop);
255+
}
228256
}
229257

230258
/**
@@ -490,14 +518,61 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
490518
return result;
491519
}
492520

521+
/**
522+
* Stop listeners from onDragOver event.
523+
* @param e
524+
*/
525+
private handleonDragOver = (e) => {
526+
e.preventDefault();
527+
e.stopPropagation();
528+
}
529+
/**
530+
* Stop listeners from onDragEnter event, enable drag and drop view.
531+
* @param e
532+
*/
533+
private handleonDragEnter = (e) => {
534+
e.preventDefault();
535+
e.stopPropagation();
536+
this.dragCounter++;
537+
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
538+
this.setState({ dragStatus: true });
539+
}
540+
}
541+
/**
542+
* Stop listeners from ondragenter event, disable drag and drop view.
543+
* @param e
544+
*/
545+
private handleonDragLeave = (e) => {
546+
e.preventDefault();
547+
e.stopPropagation();
548+
this.dragCounter--;
549+
if (this.dragCounter === 0) {
550+
this.setState({ dragStatus: false });
551+
}
552+
}
553+
/**
554+
* Stop listeners from onDrop event and load files to property onDrop.
555+
* @param e
556+
*/
557+
private handleonDrop = (e) => {
558+
e.preventDefault();
559+
e.stopPropagation();
560+
this.setState({ dragStatus: false });
561+
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
562+
this.props.onDrop(e.dataTransfer.files);
563+
e.dataTransfer.clearData();
564+
this.dragCounter = 0;
565+
}
566+
}
567+
493568
/**
494569
* Default React component render method
495570
*/
496571
public render(): React.ReactElement<IListViewProps> {
497572
let groupProps: IGroupRenderProps = {};
498573

499-
let { showFilter, filterPlaceHolder } = this.props;
500-
let { filterValue, items } = this.state;
574+
let { showFilter, filterPlaceHolder, dragDropFiles } = this.props;
575+
let { filterValue, items, dragStatus } = this.state;
501576

502577
// Check if selection mode is single selection,
503578
// if that is the case, disable the selection on grouping headers
@@ -511,7 +586,16 @@ export class ListView extends React.Component<IListViewProps, IListViewState> {
511586
}
512587

513588
return (
514-
<div>
589+
<div className={styles.DragDropArea}
590+
ref={this.dropRef}>
591+
{(dragStatus && dragDropFiles) &&
592+
<div className={styles.DragDropAreaBorder}>
593+
<div className={styles.DragDropAreaZone}>
594+
<Icon iconName="Download" className="ms-IconExample" />
595+
<div>{strings.UploadFileHeader}</div>
596+
</div>
597+
</div>
598+
}
515599
{
516600
showFilter && <SearchBox placeholder={filterPlaceHolder || strings.ListViewFilterLabel} onSearch={this._updateFilterValue} onChange={this._updateFilterValue} value={filterValue} />
517601
}

src/webparts/controlsTest/components/ControlsTest.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,16 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
361361
console.log('Items:', items);
362362
}
363363

364+
/**
365+
* Method that retrieves files from drag and drop
366+
* @param files
367+
*/
368+
private _getDropFiles = (files) => {
369+
for (var i = 0; i < files.length; i++) {
370+
console.log(files[i].name);
371+
}
372+
}
373+
364374
/**
365375
*
366376
*Method that retrieves the selected terms from the taxonomy picker and sets state
@@ -960,6 +970,8 @@ export default class ControlsTest extends React.Component<IControlsTestProps, IC
960970
selectionMode={SelectionMode.single}
961971
selection={this._getSelection}
962972
showFilter={true}
973+
dragDropFiles={true}
974+
onDrop={this._getDropFiles}
963975
// defaultFilter="Team"
964976
/>
965977

0 commit comments

Comments
 (0)