Add sample that shows documents webparts with DetailsList (+ filtering, sorting ) (#334)
* Add new sample: react-documents. Initial commit * Fix sample description
This commit is contained in:
parent
6c3b2c74ce
commit
6680c156c5
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.3.2",
|
||||
"libraryName": "react-documents",
|
||||
"libraryId": "aaecff2e-2087-4ade-bcf8-e72f89a1d933",
|
||||
"environment": "spo"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
# Documents Web Part
|
||||
|
||||
## Summary
|
||||
This sample shows how to build web parts that display documents in accordance with the SharePoint Online modern experience. The code uses Office UI Fabric components on the top of SharePoint framework. The web parts implement filtering and sorting. Two data source approaches are demonstrated: items retrieved from the search index and real-time query to a document library.
|
||||
|
||||
![Demo](./assets/Preview.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
![drop](https://img.shields.io/badge/version-GA-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Office 365 subscription with SharePoint Online.
|
||||
- SharePoint Framework [development environment](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment) already set up.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-documents|Dimcho Tsanov
|
||||
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|October 13, 2017|Initial release
|
||||
|
||||
## Disclaimer
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
- Clone this repository
|
||||
- in the command line run:
|
||||
- `npm install`
|
||||
- `gulp serve`
|
||||
|
||||
## Features
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
-Using React for building SharePoint Framework client-side web parts.
|
||||
-Using Office UI Fabric React styles for building user experience consistent with SharePoint and Office.
|
||||
-Using the SharePoint rest API for querying document library's files.
|
||||
-Using the SharePoint rest API for retrieving documents from the search index.
|
||||
-Passing web part properties to React components.
|
||||
-Reusing single React component between two web parts.
|
Binary file not shown.
After Width: | Height: | Size: 757 KiB |
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"search-documents-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/searchDocuments/SearchDocumentsWebPart.js",
|
||||
"manifest": "./src/webparts/searchDocuments/SearchDocumentsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"library-documents-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/libraryDocuments/LibraryDocumentsWebPart.js",
|
||||
"manifest": "./src/webparts/libraryDocuments/LibraryDocumentsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"SearchDocumentsWebPartStrings": "lib/webparts/searchDocuments/loc/{locale}.js",
|
||||
"LibraryDocumentsWebPartStrings": "lib/webparts/libraryDocuments/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-documents",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-documents-client-side-solution",
|
||||
"id": "aaecff2e-2087-4ade-bcf8-e72f89a1d933",
|
||||
"version": "1.0.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-documents.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
|
||||
// Display errors as warnings
|
||||
"displayAsWarning": true,
|
||||
// The TSLint task may have been configured with several custom lint rules
|
||||
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
|
||||
// project). If true, this flag will deactivate any of these rules.
|
||||
"removeExistingRules": true,
|
||||
// When true, the TSLint task is configured with some default TSLint "rules.":
|
||||
"useDefaultConfigAsBase": false,
|
||||
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
|
||||
// which are active, other than the list of rules below.
|
||||
"lintConfig": {
|
||||
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-case": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"valid-typeof": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
//NOTE: the cdnBasePath works with relative path; this is useful when you move the pakedge between different environments (dev, staging, prod)
|
||||
"cdnBasePath": "/sites/dev03/Style Library/CDN"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "react-documents",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "15.4.2",
|
||||
"react-dom": "15.4.2",
|
||||
"@types/react": "15.0.38",
|
||||
"@types/react-dom": "0.14.18",
|
||||
"@types/react-addons-shallow-compare": "0.14.17",
|
||||
"@types/react-addons-update": "0.14.14",
|
||||
"@types/react-addons-test-utils": "0.14.15",
|
||||
"@microsoft/sp-core-library": "~1.3.0",
|
||||
"@microsoft/sp-webpart-base": "~1.3.0",
|
||||
"@microsoft/sp-lodash-subset": "~1.3.0",
|
||||
"@types/webpack-env": ">=1.12.1 <1.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~1.3.0",
|
||||
"@microsoft/sp-module-interfaces": "~1.3.0",
|
||||
"@microsoft/sp-webpart-workbench": "~1.3.0",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": ">=3.4.34 <3.6.0",
|
||||
"@types/mocha": ">=2.2.33 <2.6.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
export interface IDocument {
|
||||
|
||||
Id?: number;
|
||||
Name?: string;
|
||||
FileRef?: string;
|
||||
Modified?: any;
|
||||
ModifiedBy?: any;
|
||||
FileIcon?: string;
|
||||
VersionString?: string;
|
||||
ContentType?: string;
|
||||
ParentWebUrl?: string;
|
||||
UniqueId?: string;
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
import { IDocument } from './IObjects';
|
||||
import { IContextualMenuItem, IColumn } from 'office-ui-fabric-react';
|
||||
|
||||
|
||||
export class Utils {
|
||||
|
||||
/**
|
||||
* Returns the site relative url from an absolute url
|
||||
*/
|
||||
public GetRelativePathFromAbsolute(absoluteUrl) {
|
||||
|
||||
var serverRelativeUrl =
|
||||
absoluteUrl.toLowerCase().replace(window.location.protocol.toLowerCase() + "//" + window.location.host.toLowerCase(), "");
|
||||
return serverRelativeUrl;
|
||||
}
|
||||
/**
|
||||
* Returns the site relative url from an absolute url
|
||||
*/
|
||||
public GetFilterValues(column: IColumn, arrayObjects: any[], onFilterClickCallback: (ev?: React.MouseEvent<HTMLElement>, item?: IContextualMenuItem) => void): IContextualMenuItem[] {
|
||||
|
||||
let filters: IContextualMenuItem[] = [];
|
||||
for (let i = 0; i < arrayObjects.length; i++) {
|
||||
let item = arrayObjects[i];
|
||||
let value: string = item[column.key];
|
||||
if (item[column.key]) {
|
||||
//in case we have specific column, we can add more complex logic
|
||||
if (column.data == "Taxonomy") {
|
||||
let columnValue: string = item[column.key];
|
||||
let valuesAsStrings: string[] = columnValue.split(";");
|
||||
valuesAsStrings.map((termValue) => {
|
||||
termValue = termValue.trim();
|
||||
if (termValue && !this._IsValuePresented(filters, termValue)) {
|
||||
filters.push(
|
||||
{
|
||||
key: termValue,
|
||||
name: termValue,
|
||||
data: column.key,
|
||||
onClick: onFilterClickCallback,
|
||||
isChecked: i == 0 ? true : false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (!this._IsValuePresented(filters, value)) {
|
||||
filters.push(
|
||||
{
|
||||
key: value,
|
||||
name: value,
|
||||
data: column.key,
|
||||
onClick: onFilterClickCallback,
|
||||
isChecked: i == 0 ? true : false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
/**
|
||||
* Returns sorting menu
|
||||
*/
|
||||
public GetSortingMenuItems(column: IColumn, onSortColumn: (column: IColumn, isSortedDescending: boolean) => void): IContextualMenuItem[] {
|
||||
let menuItems = [];
|
||||
if (column.data == Number) {
|
||||
menuItems.push(
|
||||
{
|
||||
key: 'smallToLarger',
|
||||
name: 'Smaller to larger',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && !column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, false)
|
||||
},
|
||||
{
|
||||
key: 'largerToSmall',
|
||||
name: 'Larger to smaller',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, true)
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (column.data == Date) {
|
||||
menuItems.push(
|
||||
{
|
||||
key: 'oldToNew',
|
||||
name: 'Older to newer',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && !column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, false)
|
||||
},
|
||||
{
|
||||
key: 'newToOld',
|
||||
name: 'Newer to Older',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, true)
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
//(column.data == String)
|
||||
// NOTE: in case of 'complex columns like Taxonomy, you need to add more logic'
|
||||
{
|
||||
menuItems.push(
|
||||
{
|
||||
key: 'aToZ',
|
||||
name: 'A to Z',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && !column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, false)
|
||||
},
|
||||
{
|
||||
key: 'zToA',
|
||||
name: 'Z to A',
|
||||
canCheck: true,
|
||||
checked: column.isSorted && column.isSortedDescending,
|
||||
onClick: () => onSortColumn(column, true)
|
||||
}
|
||||
);
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
/**
|
||||
* Returns image url for the given filename.
|
||||
* The urls points to https://spoprod-a.akamaihd.net..... !!!
|
||||
*/
|
||||
public GetImgUrl(fileName: string): string {
|
||||
|
||||
let fileNameItems = fileName.split('.');
|
||||
let fileExtenstion = fileNameItems[fileNameItems.length - 1];
|
||||
|
||||
return this.GetImgUrlByFileExtension(fileExtenstion);
|
||||
}
|
||||
/**
|
||||
* Returns image url for the given extension.
|
||||
* The urls points to https://spoprod-a.akamaihd.net..... !!!
|
||||
*/
|
||||
public GetImgUrlByFileExtension(extension: string): string {
|
||||
// cuurently in SPFx with React I didn't find different way of getting the image
|
||||
// feel free to improve this
|
||||
let imgRoot: string = "https://spoprod-a.akamaihd.net/files/odsp-next-prod_ship-2017-04-21-sts_20170503.001/odsp-media/images/filetypes/16/";
|
||||
let imgType = "genericfile.png";
|
||||
imgType = extension + ".png";
|
||||
|
||||
switch (extension) {
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "jfif":
|
||||
case "gif":
|
||||
case "png":
|
||||
imgType = "photo.png";
|
||||
break;
|
||||
case "folder":
|
||||
imgType = "folder.svg";
|
||||
break;
|
||||
|
||||
}
|
||||
return imgRoot + imgType;
|
||||
}
|
||||
/**
|
||||
* Returns formated date string
|
||||
*/
|
||||
public GetFormatedDate(dateValue: Date): string {
|
||||
if (dateValue) {
|
||||
let date: string = dateValue.toLocaleString();
|
||||
if (date.indexOf(',') > -1) {
|
||||
date = date.split(',')[0];
|
||||
}
|
||||
return date;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* Returns formated date string
|
||||
*/
|
||||
public GetFormatedDateString(dateString: string): string {
|
||||
if (dateString) {
|
||||
let convertedDate: Date = new Date(dateString);
|
||||
let date: string = convertedDate.toLocaleString();
|
||||
if (date.indexOf(',') > -1) {
|
||||
date = date.split(',')[0];
|
||||
}
|
||||
return date;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* Returns formated date string
|
||||
*/
|
||||
public GetDateOnly(dateString: string): string {
|
||||
let shortDate = "";
|
||||
if (dateString) {
|
||||
let dateItems = dateString.split(" ");
|
||||
if (dateItems.length > 1) {
|
||||
shortDate = dateItems[0];
|
||||
}
|
||||
}
|
||||
let convertedDate: Date = new Date(dateString);
|
||||
return shortDate;
|
||||
}
|
||||
/**
|
||||
* Returns the file name by spliting the file url
|
||||
*/
|
||||
public GetFileName(fileAbsoluteUrl: string): string {
|
||||
if (fileAbsoluteUrl) {
|
||||
let items = fileAbsoluteUrl.split('/');
|
||||
return items[items.length - 1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the FileRef value from the absolute url
|
||||
*/
|
||||
public GetFileRef(fileAbsoluteUrl: string): string {
|
||||
if (fileAbsoluteUrl) {
|
||||
return fileAbsoluteUrl.replace(window.location.origin, "");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
/**
|
||||
* Gets the Content Type value from the value of the search manged property ContentType
|
||||
*/
|
||||
public GetContentType(searchValue: string) {
|
||||
//the string value is in the format:
|
||||
// "application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
// <line break>
|
||||
// Document"
|
||||
debugger;
|
||||
searchValue = searchValue.replace(/\r?\n|\r/g, "#");
|
||||
let result: string = "";
|
||||
if (searchValue) {
|
||||
if (searchValue.indexOf("#") > 0) {
|
||||
result = searchValue.split("#")[searchValue.split("#").length - 1];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the document in a new tab. The code use window.open
|
||||
*/
|
||||
public OpenDocument(docItem: IDocument, thisContext: any, openPDFInClient: boolean): void {
|
||||
|
||||
let newTabObject: any = null;
|
||||
try {
|
||||
let documentWebUrl: string = "";
|
||||
if (docItem.FileRef.toLowerCase().indexOf(".pdf") > 0) {
|
||||
documentWebUrl = window.location.origin + docItem.FileRef + "?web=1";
|
||||
}
|
||||
else {
|
||||
|
||||
documentWebUrl = docItem.ParentWebUrl + "/_layouts/WopiFrame.aspx"
|
||||
+ "?sourcedoc=" + encodeURIComponent("{" + docItem.UniqueId + "}") + "&action=default";
|
||||
}
|
||||
|
||||
newTabObject = window.open(documentWebUrl);
|
||||
}
|
||||
catch (ex) {
|
||||
//optionaly, we can notify the user;
|
||||
// cuurently - do nothing
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Helper method that check if a value is in the IContextualMenuItem[]
|
||||
*/
|
||||
private _IsValuePresented(currentValues: IContextualMenuItem[], newValue: string): boolean {
|
||||
|
||||
for (let i = 0; i < currentValues.length; i++) {
|
||||
if (currentValues[i].key == newValue) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
.documents {
|
||||
|
||||
.container {
|
||||
max-width: 700px;
|
||||
margin: 0px auto;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.row {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
max-width: 715px;
|
||||
margin: 5px auto 5px auto;
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.button {
|
||||
// Our button
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
|
||||
// Primary Button
|
||||
min-width: 80px;
|
||||
background-color: "[theme:themePrimary, default:#0078d7]";
|
||||
border-color: "[theme:themePrimary, default:#0078d7]";
|
||||
color: #ffffff;
|
||||
|
||||
// Basic Button
|
||||
outline: transparent;
|
||||
position: relative;
|
||||
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
border-width: 0;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 16px;
|
||||
|
||||
.label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.notification{
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
}
|
||||
.notificationIcon{
|
||||
font-size: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.notificationHeader
|
||||
{
|
||||
font-size: 20px;
|
||||
}
|
||||
.notificationDescription
|
||||
{
|
||||
font-size: 18px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.loadingWrapper
|
||||
{
|
||||
padding-top: 80px;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
.topBar
|
||||
{
|
||||
border-top: 1px solid #eaeaea;
|
||||
height: 40px;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.topBarText
|
||||
{
|
||||
position: relative;
|
||||
top:7px;
|
||||
left:20px;
|
||||
}
|
||||
.topBarFilters
|
||||
{
|
||||
text-align: right;
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
right: 30px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.calloutHeader {
|
||||
padding: 18px 24px 12px;
|
||||
}
|
||||
.resetFilter
|
||||
{
|
||||
margin-right: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.docsWrapper{
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.iframeWrapper
|
||||
{
|
||||
width: 95%;
|
||||
height: 0px;
|
||||
}
|
|
@ -0,0 +1,505 @@
|
|||
import * as React from 'react';
|
||||
import styles from './Documents.module.scss';
|
||||
import { escape } from '@microsoft/sp-lodash-subset';
|
||||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
Link, MarqueeSelection, DetailsList, Selection, Image, ImageFit,
|
||||
SelectionMode, Spinner, SpinnerSize, Fabric, ColumnActionsMode, IColumn, CheckboxVisibility,
|
||||
Callout, Panel, PanelType, IContextualMenuItem, autobind, ContextualMenu, IContextualMenuProps, DirectionalHint,
|
||||
css
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import { IDocumentsProps } from './IDocumentsProps';
|
||||
import IDocumentsState from './IDocumentsState';
|
||||
import * as _ from "lodash";
|
||||
import MockupDataProvider from '../../../dataproviders/MockupDataProvider';
|
||||
import IDataProvider from '../../../dataproviders/IDataProvider';
|
||||
import { IDocument } from '../../../common/IObjects';
|
||||
import { Utils } from '../../../common/Utils';
|
||||
|
||||
|
||||
|
||||
export default class LibraryDocuments extends React.Component<IDocumentsProps, IDocumentsState> {
|
||||
|
||||
|
||||
private _selection: Selection;
|
||||
private _isConfigurationValid: boolean = true;
|
||||
|
||||
constructor(props: IDocumentsProps) {
|
||||
super(props);
|
||||
|
||||
this._isConfigurationValid = false;
|
||||
if (props.dataProvider) {
|
||||
this._isConfigurationValid = props.dataProvider.validateSettings();
|
||||
}
|
||||
|
||||
this.state = {
|
||||
allDocuments: [],
|
||||
displayedDocuments: [],
|
||||
isLoading: true,
|
||||
contextualMenuProps: null,
|
||||
columns: this._setupColumns()
|
||||
};
|
||||
|
||||
this._renderItemColumn = this._renderItemColumn.bind(this);
|
||||
this._onResetFiltersClicked = this._onResetFiltersClicked.bind(this);
|
||||
this._onContextualMenuDismissed = this._onContextualMenuDismissed.bind(this);
|
||||
this._getContextualMenuProps = this._getContextualMenuProps.bind(this);
|
||||
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IDocumentsProps> {
|
||||
|
||||
let { contextualMenuProps } = this.state;
|
||||
|
||||
if (!this._isConfigurationValid && this.props.webPartDisplayMode === DisplayMode.Edit) {
|
||||
return (
|
||||
<div className={styles.notification}>
|
||||
<div className={styles.notificationIcon}>
|
||||
<i className={css("ms-Icon ms-Icon--ErrorBadge", styles.notificationIcon)} aria-hidden="true"></i>
|
||||
<span className={styles.notificationHeader}>
|
||||
Edit Mode
|
||||
</span>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
if (!this._isConfigurationValid && this.props.webPartDisplayMode === DisplayMode.Read) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.notification}>
|
||||
<div className={styles.notificationIcon}>
|
||||
<i className={css("ms-Icon ms-Icon--ErrorBadge", styles.notificationIcon)} aria-hidden="true"></i>
|
||||
<span className={styles.notificationHeader}>
|
||||
Preview Mode
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
|
||||
}
|
||||
if (this._isConfigurationValid) {
|
||||
|
||||
if (this.state.isLoading) {
|
||||
if (SpinnerSize && SpinnerSize.large) {
|
||||
return (<div className={styles.loadingWrapper}>
|
||||
<Spinner size={SpinnerSize.large} label='Loading documents...' />
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
if (this.state.isErrorOccured) {
|
||||
return (<div>
|
||||
<div className={styles.notification}>
|
||||
<div className={styles.notificationIcon}>
|
||||
<i className={css("ms-Icon ms-Icon--ErrorBadge", styles.notificationIcon)} aria-hidden="true"></i>
|
||||
<span className={styles.notificationHeader}>
|
||||
Something went wrong...
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.notificationDescription}>
|
||||
<span>
|
||||
{this.state.errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.topBar}>
|
||||
<span className={styles.topBarText}>{this.props.title}</span>
|
||||
<div className={styles.topBarFilters} >
|
||||
{this.state.showResetFilters && (
|
||||
<span className={styles.resetFilter}>
|
||||
<i className="ms-Icon ms-Icon--ClearFilter"
|
||||
aria-hidden="false"
|
||||
role="button"
|
||||
onClick={this._onResetFiltersClicked} >
|
||||
</i>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<MarqueeSelection selection={this._selection}>
|
||||
<DetailsList
|
||||
className={styles.docsWrapper}
|
||||
columns={this.state.columns}
|
||||
items={this.state.displayedDocuments}
|
||||
onItemInvoked={(item) => { this._openDocument(item, this); }}
|
||||
selectionPreservedOnEmptyClick={true}
|
||||
onRenderItemColumn={this._renderItemColumn}
|
||||
checkboxVisibility={CheckboxVisibility.hidden} />
|
||||
{contextualMenuProps && (
|
||||
<ContextualMenu { ...contextualMenuProps } />
|
||||
)}
|
||||
</MarqueeSelection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public componentDidMount() {
|
||||
debugger;
|
||||
if (this._isConfigurationValid) {
|
||||
|
||||
if (this.props.useSearchData) {
|
||||
this.props.dataProvider.readDocumentsFromSearch().then(
|
||||
(documents: IDocument[]) => {
|
||||
debugger;
|
||||
this.setState({
|
||||
allDocuments: documents,
|
||||
displayedDocuments: documents,
|
||||
isLoading: false,
|
||||
columns: this.state.columns
|
||||
});
|
||||
|
||||
},
|
||||
(data: any) => {
|
||||
|
||||
this.setState({
|
||||
allDocuments: [],
|
||||
displayedDocuments: [],
|
||||
isLoading: false,
|
||||
isErrorOccured: true,
|
||||
errorMessage: data
|
||||
});
|
||||
|
||||
}).catch((ex) => {
|
||||
|
||||
this.setState({
|
||||
allDocuments: [],
|
||||
displayedDocuments: [],
|
||||
isLoading: false,
|
||||
isErrorOccured: true,
|
||||
errorMessage: ex.errorMessage
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.props.dataProvider.readDocumentsFromLibrary().then(
|
||||
//resolve
|
||||
(documents: IDocument[]) => {
|
||||
debugger;
|
||||
this.setState({
|
||||
allDocuments: documents,
|
||||
displayedDocuments: documents,
|
||||
isLoading: false,
|
||||
columns: this.state.columns
|
||||
});
|
||||
|
||||
},
|
||||
//reject
|
||||
(data: any) => {
|
||||
|
||||
this.setState({
|
||||
allDocuments: [],
|
||||
displayedDocuments: [],
|
||||
|
||||
isLoading: false,
|
||||
isErrorOccured: true,
|
||||
errorMessage: data
|
||||
});
|
||||
}
|
||||
).catch((ex) => {
|
||||
debugger;
|
||||
this.setState({
|
||||
allDocuments: [],
|
||||
displayedDocuments: [],
|
||||
isLoading: false,
|
||||
isErrorOccured: true,
|
||||
errorMessage: ex.errorMessage
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Specify the columns and their properties
|
||||
*/
|
||||
private _setupColumns(): IColumn[] {
|
||||
|
||||
const columnsSingleClient: IColumn[] =
|
||||
[{
|
||||
key: 'FileIcon',
|
||||
name: '',
|
||||
fieldName: 'FileIcon',
|
||||
minWidth: 20,
|
||||
maxWidth: 20,
|
||||
isResizable: true,
|
||||
data: String
|
||||
},
|
||||
{
|
||||
key: 'Name',
|
||||
name: 'Name',
|
||||
fieldName: 'Name',
|
||||
minWidth: 100,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
columnActionsMode: ColumnActionsMode.hasDropdown,
|
||||
onColumnClick: this._onColumnClick,
|
||||
onColumnContextMenu: this._onColumnContextMenu,
|
||||
data: String
|
||||
},
|
||||
{
|
||||
key: 'ContentType',
|
||||
name: 'Content Type',
|
||||
fieldName: 'ContentType',
|
||||
minWidth: 80,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
columnActionsMode: ColumnActionsMode.hasDropdown,
|
||||
onColumnClick: this._onColumnClick,
|
||||
onColumnContextMenu: this._onColumnContextMenu,
|
||||
data: String
|
||||
},
|
||||
{
|
||||
key: 'Id',
|
||||
name: 'ID',
|
||||
fieldName: 'Id',
|
||||
minWidth: 60,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
columnActionsMode: ColumnActionsMode.hasDropdown,
|
||||
onColumnClick: this._onColumnClick,
|
||||
onColumnContextMenu: this._onColumnContextMenu,
|
||||
data: Number
|
||||
},
|
||||
{
|
||||
key: 'VersionString',
|
||||
name: 'Version',
|
||||
fieldName: 'VersionString',
|
||||
minWidth: 60,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
data: String
|
||||
},
|
||||
{
|
||||
key: 'Modified',
|
||||
name: 'Modified',
|
||||
fieldName: 'Modified',
|
||||
minWidth: 80,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
columnActionsMode: ColumnActionsMode.hasDropdown,
|
||||
onColumnClick: this._onColumnClick,
|
||||
onColumnContextMenu: this._onColumnContextMenu,
|
||||
data: Date
|
||||
},
|
||||
{
|
||||
key: 'ModifiedBy',
|
||||
name: 'Modified By',
|
||||
fieldName: 'ModifiedBy',
|
||||
minWidth: 80,
|
||||
isResizable: true,
|
||||
isSorted: false,
|
||||
isSortedDescending: false,
|
||||
columnActionsMode: ColumnActionsMode.hasDropdown,
|
||||
onColumnClick: this._onColumnClick,
|
||||
onColumnContextMenu: this._onColumnContextMenu,
|
||||
data: String
|
||||
},
|
||||
];
|
||||
|
||||
return columnsSingleClient;
|
||||
}
|
||||
|
||||
private _onResetFiltersClicked() {
|
||||
|
||||
let columns = this.state.columns;
|
||||
//reset the columns
|
||||
_.map(columns, (c: IColumn) => {
|
||||
|
||||
c.isSorted = false;
|
||||
c.isSortedDescending = false;
|
||||
c.isFiltered = false;
|
||||
|
||||
});
|
||||
//update the state, this will force the control to refresh
|
||||
this.setState({
|
||||
displayedDocuments: this.state.allDocuments,
|
||||
showResetFilters: false,
|
||||
columns: columns
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private _renderItemColumn(item, index, column) {
|
||||
//here we can add column specific logic
|
||||
// - image control for the FileIcon column
|
||||
// - render link for the Name column
|
||||
let fieldContent = item[column.fieldName];
|
||||
|
||||
switch (column.key) {
|
||||
case 'FileIcon':
|
||||
return <Image src={fieldContent} width={16} height={16} imageFit={ImageFit.center} />;
|
||||
case 'Name':
|
||||
return <Link data-selection-invoke={true} >{item[column.key]}</Link>;
|
||||
default:
|
||||
return <span>{fieldContent}</span>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private _openDocument(docItem: IDocument, thisContext: any): void {
|
||||
debugger;
|
||||
let utility = new Utils();
|
||||
utility.OpenDocument(docItem, thisContext, true);
|
||||
}
|
||||
|
||||
@autobind
|
||||
private _onSortColumn(column: IColumn, isSortedDescending: boolean) {
|
||||
|
||||
column = _.find(this.state.columns, c => c.fieldName === column.fieldName);
|
||||
column.isSortedDescending = isSortedDescending;
|
||||
column.isSorted = true;
|
||||
|
||||
//reset the other columns
|
||||
let modifeidColumns: IColumn[] = this.state.columns;
|
||||
_.map(modifeidColumns, (c: IColumn) => {
|
||||
if (c.fieldName != column.fieldName) {
|
||||
c.isSorted = false;
|
||||
c.isSortedDescending = false;
|
||||
}
|
||||
});
|
||||
|
||||
let modifiedDocs = this.state.displayedDocuments;
|
||||
|
||||
modifiedDocs = _.orderBy(
|
||||
modifiedDocs,
|
||||
[(document) => {
|
||||
console.log(document[column.fieldName]);
|
||||
console.log(typeof (document[column.fieldName]));
|
||||
|
||||
if (column.data == Number) {
|
||||
if (document[column.fieldName]) {
|
||||
return parseInt(document[column.fieldName]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (column.data == Date) {
|
||||
if (document[column.fieldName]) {
|
||||
|
||||
return new Date(document[column.fieldName]);
|
||||
}
|
||||
return new Date(0);
|
||||
}
|
||||
|
||||
return document[column.fieldName];
|
||||
}],
|
||||
[column.isSortedDescending ? "desc" : "asc"]);
|
||||
|
||||
this.setState({
|
||||
displayedDocuments: modifiedDocs,
|
||||
showResetFilters: true,
|
||||
columns: modifeidColumns
|
||||
});
|
||||
}
|
||||
|
||||
@autobind
|
||||
public ClickFilter(ev?: React.MouseEvent<HTMLElement>, item?: IContextualMenuItem): void {
|
||||
debugger;
|
||||
if (item) {
|
||||
let columns = this.state.columns;
|
||||
|
||||
columns.filter(matchColumn => matchColumn.key === item.data)
|
||||
.forEach((filteredColumn: IColumn) => {
|
||||
filteredColumn.isFiltered = true;
|
||||
});
|
||||
|
||||
let documents = this.state.displayedDocuments;
|
||||
let newDocs = [];
|
||||
if (item.data != "Tags") {
|
||||
newDocs = documents.filter(matchDoc => matchDoc[item.data] === item.key);
|
||||
}
|
||||
else {
|
||||
for (let i = 0; i < documents.length; i++) {
|
||||
let itemValue: string = documents[i][item.data];
|
||||
if (itemValue.indexOf(item.key) > -1) {
|
||||
newDocs.push(documents[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
this.setState({ displayedDocuments: newDocs, showResetFilters: true });
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private _onColumnClick(ev: React.MouseEvent<HTMLElement>, column: IColumn) {
|
||||
debugger;
|
||||
if (column.columnActionsMode !== ColumnActionsMode.disabled) {
|
||||
this.setState({
|
||||
contextualMenuProps: this._getContextualMenuProps(ev, column)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
private _onColumnContextMenu(column: IColumn, ev: React.MouseEvent<HTMLElement>) {
|
||||
debugger;
|
||||
if (column.columnActionsMode !== ColumnActionsMode.disabled) {
|
||||
this.setState({
|
||||
contextualMenuProps: this._getContextualMenuProps(ev, column)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _getContextualMenuProps(ev: React.MouseEvent<HTMLElement>, column: IColumn): IContextualMenuProps {
|
||||
debugger;
|
||||
let utility = new Utils();
|
||||
let items: IContextualMenuItem[] = utility.GetSortingMenuItems(column, this._onSortColumn);
|
||||
if (this.isFilterable(column.key)) {
|
||||
items.push({
|
||||
key: 'filterBy',
|
||||
name: 'Filter by ',// + column.name,
|
||||
canCheck: true,
|
||||
checked: column.isFiltered,
|
||||
subMenuProps: {
|
||||
items: this._GetFilterValues(column)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
items: items,
|
||||
targetElement: ev.currentTarget as HTMLElement,
|
||||
directionalHint: DirectionalHint.bottomLeftEdge,
|
||||
gapSpace: 10,
|
||||
isBeakVisible: true,
|
||||
onDismiss: this._onContextualMenuDismissed
|
||||
};
|
||||
}
|
||||
|
||||
private _onContextualMenuDismissed() {
|
||||
this.setState({
|
||||
contextualMenuProps: null
|
||||
});
|
||||
}
|
||||
|
||||
private _GetFilterValues(column: IColumn): IContextualMenuItem[] {
|
||||
|
||||
debugger;
|
||||
let utility = new Utils();
|
||||
let filters = utility.GetFilterValues(column, this.state.displayedDocuments, this.ClickFilter);
|
||||
return filters;
|
||||
}
|
||||
|
||||
// one approach of how to control where to have filter menu
|
||||
private isFilterable(columnKey: string): boolean {
|
||||
return columnKey != "Name";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import IDataProvider from '../../../dataproviders/IDataProvider';
|
||||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
|
||||
export interface IDocumentsProps {
|
||||
|
||||
title: string;
|
||||
webPartDisplayMode: DisplayMode;
|
||||
dataProvider: IDataProvider;
|
||||
useSearchData: boolean;
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import { IDocument } from '../../../common/IObjects';
|
||||
import { IColumn, IContextualMenuProps } from 'office-ui-fabric-react';
|
||||
|
||||
interface IDocumentsState {
|
||||
allDocuments?: IDocument[];
|
||||
displayedDocuments?: IDocument[];
|
||||
showResetFilters?: boolean;
|
||||
isLoading?: boolean;
|
||||
columns?: IColumn[];
|
||||
showPanel?: boolean;
|
||||
panelDocUrl?: string;
|
||||
panelTitle?: string;
|
||||
contextualMenuProps?: IContextualMenuProps;
|
||||
|
||||
//???
|
||||
isErrorOccured?: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export default IDocumentsState;
|
|
@ -0,0 +1,11 @@
|
|||
import { IDocument } from "../common/IObjects";
|
||||
|
||||
export default interface IDataProvider {
|
||||
|
||||
validateSettings(): boolean;
|
||||
|
||||
readDocumentsFromSearch(): Promise<IDocument[]>;
|
||||
|
||||
readDocumentsFromLibrary(): Promise<IDocument[]>;
|
||||
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import { IDocument } from "../common/IObjects";
|
||||
import IDataProvider from './IDataProvider';
|
||||
import { Utils } from '../common/Utils';
|
||||
|
||||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
||||
|
||||
|
||||
export default class MockupDataProvider implements IDataProvider {
|
||||
|
||||
private _libraryAbsoluteUrl: string;
|
||||
|
||||
constructor(libraryUrl: string) {
|
||||
if (libraryUrl) {
|
||||
this._libraryAbsoluteUrl = libraryUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public validateSettings(): boolean {
|
||||
|
||||
if (!this._libraryAbsoluteUrl) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sample data
|
||||
*/
|
||||
public readDocumentsFromSearch(): Promise<IDocument[]> {
|
||||
let utility = new Utils();
|
||||
let today: Date = new Date();
|
||||
let date2: Date = new Date();
|
||||
date2.setDate(today.getDate() - 5);
|
||||
|
||||
let _items: IDocument[] = [
|
||||
{
|
||||
Id: 1,
|
||||
Name: "Test File.pdf",
|
||||
FileRef: "/somesite/library/Test File.pdf",
|
||||
Modified: utility.GetFormatedDate(today),
|
||||
ModifiedBy: "Dimcho Tsanov",
|
||||
ContentType: "Document",
|
||||
FileIcon: utility.GetImgUrl("Test File.pdf"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Name: "Report 2017-09.xlsx",
|
||||
FileRef: "/somesite/library/Report 2017-09.xlsx",
|
||||
Modified: utility.GetFormatedDate(today),
|
||||
ModifiedBy: "Dimcho Tsanov",
|
||||
ContentType: "Document",
|
||||
FileIcon: utility.GetImgUrl("Report 2017-09.xlsx"),
|
||||
VersionString: "1.0",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Name: "Report 2017-10.xlsx",
|
||||
FileRef: "/somesite/library/Report 2017-10.xlsx",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Velin Georgiev",
|
||||
ContentType: "Report",
|
||||
FileIcon: utility.GetImgUrl("Report 2017-10.xlsx"),
|
||||
VersionString: "11.0",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Name: "Report 2016-11.xlsx",
|
||||
FileRef: "/somesite/library/Report 2016-11.xlsx",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Velin Georgiev",
|
||||
ContentType: "Meeting Minutes",
|
||||
FileIcon: utility.GetImgUrl("Report 2016-11.xlsx"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
Name: "Test File_final.pdf",
|
||||
FileRef: "/somesite/library/Test File_final.pdf",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Yana Tsanova",
|
||||
ContentType: "Meeting Minutes",
|
||||
FileIcon: utility.GetImgUrl("Test File_final.pdf"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
];
|
||||
|
||||
return new Promise<IDocument[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(_items);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns sample data
|
||||
*/
|
||||
public readDocumentsFromLibrary(): Promise<IDocument[]> {
|
||||
let utility = new Utils();
|
||||
let today: Date = new Date();
|
||||
let date2: Date = new Date();
|
||||
date2.setDate(today.getDate() - 5);
|
||||
|
||||
let _items: IDocument[] = [
|
||||
{
|
||||
Id: 1,
|
||||
Name: "Test File.pdf",
|
||||
FileRef: "/somesite/library/Test File.pdf",
|
||||
Modified: utility.GetFormatedDate(today),
|
||||
ModifiedBy: "Dimcho Tsanov",
|
||||
ContentType: "Document",
|
||||
FileIcon: utility.GetImgUrl("Test File.pdf"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Name: "Report 2017-09.xlsx",
|
||||
FileRef: "/somesite/library/Report 2017-09.xlsx",
|
||||
Modified: utility.GetFormatedDate(today),
|
||||
ModifiedBy: "Dimcho Tsanov",
|
||||
ContentType: "Document",
|
||||
FileIcon: utility.GetImgUrl("Report 2017-09.xlsx"),
|
||||
VersionString: "1.0",
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
Name: "Report 2017-10.xlsx",
|
||||
FileRef: "/somesite/library/Report 2017-10.xlsx",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Velin Georgiev",
|
||||
ContentType: "Report",
|
||||
FileIcon: utility.GetImgUrl("Report 2017-10.xlsx"),
|
||||
VersionString: "11.0",
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
Name: "Report 2016-11.xlsx",
|
||||
FileRef: "/somesite/library/Report 2016-11.xlsx",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Velin Georgiev",
|
||||
ContentType: "Meeting Minutes",
|
||||
FileIcon: utility.GetImgUrl("Report 2016-11.xlsx"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
Name: "Test File_final.pdf",
|
||||
FileRef: "/somesite/library/Test File_final.pdf",
|
||||
Modified: utility.GetFormatedDate(date2),
|
||||
ModifiedBy: "Yana Tsanova",
|
||||
ContentType: "Meeting Minutes",
|
||||
FileIcon: utility.GetImgUrl("Test File_final.pdf"),
|
||||
VersionString: "1.2",
|
||||
},
|
||||
];
|
||||
return new Promise<IDocument[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(_items);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
import { IWebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import { SPHttpClient } from '@microsoft/sp-http';
|
||||
|
||||
import { IDocument } from "../common/IObjects";
|
||||
import IDataProvider from "./IDataProvider";
|
||||
import { Utils } from '../common/Utils';
|
||||
|
||||
|
||||
export default class SharePointDataProvider implements IDataProvider {
|
||||
|
||||
private _webPartContext: IWebPartContext;
|
||||
private _libraryAbsoluteUrl: string;
|
||||
private _webAbsoluteUrl: string;
|
||||
|
||||
constructor(value: IWebPartContext, libraryUrl: string) {
|
||||
this._webPartContext = value;
|
||||
this._libraryAbsoluteUrl =
|
||||
libraryUrl.lastIndexOf("/") == libraryUrl.length - 1 ?
|
||||
libraryUrl.substr(0, libraryUrl.length - 1) :
|
||||
libraryUrl;
|
||||
this._webAbsoluteUrl = value.pageContext.web.absoluteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is all settings passed in the constructor are correctly initialized
|
||||
*/
|
||||
public validateSettings(): boolean {
|
||||
|
||||
if (!this._libraryAbsoluteUrl) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all documents from the Search index where the Path contains the library url
|
||||
* Note: Library url is passed as parameter in the constructor
|
||||
*/
|
||||
public readDocumentsFromSearch(): Promise<IDocument[]> {
|
||||
let utility = new Utils();
|
||||
let searchQuery = '(path:"' + encodeURIComponent(this._libraryAbsoluteUrl) + '*")AND(IsDocument:1)';
|
||||
let webAbsoluteUrl = this._webPartContext.pageContext.web.absoluteUrl;
|
||||
const searchRequestUrl1: string = `${webAbsoluteUrl}/_api/search/query?querytext='${searchQuery}'` +
|
||||
"&selectproperties='DocId,ContentType,ModifiedBy,LastModifiedTime,FileExtension,Path,SPWebURL,UIVersionStringOWSTEXT,UniqueId'";
|
||||
// log in the console for debugging purpose
|
||||
console.log(searchQuery);
|
||||
return this._webPartContext.spHttpClient.get(
|
||||
searchRequestUrl1,
|
||||
SPHttpClient.configurations.v1,
|
||||
{
|
||||
headers: {
|
||||
"odata-version": "3.0",
|
||||
"accept": "application/json;odata=verbose"
|
||||
},
|
||||
method: "GET"
|
||||
})
|
||||
.then((response: any) => {
|
||||
debugger;
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
} else {
|
||||
return Promise.reject(new Error(JSON.stringify(response)));
|
||||
}
|
||||
}).then((response: any) => {
|
||||
debugger;
|
||||
//convert the reuselts in object with properties
|
||||
let results: any[] = response.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results;
|
||||
var obj = [];
|
||||
for (let l = 0; l < results.length; l++) {
|
||||
var cells = results[l].Cells.results;
|
||||
var cell = {};
|
||||
for (let m = 0; m < cells.length; m++) {
|
||||
cell[cells[m].Key] = cells[m].Value;
|
||||
}
|
||||
obj.push(cell);
|
||||
}
|
||||
// use the search results as objects
|
||||
let docs: IDocument[] = [];
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
docs.push({
|
||||
Id: parseInt(obj[i].DocId),
|
||||
FileRef: utility.GetFileRef(obj[i].OriginalPath),
|
||||
Modified: utility.GetFormatedDateString(obj[i].LastModifiedTime),
|
||||
ModifiedBy: obj[i].ModifiedBy,
|
||||
FileIcon: utility.GetImgUrlByFileExtension(obj[i].FileExtension),
|
||||
Name: utility.GetFileName(obj[i].Path),
|
||||
VersionString: obj[i].UIVersionStringOWSTEXT,
|
||||
ContentType: utility.GetContentType(obj[i].ContentType),
|
||||
ParentWebUrl: obj[i].SPWebURL,
|
||||
UniqueId: obj[i].UniqueId.replace("{", "").replace("}", "")
|
||||
});
|
||||
}
|
||||
return docs;
|
||||
});
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
the above code use get request for retrieving the search results; alternatively, you can use POST request
|
||||
Sample code:
|
||||
|
||||
var body = {
|
||||
'request': {
|
||||
'__metadata': { 'type': 'Microsoft.Office.Server.Search.REST.SearchRequest' },
|
||||
'Querytext': searchQuery,
|
||||
'RowLimit': '100',
|
||||
'TrimDuplicates': 'False',
|
||||
'SelectProperties': {
|
||||
'results':
|
||||
['DocId', 'ModifiedBy', 'OriginalPath', 'LastModifiedTime', 'FileExtension', 'Path', 'SPWebURL']
|
||||
}
|
||||
}
|
||||
};
|
||||
const searchRequestUrl: string = `${webAbsoluteUrl}/_api/search/postquery`;
|
||||
return this._webPartContext.spHttpClient.post(
|
||||
searchRequestUrl,
|
||||
SPHttpClient.configurations.v1,
|
||||
{
|
||||
headers: {
|
||||
"odata-version": "3.0",
|
||||
"accept": "application/json;odata=verbose"
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
method: "POST"
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all documents from the library
|
||||
* Note: Library url is passed as parameter in the constructor
|
||||
*/
|
||||
public readDocumentsFromLibrary(): Promise<IDocument[]> {
|
||||
|
||||
debugger;
|
||||
let utility = new Utils();
|
||||
let libraryRelativeUrl = utility.GetRelativePathFromAbsolute(this._libraryAbsoluteUrl);
|
||||
|
||||
return this._readListId(libraryRelativeUrl).then((listId: string): Promise<IDocument[]> => {
|
||||
|
||||
|
||||
const queryUrlGetAllItems: string = this._webAbsoluteUrl + `/_api/web/lists(guid'${listId}')/Items` +
|
||||
"?$select=ID,DocIcon,FileLeafRef,FileRef,Modified,UniqueId,OData__UIVersionString,ContentTypeId,ContentType/Name,Editor/Title&$expand=Editor,ContentType";
|
||||
|
||||
/*
|
||||
The above query will get all items, including folders and items in the folders.
|
||||
After that we remove those items, that are not based on the Document Content Type.
|
||||
Depending on your logic, you can use different endpoints, like:
|
||||
|
||||
/_api/web/lists(guid'${listId}')/GetItems(query=@v1)?@v1={"FolderServerRelativeUrl" : "${libraryRelativeUrl}", "ViewXml":"<View Scope='RecursiveAll'></View>"}
|
||||
/_api/web/GetFolderByServerRelativePath(decodedurl='${libraryRelativeUrl}')?$select=ID,FileLeafRef,FileRef,ModifiedBy&$expand=Files,ModifiedBy
|
||||
/_api/web/GetFolderByServerRelativeUrl('${libraryRelativeUrl}')/Files?$expand=ListItemAllFields
|
||||
*/
|
||||
return this._webPartContext.spHttpClient.get(
|
||||
queryUrlGetAllItems,
|
||||
SPHttpClient.configurations.v1)
|
||||
.then(
|
||||
(response: any) => {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
} else {
|
||||
return Promise.reject(new Error(JSON.stringify(response)));
|
||||
}
|
||||
})
|
||||
.then((data: any) => {
|
||||
debugger;
|
||||
let documents: IDocument[] = [];
|
||||
if (data) {
|
||||
for (let i = 0; i < data.value.length; i++) {
|
||||
let item = data.value[i];
|
||||
|
||||
//check the content type; Include only documents in the response
|
||||
if (item.ContentTypeId.indexOf("0x0101") == 0) {
|
||||
var doc: IDocument = {
|
||||
Id: item.Id,
|
||||
FileRef: item.FileRef,
|
||||
Name: item.FileLeafRef,
|
||||
VersionString: item.OData__UIVersionString,
|
||||
ContentType: item.ContentType.Name,
|
||||
ModifiedBy: item.Editor.Title,
|
||||
Modified: utility.GetFormatedDateString(item.Modified),
|
||||
UniqueId: item.UniqueId,
|
||||
ParentWebUrl: this._webAbsoluteUrl,// this will work in case the library is in the same web as the web part!
|
||||
//icon for the Folder content type is a different
|
||||
FileIcon: item.ContentType.Name != "Folder" ? utility.GetImgUrlByFileExtension(item.DocIcon) : utility.GetImgUrlByFileExtension("folder")
|
||||
};
|
||||
documents.push(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
return documents;
|
||||
|
||||
}).catch((ex) => {
|
||||
console.log("readDocumentsFromLibrary > spHttpClient.get()...catch:", ex);
|
||||
throw ex;
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Helper Methods
|
||||
|
||||
/**
|
||||
* Returns the list's ID based on its site relative url
|
||||
* listRelativeUrl format: '/sites/mysite/shared documents'
|
||||
* returned value: Guid if succeeded, otherwise - empty string
|
||||
*/
|
||||
private _readListId(listRelativeUrl: string): Promise<string> {
|
||||
|
||||
let queryUrlGetList = this._webAbsoluteUrl + "/_api/web/GetFolderByServerRelativePath(decodedurl='" + decodeURIComponent(listRelativeUrl) + "')/Properties";
|
||||
|
||||
return this._webPartContext.spHttpClient.get(
|
||||
queryUrlGetList,
|
||||
SPHttpClient.configurations.v1)
|
||||
.then(
|
||||
(response: any) => {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
} else {
|
||||
return Promise.reject(new Error(JSON.stringify(response)));
|
||||
}
|
||||
})
|
||||
.then((data: any) => {
|
||||
debugger;
|
||||
if (data) {
|
||||
let listIdValue: string = data.vti_x005f_listname; // string format '{00000000-0000-0000-0000-000000000000}'
|
||||
let listId = listIdValue.replace("{", "").replace("}", "");
|
||||
return listId;
|
||||
}
|
||||
else {
|
||||
console.log("no list info");
|
||||
}
|
||||
return "";
|
||||
}).catch((ex) => {
|
||||
console.log("_readListId > spHttpClient.get()...catch:", ex);
|
||||
throw ex;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "8635c52e-cf0b-4c6d-a5ba-66f1d8d92005",
|
||||
"alias": "LibraryDocumentsWebPart",
|
||||
"componentType": "WebPart",
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": {
|
||||
"default": "SPFx Samples"
|
||||
},
|
||||
"title": {
|
||||
"default": "Library Documents"
|
||||
},
|
||||
"description": {
|
||||
"default": "A sample webpart that displays library documents in Office UI Fabric DetailsList."
|
||||
},
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"libraryUrl": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
|
||||
import Documents from '../../components/documentsList/component/Documents';
|
||||
import { IDocumentsProps } from '../../components/documentsList/component/IDocumentsProps';
|
||||
|
||||
import IDataProvider from '../../dataproviders/IDataProvider';
|
||||
import SharePointDataProvider from '../../dataproviders/SharePointDataProvider';
|
||||
import MockupDataProvider from '../../dataproviders/MockupDataProvider';
|
||||
|
||||
import * as strings from 'LibraryDocumentsWebPartStrings';
|
||||
|
||||
export interface ILibraryDocumentsWebPartProps {
|
||||
libraryUrl: string;
|
||||
}
|
||||
|
||||
export default class LibraryDocumentsWebPart extends BaseClientSideWebPart<ILibraryDocumentsWebPartProps> {
|
||||
|
||||
private _dataProvider: IDataProvider;
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
debugger;
|
||||
if (DEBUG && Environment.type === EnvironmentType.Local) {
|
||||
this._dataProvider = new MockupDataProvider(this.properties.libraryUrl);
|
||||
|
||||
} else {
|
||||
if (this.properties.libraryUrl) {
|
||||
this._dataProvider = new SharePointDataProvider(this.context, this.properties.libraryUrl);
|
||||
}
|
||||
else {
|
||||
//the WebPart property is not filled
|
||||
//do nothing, the Documents component will display notification message
|
||||
}
|
||||
}
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IDocumentsProps> = React.createElement(
|
||||
Documents,
|
||||
{
|
||||
title: "Library Documents",
|
||||
useSearchData: false,
|
||||
webPartDisplayMode: this.displayMode,
|
||||
dataProvider: this._dataProvider
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, 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('libraryUrl', {
|
||||
label: strings.LibraryUrlFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
7
samples/react-documents-detailslist/src/webparts/libraryDocuments/loc/en-us.js
vendored
Normal file
7
samples/react-documents-detailslist/src/webparts/libraryDocuments/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"LibraryUrlFieldLabel": "Add a library absolute url:"
|
||||
}
|
||||
});
|
10
samples/react-documents-detailslist/src/webparts/libraryDocuments/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-documents-detailslist/src/webparts/libraryDocuments/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface ILibraryDocumentsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
LibraryUrlFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'LibraryDocumentsWebPartStrings' {
|
||||
const strings: ILibraryDocumentsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('LibraryDocumentsWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "e7054e6c-a83c-4522-9cb1-b311381142f4",
|
||||
"alias": "SearchDocumentsWebPart",
|
||||
"componentType": "WebPart",
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": {
|
||||
"default": "SPFx Samples"
|
||||
},
|
||||
"title": {
|
||||
"default": "Search Documents"
|
||||
},
|
||||
"description": {
|
||||
"default": "A sample webpart that displays search documents in Office UI Fabric DetailsList."
|
||||
},
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"libraryUrl": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-webpart-base';
|
||||
import Documents from '../../components/documentsList/component/Documents';
|
||||
import { IDocumentsProps } from '../../components/documentsList/component/IDocumentsProps';
|
||||
import IDataProvider from '../../dataproviders/IDataProvider';
|
||||
import SharePointDataProvider from '../../dataproviders/SharePointDataProvider';
|
||||
import MockupDataProvider from '../../dataproviders/MockupDataProvider';
|
||||
import * as strings from 'SearchDocumentsWebPartStrings';
|
||||
|
||||
|
||||
export interface ISearchDocumentsWebPartProps {
|
||||
libraryUrl: string;
|
||||
}
|
||||
|
||||
export default class SearchDocumentsWebPart extends BaseClientSideWebPart<ISearchDocumentsWebPartProps> {
|
||||
|
||||
private _dataProvider: IDataProvider;
|
||||
|
||||
protected onInit(): Promise<void> {
|
||||
|
||||
debugger;
|
||||
if (DEBUG && Environment.type === EnvironmentType.Local) {
|
||||
this._dataProvider = new MockupDataProvider(this.properties.libraryUrl);
|
||||
|
||||
} else {
|
||||
if (this.properties.libraryUrl) {
|
||||
this._dataProvider = new SharePointDataProvider(this.context, this.properties.libraryUrl);
|
||||
}
|
||||
else {
|
||||
//the WebPart property is not filled
|
||||
//do nothing, the Documents component will display notification message
|
||||
}
|
||||
}
|
||||
return super.onInit();
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IDocumentsProps> = React.createElement(
|
||||
Documents,
|
||||
{
|
||||
title: "Search Documents",
|
||||
useSearchData: true,
|
||||
webPartDisplayMode: this.displayMode,
|
||||
dataProvider: this._dataProvider
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, 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('libraryUrl', {
|
||||
label: strings.LibraryUrlFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
7
samples/react-documents-detailslist/src/webparts/searchDocuments/loc/en-us.js
vendored
Normal file
7
samples/react-documents-detailslist/src/webparts/searchDocuments/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"LibraryUrlFieldLabel": "Add library absolute url:"
|
||||
}
|
||||
});
|
10
samples/react-documents-detailslist/src/webparts/searchDocuments/loc/mystrings.d.ts
vendored
Normal file
10
samples/react-documents-detailslist/src/webparts/searchDocuments/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare interface ISearchDocumentsWebPartStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
LibraryUrlFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'SearchDocumentsWebPartStrings' {
|
||||
const strings: ISearchDocumentsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/// <reference types="mocha" />
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('SearchDocumentsWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"es6-collections",
|
||||
"webpack-env"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Type definitions for Microsoft ODSP projects
|
||||
// Project: ODSP
|
||||
|
||||
/* Global definition for UNIT_TEST builds
|
||||
Code that is wrapped inside an if(UNIT_TEST) {...}
|
||||
block will not be included in the final bundle when the
|
||||
--ship flag is specified */
|
||||
declare const UNIT_TEST: boolean;
|
||||
|
||||
/* Global defintion for SPO builds */
|
||||
declare const DATACENTER: boolean;
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
Loading…
Reference in New Issue