add todo-webpart-sample which contains 4 steps web part.
This commit is contained in:
parent
9e131acb18
commit
1a547030cc
|
@ -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 @@
|
|||
* text=auto
|
|
@ -0,0 +1,35 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Yeoman configuration files
|
||||
.yo-rc.json
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.spapp
|
||||
|
||||
# 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,14 @@
|
|||
# Folders
|
||||
.vscode
|
||||
coverage
|
||||
node_modules
|
||||
solution
|
||||
src
|
||||
temp
|
||||
|
||||
# Files
|
||||
*.csproj
|
||||
.git*
|
||||
.yo-rc.json
|
||||
gulpfile.js
|
||||
tsconfig.json
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
// The number of spaces a tab is equal to.
|
||||
"editor.tabSize": 2,
|
||||
|
||||
// When enabled, will trim trailing whitespace when you save a file.
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
// Controls if the editor should automatically close brackets after opening them
|
||||
"editor.autoClosingBrackets": false,
|
||||
|
||||
// Configure glob patterns for excluding files and folders.
|
||||
"search.exclude": {
|
||||
"**/bower_components": true,
|
||||
"**/node_modules": true,
|
||||
"coverage": true,
|
||||
"dist": true,
|
||||
"lib-amd": true,
|
||||
"lib": true,
|
||||
"temp": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "0.1.0",
|
||||
"command": "gulp",
|
||||
"isShellCommand": true,
|
||||
"showOutput": "always",
|
||||
"args": [
|
||||
"--no-color"
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "bundle",
|
||||
"isBuildCommand": true,
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"taskName": "test",
|
||||
"isTestCommand": true,
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"taskName": "serve",
|
||||
"isWatching": true,
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
## todo-webpart-sample
|
||||
|
||||
This is where you include your web part docs.
|
||||
|
||||
### Building the code
|
||||
|
||||
```bash
|
||||
git clone the repo
|
||||
npm i
|
||||
npm i -g gulp
|
||||
gulp
|
||||
```
|
||||
|
||||
This package produces the following:
|
||||
|
||||
* lib/* commonjs components - this allows this package to be reused from other packages.
|
||||
* dist/* - a single bundle containing the components used for uploading to a cdn pointing a registered Sharepoint webpart library to.
|
||||
* example/* a test page that hosts all components in this package.
|
||||
|
||||
### Build options
|
||||
|
||||
gulp nuke - TODO
|
||||
gulp test - TODO
|
||||
gulp watch - TODO
|
||||
gulp build - TODO
|
||||
gulp deploy - TODO
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"entries": [
|
||||
{
|
||||
"entry": "./lib/webparts/todo-step-1/TodoWebPart.js",
|
||||
"manifest": "./src/webparts/todo-step-1/TodoWebPart.manifest.json",
|
||||
"outputPath": "./dist/todo-step-1.bundle.js"
|
||||
},
|
||||
{
|
||||
"entry": "./lib/webparts/todo-step-2/TodoWebPart.js",
|
||||
"manifest": "./src/webparts/todo-step-2/TodoWebPart.manifest.json",
|
||||
"outputPath": "./dist/todo-step-2.bundle.js"
|
||||
},
|
||||
{
|
||||
"entry": "./lib/webparts/todo-step-3/TodoWebPart.js",
|
||||
"manifest": "./src/webparts/todo-step-3/TodoWebPart.manifest.json",
|
||||
"outputPath": "./dist/todo-step-3.bundle.js"
|
||||
},
|
||||
{
|
||||
"entry": "./lib/webparts/todo-step-4/TodoWebPart.js",
|
||||
"manifest": "./src/webparts/todo-step-4/TodoWebPart.manifest.json",
|
||||
"outputPath": "./dist/todo-step-4.bundle.js"
|
||||
}
|
||||
],
|
||||
"externals": {
|
||||
"@microsoft/sp-client-base": "node_modules/@microsoft/sp-client-base/dist/sp-client-base.js",
|
||||
"@microsoft/sp-client-preview": "node_modules/@microsoft/sp-client-preview/dist/sp-client-preview.js",
|
||||
"@microsoft/sp-lodash-subset": "node_modules/@microsoft/sp-lodash-subset/dist/sp-lodash-subset.js",
|
||||
"office-ui-fabric-react": "node_modules/office-ui-fabric-react/dist/office-ui-fabric-react.js",
|
||||
"react": "node_modules/react/dist/react.min.js",
|
||||
"react-dom": "node_modules/react-dom/dist/react-dom.min.js",
|
||||
"react-dom/server": "node_modules/react-dom/dist/react-dom-server.min.js"
|
||||
},
|
||||
"localizedResources": {
|
||||
"helloWorldStrings": "webparts/helloWorld/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "todo-webpart-sample",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"solution": {
|
||||
"name": "todo-webpart-sample-client-side-solution",
|
||||
"id": "99a1fe26-363c-4980-b7c7-f14322a071ed",
|
||||
"version": "1.0.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "todo-webpart-sample.spapp"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"port": 4321,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"https": true,
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
// 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,
|
||||
"label-undefined": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-case": true,
|
||||
"no-duplicate-key": false,
|
||||
"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-unused-imports": true,
|
||||
"no-unused-variable": true,
|
||||
"no-unreachable": 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,
|
||||
"prefer-const": true,
|
||||
"a11y-role": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
|
||||
build.initialize(gulp);
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "todo-webpart-sample",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-lodash-subset": "~0.2.0",
|
||||
"@microsoft/sp-client-base": "~0.2.0",
|
||||
"@microsoft/sp-client-preview": "~0.2.0",
|
||||
"office-ui-fabric": "2.6.1",
|
||||
"office-ui-fabric-react": "0.46.1",
|
||||
"react": "0.14.8",
|
||||
"react-addons-update": "0.14.8",
|
||||
"react-dom": "0.14.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "~0.5.0",
|
||||
"@microsoft/sp-module-interfaces": "~0.2.0",
|
||||
"@microsoft/sp-webpart-workbench": "~0.2.0",
|
||||
"gulp": "~3.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp nuke",
|
||||
"test": "gulp test"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
|
||||
<ListInstance
|
||||
Title="DefaultTodoList"
|
||||
OnQuickLaunch="TRUE"
|
||||
TemplateType="171"
|
||||
FeatureId="F9CE21F8-F437-4f7e-8BC6-946378C850F0"
|
||||
Url="Lists/DefaultTodoList"
|
||||
Description="Default list for the Todo sample webpart"
|
||||
>
|
||||
</ListInstance>
|
||||
</Elements>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Feature
|
||||
xmlns="http://schemas.microsoft.com/sharepoint/"
|
||||
Title="Todo web part"
|
||||
Id="771d8fd4-46ce-421d-a61c-79c55e1fba19"
|
||||
Scope="Web"
|
||||
Description="Provision default tasks list (DefaultTodoList) for todo web part."
|
||||
Hidden="FALSE"
|
||||
>
|
||||
</Feature>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AppPartConfig
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://schemas.microsoft.com/sharepoint/2012/app/partconfiguration"
|
||||
>
|
||||
<Id>771d8fd4-46ce-421d-a61c-79c55e1fba19</Id>
|
||||
</AppPartConfig>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
||||
<Relationship
|
||||
Type="http://schemas.microsoft.com/sharepoint/2012/app/relationships/partconfiguration"
|
||||
Target="/TodoWebPartFeature.xml.config.xml"
|
||||
Id="r1"
|
||||
/>
|
||||
<Relationship
|
||||
Type="http://schemas.microsoft.com/sharepoint/2012/app/relationships/feature-elementmanifest"
|
||||
Target="/List/Elements.xml"
|
||||
Id="r2"
|
||||
/>
|
||||
</Relationships>
|
|
@ -0,0 +1,5 @@
|
|||
var context = require.context('.', true, /.+\.test\.js?$/);
|
||||
|
||||
context.keys().forEach(context);
|
||||
|
||||
module.exports = context;
|
|
@ -0,0 +1,3 @@
|
|||
export interface ITodoWebPartProps {
|
||||
description: string;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
## Todo sample web part step 1
|
||||
|
||||
In this step, we create an empty React web part using Yeoman SharePoint Generator.
|
||||
|
||||
## How to build
|
||||
|
||||
```bash
|
||||
npm install
|
||||
gulp serve
|
||||
gulp package-solution
|
||||
```
|
|
@ -0,0 +1,21 @@
|
|||
.todo {
|
||||
.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 {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
|
||||
"id": "e31b9536-2df9-4d43-9ab2-1fdaaff05154",
|
||||
"componentType": "WebPart",
|
||||
"version": "0.0.1",
|
||||
"manifestVersion": 2,
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "e31b9536-2df9-4d43-9ab2-1fdaaff05154",
|
||||
"group": { "default": "Under Development" },
|
||||
"title": { "default": "Todo step 1" },
|
||||
"description": { "default": "Todo sample webpart step 1" },
|
||||
"officeFabricIconFontName": "Page",
|
||||
"properties": {
|
||||
"description": "Todo"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IPropertyPaneSettings,
|
||||
PropertyPaneTextField
|
||||
} from '@microsoft/sp-client-preview';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import Todo, { ITodoProps } from './components/Todo';
|
||||
import { ITodoWebPartProps } from './ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* This is the todo sample web part built using the SharePoint Framework.
|
||||
*
|
||||
* Find out more docs and tutorials at:
|
||||
* https://github.com/SharePoint/sp-dev-docs/wiki
|
||||
*/
|
||||
export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps> {
|
||||
/**
|
||||
* Override the base render() implementation to render the todo sample web part.
|
||||
*/
|
||||
public render(): void {
|
||||
const element: React.ReactElement<ITodoProps> = React.createElement(Todo, {
|
||||
description: this.properties.description
|
||||
});
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* The PropertyPane settings for properties to be configured in PropertyPane.
|
||||
*/
|
||||
protected get propertyPaneSettings(): IPropertyPaneSettings {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField('description', {
|
||||
label: strings.DescriptionFieldLabel
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import * as React from 'react';
|
||||
import { css } from 'office-ui-fabric-react';
|
||||
|
||||
import styles from '../Todo.module.scss';
|
||||
import { ITodoWebPartProps } from '../ITodoWebPartProps';
|
||||
|
||||
export interface ITodoProps extends ITodoWebPartProps {
|
||||
}
|
||||
|
||||
export default class Todo extends React.Component<ITodoProps, {}> {
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<div className={styles.todo}>
|
||||
<div className={styles.container}>
|
||||
<div className={css('ms-Grid-row ms-bgColor-themeDark ms-fontColor-white', styles.row)}>
|
||||
<div className='ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1'>
|
||||
<span className='ms-font-xl ms-fontColor-white'>
|
||||
Welcome to SharePoint!
|
||||
</span>
|
||||
<p className='ms-font-l ms-fontColor-white'>
|
||||
Customize SharePoint experiences using Web Parts.
|
||||
</p>
|
||||
<p className='ms-font-l ms-fontColor-white'>
|
||||
{this.props.description}
|
||||
</p>
|
||||
<a
|
||||
className={css('ms-Button', styles.button)}
|
||||
href='https://github.com/SharePoint/sp-dev-docs/wiki'
|
||||
>
|
||||
<span className='ms-Button-label'>Learn more</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"PropertyPaneDescription": "Description",
|
||||
"BasicGroupName": "Group Name",
|
||||
"DescriptionFieldLabel": "Description Field"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface ITodoStrings {
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
DescriptionFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'todoStrings' {
|
||||
const strings: ITodoStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import * as assert from 'assert';
|
||||
|
||||
describe('TodoWebPart', () => {
|
||||
it('should do something', () => {
|
||||
assert.ok(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoWebPartProps.tsx
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for the Todo web part properties.
|
||||
*/
|
||||
export interface ITodoWebPartProps {
|
||||
/**
|
||||
* The current selected SharePoint tasks list.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* The max number of tasks showing in todo web part.
|
||||
*
|
||||
* The number of list items shown in the list will not exceed this number, and it also limits
|
||||
* the number of return items when it sends request to the sever.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the data used to render the todo component.
|
||||
*/
|
||||
export interface ITodoComponentData {
|
||||
/**
|
||||
* The selected list items rendered in Todo Component.
|
||||
*/
|
||||
selectedListItems?: ITodoTask[];
|
||||
|
||||
/**
|
||||
* The loading status of the list items.
|
||||
*/
|
||||
loadingStatus?: LoadingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of user data model related to this web part.
|
||||
*/
|
||||
export interface ITodoPerson {
|
||||
/**
|
||||
* The ID of the person which used to identify the user and fetch the user data from server.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The name of this person.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The url which representing the avator url of this person.
|
||||
*/
|
||||
Picture: string;
|
||||
|
||||
/**
|
||||
* The email address of this person.
|
||||
*
|
||||
* This field is only used in data provider to construct url for fetch user avator.
|
||||
*/
|
||||
EMail: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface of data modal for Todo task.
|
||||
*/
|
||||
export interface ITodoTask {
|
||||
/**
|
||||
* The ID of the todo item.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The title of the todo item.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The percent of the task that is completed.
|
||||
* In todo web part we use 0 to indicate task uncompleted and 1 to indicate task completed.
|
||||
*/
|
||||
PercentComplete: number;
|
||||
|
||||
/**
|
||||
* The person who created this todo task.
|
||||
*/
|
||||
Author: ITodoPerson;
|
||||
|
||||
/**
|
||||
* The person who marked this todo item as completed.
|
||||
*
|
||||
* Editor is the last person who performed editting operation.
|
||||
* In Todo web part, the only editting operation that can be performed by user is toggling complete.
|
||||
* This field is optional because it is not required for incomplete tasks.
|
||||
*/
|
||||
Editor?: ITodoPerson;
|
||||
}
|
||||
|
||||
/**
|
||||
* ITodoTaskList contains title and entity type full name which used in REST calls.
|
||||
*/
|
||||
export interface ITodoTaskList {
|
||||
/**
|
||||
* The title of the todo task list.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The ListItemEntityTypeFullName property of the list.
|
||||
*/
|
||||
ListItemEntityTypeFullName?: string;
|
||||
|
||||
/**
|
||||
* The Id property of the list.
|
||||
*/
|
||||
Id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the loading status for requesting for the items from SharePoint List.
|
||||
*/
|
||||
export enum LoadingStatus {
|
||||
/**
|
||||
* We are not loading anything.
|
||||
*/
|
||||
None,
|
||||
|
||||
/**
|
||||
* We are fetching the tasks items (Read items).
|
||||
*/
|
||||
FetchingTasks,
|
||||
|
||||
/**
|
||||
* We are updating the tasks list (Create, Update, or Delete the item).
|
||||
*/
|
||||
UpdatingTasks
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemOperationCallback is the type of callback that handle the operation on item.
|
||||
* It is used for creating, updating and deleting callbacks.
|
||||
*
|
||||
* @param {ITodoTask} item is the Todo task item that will be either created, updated, or deleted.
|
||||
*/
|
||||
export type ItemOperationCallback = (item: ITodoTask) => void;
|
|
@ -0,0 +1,11 @@
|
|||
## Todo sample web part step 2
|
||||
|
||||
In this step, we add the React components and a mock data provider for todo web part.
|
||||
|
||||
## How to build
|
||||
|
||||
```bash
|
||||
npm install
|
||||
gulp serve
|
||||
gulp package-solution
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
|
||||
"componentType": "WebPart",
|
||||
"version": "1.0.0",
|
||||
"manifestVersion": 2,
|
||||
"id": "89166469-d248-498f-8400-6d124b016f13",
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "89166469-d248-498f-8400-6d124b016f13",
|
||||
"group": {
|
||||
"default": "Under Development"
|
||||
},
|
||||
"title": {
|
||||
"default": "Todo step 2"
|
||||
},
|
||||
"description": {
|
||||
"default": "Todo sample webpart step 2"
|
||||
},
|
||||
"officeFabricIconFontName": "BulletedList",
|
||||
"properties": {
|
||||
"selectedList": {
|
||||
"Title": "DefaultTodoList"
|
||||
},
|
||||
"shouldShowCompletedTasks": true,
|
||||
"shouldShowCompletedBy": true,
|
||||
"shouldShowCreatedBy": true,
|
||||
"maxNumberOfTasks": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoWebPart.tsx
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
import update = require('react-addons-update');
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IWebPartContext
|
||||
} from '@microsoft/sp-client-preview';
|
||||
|
||||
import { ITodoDataProvider } from './dataProviders/ITodoDataProvider';
|
||||
import MockTodoDataProvider from './dataProviders/MockTodoDataProvider';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
ITodoTaskList,
|
||||
ITodoTask,
|
||||
LoadingStatus
|
||||
} from './ITodoWebPartProps';
|
||||
|
||||
import Todo, { ITodoProps } from './components/Todo';
|
||||
|
||||
/**
|
||||
* This is the client-side todo sample web part built using the SharePoint Framework.
|
||||
* It could interact with tasks lists in SharePoint site.
|
||||
*
|
||||
* Find out more docs and tutorials at:
|
||||
* https://github.com/SharePoint/sp-dev-docs/wiki
|
||||
*/
|
||||
export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps> {
|
||||
private _dataProvider: ITodoDataProvider;
|
||||
|
||||
private _shouldGetLists: boolean;
|
||||
private _listTitleToList: { [title: string]: ITodoTaskList };
|
||||
|
||||
private _todoComponentData: ITodoComponentData;
|
||||
|
||||
constructor(context: IWebPartContext) {
|
||||
super(context);
|
||||
|
||||
this._shouldGetLists = true;
|
||||
|
||||
this._todoComponentData = {
|
||||
selectedListItems: [],
|
||||
loadingStatus: LoadingStatus.None
|
||||
};
|
||||
|
||||
this._renderTodoComponent = this._renderTodoComponent.bind(this);
|
||||
this._readLists = this._readLists.bind(this);
|
||||
this._ensureSelectedList = this._ensureSelectedList.bind(this);
|
||||
this._createItem = this._createItem.bind(this);
|
||||
this._readItems = this._readItems.bind(this);
|
||||
this._updateItem = this._updateItem.bind(this);
|
||||
this._deleteItem = this._deleteItem.bind(this);
|
||||
this._toggleComplete = this._toggleComplete.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base onInit() implementation to get the persisted properties to initialize data provider.
|
||||
*/
|
||||
public onInit(): Promise<void> {
|
||||
this._dataProvider = new MockTodoDataProvider(this.properties);
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base render() implementation to render the todo sample web part.
|
||||
*/
|
||||
public render(): void {
|
||||
this._renderTodoComponent();
|
||||
|
||||
this._ensureSelectedList();
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.domElement);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _renderTodoComponent(partialData?: ITodoComponentData): void {
|
||||
lodash.extend(this._todoComponentData, partialData);
|
||||
|
||||
ReactDOM.render(
|
||||
<Todo
|
||||
{ ...this.properties }
|
||||
{ ...this._todoComponentData }
|
||||
onCreateItem={ this._createItem }
|
||||
onDeleteItem={ this._deleteItem }
|
||||
onToggleComplete={ this._toggleComplete }
|
||||
/> as React.ReactElement<ITodoProps>,
|
||||
this.domElement
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the information of all the task lists stored in the current site through data provider.
|
||||
*/
|
||||
private _readLists(): Promise<void> {
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
// Create map from list title to the list
|
||||
this._listTitleToList = {};
|
||||
lists.forEach((list: ITodoTaskList) => {
|
||||
this._listTitleToList[list.Title] = list;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no GUID for the selected task list in properties, we will read all task lists on the
|
||||
* site and retrieve the GUID. Usually it will happen at the first time we add todo web part, since
|
||||
* in manifest we could not predict the GUID for the default list added by SharePoint feature XML.
|
||||
* Finally we will read task items of the selected list.
|
||||
*/
|
||||
private _ensureSelectedList(): Promise<void> {
|
||||
if (!this.properties.selectedList.Id) {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
const selectedLists: ITodoTaskList[] = lists.filter((list: ITodoTaskList) => {
|
||||
return list.Title === this.properties.selectedList.Title;
|
||||
});
|
||||
|
||||
this.properties.selectedList = selectedLists[0] || lists[0];
|
||||
this._dataProvider.selectedList = this.properties.selectedList;
|
||||
})
|
||||
.then(this._readItems, (error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
});
|
||||
} else if (!this.renderedOnce) {
|
||||
return this._readItems();
|
||||
} else {
|
||||
// The list id exists and items have been fetched, do nothing.
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item and add it to the list through data provider.
|
||||
*/
|
||||
private _createItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.UpdatingTasks });
|
||||
|
||||
return this._dataProvider.createItem(item.Title)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the list items from the data provider.
|
||||
*/
|
||||
private _readItems(): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
if (this._dataProvider.maxNumberOfTasks > this._todoComponentData.selectedListItems.length) {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readItems()
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.slice(0, this._dataProvider.maxNumberOfTasks)
|
||||
});
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a item in the list through data provider.
|
||||
*/
|
||||
private _updateItem(newItem: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
const updatingIndex: number = lodash.findIndex(this._todoComponentData.selectedListItems,
|
||||
(item: ITodoTask) => item.Id === newItem.Id
|
||||
);
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: update(this._todoComponentData.selectedListItems, { [updatingIndex]: { $set: newItem } })
|
||||
});
|
||||
|
||||
return this._dataProvider.updateItem(newItem)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a item from the list through data provider.
|
||||
*/
|
||||
private _deleteItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.filter((task: ITodoTask) => task.Id !== item.Id)
|
||||
});
|
||||
|
||||
return this._dataProvider.deleteItem(item)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the complete state of an item by.
|
||||
*
|
||||
* Will call updateItem function to update complete state of this item.
|
||||
*/
|
||||
private _toggleComplete(item: ITodoTask): Promise<void> {
|
||||
// Create a new Item in which the PercentComplete value has been changed.
|
||||
const newItem: ITodoTask = update(item, {
|
||||
PercentComplete: { $set: item.PercentComplete >= 1 ? 0 : 1 }
|
||||
});
|
||||
|
||||
return this._updateItem(newItem);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Utils.ts
|
||||
*/
|
||||
|
||||
// Regex that finds { and } so they can be removed on a lookup for string format
|
||||
const FORMAT_ARGS_REGEX: RegExp = /[\{\}]/g;
|
||||
|
||||
// Regex that finds {#} so it can be replaced by the arguments in string format
|
||||
const FORMAT_REGEX: RegExp = /\{\d+\}/g;
|
||||
|
||||
/**
|
||||
* String Format is like C# string format.
|
||||
* Usage Example: "hello {0}!".format("mike") will return "hello mike!"
|
||||
* Calling format on a string with less arguments than specified in the format is invalid
|
||||
* Example "I love {0} every {1}".format("CXP") will result in a Debug Exception.
|
||||
*/
|
||||
/* tslint:disable:no-any no-null-keyword export-name */
|
||||
export function format(s: string, ...values: any[]): string {
|
||||
'use strict';
|
||||
|
||||
const args: any[] = values;
|
||||
// Callback match function
|
||||
function replace_func(match: string): any {
|
||||
// looks up in the args
|
||||
let replacement: any = args[match.replace(FORMAT_ARGS_REGEX, '')];
|
||||
|
||||
// catches undefined in nondebug and null in debug and nondebug
|
||||
if (replacement === null) {
|
||||
replacement = '';
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
return (s.replace(FORMAT_REGEX, replace_func));
|
||||
}
|
||||
/* tslint:enable:no-any no-null-keyword export-name */
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Todo.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
LoadingStatus,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoForm from './TodoForm';
|
||||
import TodoTabs from './TodoTabs';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for Todo component.
|
||||
*/
|
||||
export interface ITodoProps extends ITodoComponentData, ITodoWebPartProps {
|
||||
/**
|
||||
* onCreateItem callback triggered when we add a new item to the tasks list.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onCreateItem: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo component is the top level react component of this web part.
|
||||
* It uses fabric-react component <Spinner>
|
||||
*
|
||||
* Link of Spinner: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/spinner
|
||||
*/
|
||||
export default class Todo extends React.Component<ITodoProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return (
|
||||
<div className={ styles.todo }>
|
||||
<div className={ styles.topRow }>
|
||||
<h2 className={ styles.todoHeading }>{ strings.TodoListTitle }</h2>
|
||||
{ this._workingOnItSpinner }
|
||||
</div>
|
||||
<TodoForm
|
||||
onSubmit={ this.props.onCreateItem }
|
||||
/>
|
||||
<TodoTabs
|
||||
{ ...this.props }
|
||||
items={ this.props.selectedListItems }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
{ this._fetchingSpinner }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get _workingOnItSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.UpdatingTasks
|
||||
? (
|
||||
<div className={ styles.workingOnItSpinner }>
|
||||
<Spinner type={ SpinnerType.normal } />
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private get _fetchingSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.FetchingTasks
|
||||
? (
|
||||
<div className={ styles.fetchingTasksSpinner }>
|
||||
<Spinner
|
||||
type={ SpinnerType.large }
|
||||
label= { strings.FetchingTasksLabel }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoForm.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
ButtonType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormProps {
|
||||
/**
|
||||
* onSubmit callback triggered when the form is submitted.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onSubmit: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormState {
|
||||
/**
|
||||
* inputValue is the react state of input box value.
|
||||
*/
|
||||
inputValue: string;
|
||||
|
||||
/**
|
||||
* The error message will show below the input box if the title filled in is invalid.
|
||||
*/
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The form component used for adding new item to the list.
|
||||
*
|
||||
* It uses fabric-react component <TextField> <Button>
|
||||
* Link of TextField: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/textfield
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
*/
|
||||
export default class TodoForm extends React.Component<ITodoFormProps, ITodoFormState> {
|
||||
private _textField: TextField;
|
||||
|
||||
constructor(props: ITodoFormProps) {
|
||||
super(props);
|
||||
|
||||
this._handleSubmit = this._handleSubmit.bind(this);
|
||||
this._handleChanged = this._handleChanged.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
};
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoFormProps, nextState: ITodoFormState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<form className={ styles.todoForm } onSubmit={ this._handleSubmit }>
|
||||
<TextField
|
||||
className={ styles.textField }
|
||||
value={ this.state.inputValue }
|
||||
ref={(ref: TextField) => this._textField = ref}
|
||||
placeholder={ strings.InputBoxPlaceholder }
|
||||
onBeforeChange={ this._handleChanged }
|
||||
autoComplete='off'
|
||||
errorMessage={ this.state.errorMessage }
|
||||
/>
|
||||
<div className={ styles.addButtonCell }>
|
||||
<Button
|
||||
className={ styles.addButton }
|
||||
buttonType={ ButtonType.primary }
|
||||
ariaLabel={ strings.AddButton }
|
||||
>
|
||||
{ strings.AddButton }
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSubmit(event: React.FormEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this._getTitleErrorMessage(this.state.inputValue)) {
|
||||
this.setState({
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
});
|
||||
|
||||
this.props.onSubmit({
|
||||
Title: this.state.inputValue
|
||||
} as ITodoTask);
|
||||
} else {
|
||||
this.setState({
|
||||
errorMessage: this._getTitleErrorMessage(this.state.inputValue)
|
||||
} as ITodoFormState);
|
||||
|
||||
this._textField.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChanged(newValue: string): void {
|
||||
this.setState({
|
||||
inputValue: newValue
|
||||
} as ITodoFormState);
|
||||
}
|
||||
|
||||
private _getTitleErrorMessage(title: string): string {
|
||||
if (title === '') {
|
||||
return strings.TitleEmptyErrorMessage;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoItem.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Checkbox,
|
||||
Button,
|
||||
ButtonType,
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
DocumentCardActivity,
|
||||
css
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The props for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemProps {
|
||||
/**
|
||||
* The current Todo item to be rendered.
|
||||
*/
|
||||
item: ITodoTask;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of this item is checked or unchecked.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete button of this item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemState {
|
||||
/**
|
||||
* isDeleting indicates whether we are deleting this item.
|
||||
* If the item is being deleted, we will add animation to it.
|
||||
*/
|
||||
isDeleting: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TodoItem component using fabric-react component <FocusZone> <Checkbox> <Button> <DocumentCardActivity>.
|
||||
*
|
||||
* Link of FocusZone: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
* Link of Checkbox: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/checkbox
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
* Link of DocumentCardActivity: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/documentcard
|
||||
*/
|
||||
export default class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {
|
||||
private static ANIMATION_TIMEOUT: number = 200;
|
||||
|
||||
private _animationTimeoutId: number;
|
||||
|
||||
constructor(props: ITodoItemProps) {
|
||||
super(props);
|
||||
|
||||
this._handleToggleChanged = this._handleToggleChanged.bind(this);
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
|
||||
this.state = { isDeleting: false };
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoItemProps, nextState: ITodoItemState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
const className: string = css(
|
||||
styles.todoItem,
|
||||
'ms-Grid',
|
||||
'ms-u-slideDownIn20',
|
||||
{
|
||||
[styles.isCompleted]: this.props.item.PercentComplete >= 1,
|
||||
'ms-u-slideUpOut20': this.state.isDeleting
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='row'
|
||||
className={ className }
|
||||
aria-label={ this._ariaLabel }
|
||||
data-is-focusable={ true }
|
||||
>
|
||||
<FocusZone direction={ FocusZoneDirection.horizontal }>
|
||||
<div className={ css(styles.itemTaskRow, 'ms-Grid-row') }>
|
||||
<Checkbox
|
||||
className={ css(styles.checkbox, 'ms-Grid-col', 'ms-u-sm11') }
|
||||
label={ this.props.item.Title }
|
||||
onChange={ this._handleToggleChanged }
|
||||
checked={ this.props.item.PercentComplete >= 1 }
|
||||
/>
|
||||
<Button
|
||||
className={ css(styles.deleteButton, 'ms-Grid-col', 'ms-u-sm1') }
|
||||
buttonType={ ButtonType.icon }
|
||||
icon='X'
|
||||
onClick={ this._handleClick }
|
||||
ariaLabel={ strings.DeleteItemAriaLabel }
|
||||
rootProps={{
|
||||
title: strings.DeleteItemTitle
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={ css(styles.itemPeopleRow, 'ms-Grid-row') }>
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCreateLabel,
|
||||
this.props.item.Author,
|
||||
this.props.shouldShowCreatedBy
|
||||
)
|
||||
}
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCompleteLabel,
|
||||
this.props.item.Editor,
|
||||
this.props.shouldShowCompletedBy && this.props.item.PercentComplete >= 1
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FocusZone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPersona(
|
||||
activity: string,
|
||||
person: ITodoPerson,
|
||||
shouldShow: boolean
|
||||
): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return person && shouldShow
|
||||
? <DocumentCardActivity
|
||||
activity={ activity }
|
||||
people={[
|
||||
{
|
||||
name: person.Title,
|
||||
profileImageSrc: person.Picture
|
||||
}
|
||||
]}
|
||||
/>
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private get _ariaLabel(): string {
|
||||
const completeState: string = this.props.item.PercentComplete >= 1
|
||||
? strings.TodoItemAriaLabelCheckedState
|
||||
: strings.TodoItemAriaLabelUncheckedState;
|
||||
const titleString: string = format(strings.TodoItemAriaLabelTitle, this.props.item.Title);
|
||||
const createdBy: string = format(strings.TodoItemAriaLabelCreator, this.props.item.Author.Title);
|
||||
const completedBy: string = this.props.item.PercentComplete >= 1
|
||||
? format(strings.TodoItemAriaLabelEditor, this.props.item.Editor && this.props.item.Editor.Title)
|
||||
: '';
|
||||
|
||||
return format(strings.TodoItemAriaLabel, completeState, titleString, createdBy, completedBy);
|
||||
}
|
||||
|
||||
private _handleToggleChanged(ev: React.FormEvent, isChecked: boolean): void {
|
||||
this._handleWithAnimation(this.props.onToggleComplete);
|
||||
}
|
||||
|
||||
private _handleClick(event: React.MouseEvent): void {
|
||||
this._handleWithAnimation(this.props.onDeleteItem);
|
||||
}
|
||||
|
||||
private _handleWithAnimation(callback: (task: ITodoTask) => void): void {
|
||||
this.setState({ isDeleting: true });
|
||||
|
||||
// After ANIMATION_TIMEOUT milliseconds, the animation is finished and
|
||||
// we will delete the item from the task list and remove the animation from it.
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
this._animationTimeoutId = window.setTimeout(
|
||||
() => {
|
||||
this.setState({ isDeleting: false });
|
||||
|
||||
callback(this.props.item);
|
||||
},
|
||||
TodoItem.ANIMATION_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoList.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
IFocusZoneProps,
|
||||
List,
|
||||
KeyCodes,
|
||||
getRTLSafeKeyCode
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoItem, { ITodoItemProps } from './TodoItem';
|
||||
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props of TodoList component.
|
||||
*/
|
||||
export interface ITodoListProps {
|
||||
/**
|
||||
* The Todo items rendered in this List component.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoList component using fabric-react component <List> <FocusZone>
|
||||
*
|
||||
* Link of <List>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/list
|
||||
* Link of <FocusZone>: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
*/
|
||||
export default class TodoList extends React.Component<ITodoListProps, {}> {
|
||||
constructor(props: ITodoListProps) {
|
||||
super(props);
|
||||
|
||||
this._onRenderCell = this._onRenderCell.bind(this);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoListProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IFocusZoneProps> {
|
||||
return (
|
||||
<FocusZone
|
||||
direction={ FocusZoneDirection.vertical }
|
||||
isInnerZoneKeystroke={ (ev: React.KeyboardEvent) => ev.which === getRTLSafeKeyCode(KeyCodes.right) }
|
||||
>
|
||||
<List
|
||||
className={ styles.todoList }
|
||||
items={ this.props.items }
|
||||
onRenderCell={ this._onRenderCell }
|
||||
/>
|
||||
</FocusZone>
|
||||
);
|
||||
}
|
||||
|
||||
private _onRenderCell(item: ITodoTask): React.ReactElement<ITodoItemProps> {
|
||||
return (
|
||||
<TodoItem
|
||||
{ ...this.props }
|
||||
key={ item.Id }
|
||||
item={ item }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoTabs.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
Pivot,
|
||||
PivotItem,
|
||||
IPivotProps,
|
||||
PivotLinkSize
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoList from './TodoList';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The tab type used as the key of the PivotItem.
|
||||
*/
|
||||
enum TabType {
|
||||
/**
|
||||
* The tab showing the active tasks.
|
||||
*/
|
||||
Active,
|
||||
|
||||
/**
|
||||
* The tab showing the completed tasks.
|
||||
*/
|
||||
Completed,
|
||||
|
||||
/**
|
||||
* The tab showing all tasks in the list.
|
||||
*/
|
||||
All
|
||||
}
|
||||
|
||||
/**
|
||||
* Props of TodoTabs component.
|
||||
*/
|
||||
export interface ITodoTabsProps {
|
||||
/**
|
||||
* The list items rendered in TodoTabs.
|
||||
* It will be filtered in each PivotItems by needs.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoTabs component using fabric-react component <Pivot>.
|
||||
*
|
||||
* Link of <Pivot>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/pivot
|
||||
*/
|
||||
export default class TodoTabs extends React.Component<ITodoTabsProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoTabsProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IPivotProps> {
|
||||
const pivotArray: IPivotProps[] = [];
|
||||
|
||||
const activeTasks: ITodoTask[] = [];
|
||||
const completedTasks: ITodoTask[] = [];
|
||||
|
||||
this.props.items.forEach((item: ITodoTask) => {
|
||||
if (item.PercentComplete < 1) {
|
||||
activeTasks.push(item);
|
||||
} else if (this.props.shouldShowCompletedTasks) {
|
||||
completedTasks.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const allTasks: ITodoTask[] = activeTasks.concat(completedTasks);
|
||||
|
||||
if (activeTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(activeTasks, strings.TodoListTabNameActive, TabType.Active)
|
||||
);
|
||||
}
|
||||
|
||||
if (completedTasks.length > 0 && this.props.shouldShowCompletedTasks) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(completedTasks, strings.TodoListTabNameCompleted, TabType.Completed)
|
||||
);
|
||||
}
|
||||
|
||||
if (allTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(allTasks, strings.TodoListTabNameAllTasks, TabType.All)
|
||||
);
|
||||
}
|
||||
|
||||
return pivotArray.length > 0
|
||||
? (
|
||||
<div className={ styles.todoPivot }>
|
||||
<Pivot linkSize={ PivotLinkSize.large }>
|
||||
{ pivotArray }
|
||||
</Pivot>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private _renderPivotItemList(
|
||||
tasks: ITodoTask[],
|
||||
tabName: string,
|
||||
tabKey: TabType
|
||||
): React.ReactElement<IPivotProps> {
|
||||
return (
|
||||
<PivotItem
|
||||
linkText={ format(tabName, tasks.length) }
|
||||
itemKey={ tabKey }
|
||||
key={ tabKey }
|
||||
>
|
||||
<TodoList
|
||||
{ ...this.props }
|
||||
items={ tasks }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
</PivotItem>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* The data provider interface implemented by MockTodoDataProvider and TodoDataProvider.
|
||||
*/
|
||||
export interface ITodoDataProvider {
|
||||
/**
|
||||
* The current selected list.
|
||||
*
|
||||
* It should be always the same with the list in web part properties.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* The max number of tasks show in the Todo list.
|
||||
*
|
||||
* It should be always the sam with the maxNumberOfTasks in web part properties.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
|
||||
/**
|
||||
* readLists will fetch the information of all the lists in current site.
|
||||
*/
|
||||
readLists(): Promise<ITodoTaskList[]>;
|
||||
|
||||
/**
|
||||
* createItem will send REST call to add an item in the current .
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {string} title is the title of item that will be created in current list.
|
||||
*/
|
||||
createItem(title: string): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* readItems will send REST call to fetch the a number up to maxNumberOfTasks of items
|
||||
* in the current.
|
||||
*/
|
||||
readItems(): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* updateItem will send REST call to update(merge) an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemUpdated is the item which will be merged to current list.
|
||||
*/
|
||||
updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* deleteItem will send REST call to remove an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemDeleted is the item which will be deleted in current list.
|
||||
*/
|
||||
deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]>;
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file MockTodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import { ITodoDataProvider } from './ITodoDataProvider';
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* MockTodoDataProvider is used for testing page and unit test page.
|
||||
*/
|
||||
export default class MockTodoDataProvider implements ITodoDataProvider {
|
||||
private _lists: ITodoTaskList[];
|
||||
private _selectedList: ITodoTaskList;
|
||||
|
||||
private _maxNumberOfTasks: number;
|
||||
|
||||
private _itemsStore: { [listTitle: string]: ITodoTask[] };
|
||||
private _idCounter: number;
|
||||
|
||||
public get selectedList(): ITodoTaskList { return this._selectedList; }
|
||||
public set selectedList(value: ITodoTaskList) { this._selectedList = value; }
|
||||
|
||||
public get maxNumberOfTasks(): number { return this._maxNumberOfTasks; }
|
||||
public set maxNumberOfTasks(value: number) { this._maxNumberOfTasks = value; }
|
||||
|
||||
constructor(webPartProps: ITodoWebPartProps) {
|
||||
this._maxNumberOfTasks = webPartProps.maxNumberOfTasks;
|
||||
this.selectedList = webPartProps.selectedList;
|
||||
|
||||
this._idCounter = 0;
|
||||
|
||||
this._lists = [
|
||||
{
|
||||
Title: 'DefaultTodoList',
|
||||
ListItemEntityTypeFullName: '#SP.Data.DefaultTodoListListItem',
|
||||
Id: 'f3c61a58-44a8-4f87-bf5b-03668af148a6'
|
||||
},
|
||||
{
|
||||
Title: 'ListOne',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListOneListItem',
|
||||
Id: '01c78e45-06c2-4384-be9e-caa23912ebda'
|
||||
},
|
||||
{
|
||||
Title: 'ListTwo',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListTwoListItem',
|
||||
Id: '90c704fe-ab47-4d88-8b26-56f9a71e09e6'
|
||||
}
|
||||
];
|
||||
|
||||
this._itemsStore = {
|
||||
'DefaultTodoList': [
|
||||
this._generateItem('Finish Sample Todo web part before dev kitchen', false),
|
||||
this._generateItem('Finish All the work in Todo web part before dev kitchen', false),
|
||||
this._generateItem('Sharepoint API investigation for Todo web part', true),
|
||||
this._generateItem('Bug fixing of Pivot Control', true)
|
||||
],
|
||||
'ListOne': [
|
||||
this._generateItem('Item one in ListOne', false),
|
||||
this._generateItem('Item two in ListOne', false),
|
||||
this._generateItem('Item three in ListOne', true),
|
||||
this._generateItem('Item four in ListOne', true)
|
||||
],
|
||||
'ListTwo': [
|
||||
this._generateItem('Item one in ListTwo', false),
|
||||
this._generateItem('Item two in ListTwo', false),
|
||||
this._generateItem('Item three in ListTwo', true),
|
||||
this._generateItem('Item four in ListTwo', true)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the mock task lists.
|
||||
*/
|
||||
public readLists(): Promise<ITodoTaskList[]> {
|
||||
const lists: ITodoTaskList[] = this._lists;
|
||||
|
||||
return new Promise<ITodoTaskList[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(lists), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock task item using the title.
|
||||
*/
|
||||
public createItem(title: string): Promise<ITodoTask[]> {
|
||||
const newItem: ITodoTask = {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 3,
|
||||
Title: 'Lisa Andrews',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: 0
|
||||
};
|
||||
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].concat(newItem);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read task items from selected list.
|
||||
*/
|
||||
public readItems(): Promise<ITodoTask[]> {
|
||||
const items: ITodoTask[] =
|
||||
this._itemsStore[this._selectedList.Title].slice(0, this._maxNumberOfTasks);
|
||||
|
||||
return new Promise<ITodoTask[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(items), 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the task item.
|
||||
*/
|
||||
public updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]> {
|
||||
const index: number =
|
||||
lodash.findIndex(
|
||||
this._itemsStore[this._selectedList.Title],
|
||||
(item: ITodoTask) => item.Id === itemUpdated.Id
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
const editor: ITodoPerson = itemUpdated.PercentComplete >= 1
|
||||
? {
|
||||
Id: 3,
|
||||
Title: 'Chris Meyer',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
}
|
||||
: undefined;
|
||||
|
||||
this._itemsStore[this._selectedList.Title][index] = itemUpdated;
|
||||
this._itemsStore[this._selectedList.Title][index].Editor = editor;
|
||||
|
||||
return this.readItems();
|
||||
} else {
|
||||
return Promise.reject(new Error(`Item to update doesn't exist.`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the task item.
|
||||
*/
|
||||
public deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]> {
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].filter((item: ITodoTask) => item.Id !== itemDeleted.Id);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
private _generateItem(title: string, isCompleted: boolean): ITodoTask {
|
||||
return {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 1,
|
||||
Title: 'Misty Shock',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
Editor: !isCompleted ? undefined : {
|
||||
Id: 2,
|
||||
Title: 'Burton Guido',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: !isCompleted ? 0 : 1
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"TodoListTitle": "Todo List",
|
||||
"TodoListTabNameAllTasks": "All Tasks ({0})",
|
||||
"TodoListTabNameCompleted": "Completed ({0})",
|
||||
"TodoListTabNameActive": "Active ({0})",
|
||||
"AddButton": "Add",
|
||||
"InputBoxPlaceholder": "Add a task...",
|
||||
"FetchingListsLabel": "Fetching your lists...",
|
||||
"FetchingTasksLabel": "Fetching your tasks...",
|
||||
"TodoItemCreateLabel": "Creator",
|
||||
"TodoItemCompleteLabel": "Doer",
|
||||
"TodoItemAriaLabelCheckedState": "This item is completed.",
|
||||
"TodoItemAriaLabelUncheckedState": "This item is active.",
|
||||
"TodoItemAriaLabelTitle": "The title is {0}.",
|
||||
"TodoItemAriaLabelCreator": "This item is created by {0}",
|
||||
"TodoItemAriaLabelEditor": "This item is completed by {0}",
|
||||
"TodoItemAriaLabel": "{0} {1} {2} {3}",
|
||||
"DeleteItemTitle": "Delete this item.",
|
||||
"DeleteItemAriaLabel": "Delete",
|
||||
"WorkingOnSpinnerLabel": "Working on...",
|
||||
"PropertyPaneDescriptionSetProperties": "Set the properties of your todo items.",
|
||||
"PropertyPaneHeadingConfigureSoruce": "Configure source",
|
||||
"PropertyPaneHeadingConfigureDisplay": "Configure display",
|
||||
"PropertyPaneDropdownLoadingLabel": "Loading lists...",
|
||||
"DropdownErrorMessageNoListAvailable": "There is no task list in current SharePoint site. Please check there is at least one task list in site content.",
|
||||
"PropertyPaneDropdownLabelTasksList": "Tasks list",
|
||||
"PropertyPaneToggleOnTextShowCompletedTasks": "Show completed tasks",
|
||||
"PropertyPaneToggleOffTextHideCompletedTasks": "Hide completed tasks",
|
||||
"PropertyPaneCheckboxGroupLabel": "Select display fields to show",
|
||||
"PropertyPaneCheckboxCreatedByLabel": "Created By",
|
||||
"PropertyPaneCheckboxCompletedByLabel": "Completed By",
|
||||
"PropertyPaneSliderLabel": "Show this many tasks at a time",
|
||||
"TitleEmptyErrorMessage": "You can't leave this blank."
|
||||
}
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
declare interface ITodoStrings {
|
||||
TodoListTitle: string;
|
||||
TodoListTabNameAllTasks: string;
|
||||
TodoListTabNameCompleted: string;
|
||||
TodoListTabNameActive: string;
|
||||
AddButton: string;
|
||||
InputBoxPlaceholder: string;
|
||||
FetchingListsLabel: string;
|
||||
FetchingTasksLabel: string;
|
||||
TodoItemCreateLabel: string;
|
||||
TodoItemCompleteLabel: string;
|
||||
TodoItemAriaLabelCheckedState: string;
|
||||
TodoItemAriaLabelUncheckedState: string;
|
||||
TodoItemAriaLabelTitle: string;
|
||||
TodoItemAriaLabelCreator: string;
|
||||
TodoItemAriaLabelEditor: string;
|
||||
TodoItemAriaLabel: string;
|
||||
DeleteItemTitle: string;
|
||||
DeleteItemAriaLabel: string;
|
||||
WorkingOnSpinnerLabel: string;
|
||||
PropertyPaneDescriptionSetProperties: string;
|
||||
PropertyPaneHeadingConfigureSoruce: string;
|
||||
PropertyPaneHeadingConfigureDisplay: string;
|
||||
PropertyPaneDropdownLoadingLabel: string;
|
||||
DropdownErrorMessageNoListAvailable: string;
|
||||
PropertyPaneDropdownLabelTasksList: string;
|
||||
PropertyPaneToggleOnTextShowCompletedTasks: string;
|
||||
PropertyPaneToggleOffTextHideCompletedTasks: string;
|
||||
PropertyPaneCheckboxGroupLabel: string;
|
||||
PropertyPaneCheckboxCreatedByLabel: string;
|
||||
PropertyPaneCheckboxCompletedByLabel: string;
|
||||
PropertyPaneSliderLabel: string;
|
||||
TitleEmptyErrorMessage: string;
|
||||
}
|
||||
|
||||
declare module 'todoStrings' {
|
||||
const strings: ITodoStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
@import "~office-ui-fabric/dist/sass/Fabric.Common";
|
||||
|
||||
.todo {
|
||||
padding: 28px 40px;
|
||||
|
||||
.topRow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.todoHeading {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.todoError {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
}
|
||||
|
||||
.todoPivot {
|
||||
padding-top: 24px;
|
||||
|
||||
:global li.ms-Pivot-link{
|
||||
margin-right: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.todoForm {
|
||||
display: table;
|
||||
|
||||
.textField {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
.addButtonCell {
|
||||
display: table-cell;
|
||||
|
||||
.addButton {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.todoList {
|
||||
margin-top: 20px;
|
||||
border-top: 1px lightgrey solid;
|
||||
|
||||
:global .ms-Icon {
|
||||
font-family: 'sp-MDL2';
|
||||
}
|
||||
:global .ms-Icon--X {
|
||||
transform: rotate(45deg);
|
||||
|
||||
&:before {
|
||||
content: "\E710";
|
||||
}
|
||||
}
|
||||
|
||||
.todoItem {
|
||||
border: 1px lightgrey solid;
|
||||
border-top: none;
|
||||
|
||||
.itemTaskRow {
|
||||
padding: 16px 20px 8px 20px;
|
||||
|
||||
.deleteButton {
|
||||
:global .ms-Button-icon {
|
||||
float: right;
|
||||
font-size: 21px;
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
color: $ms-color-themePrimary;
|
||||
}
|
||||
}
|
||||
.checkbox {
|
||||
:global .ms-ChoiceField-field {
|
||||
margin: 0px;
|
||||
|
||||
&:after {
|
||||
top: 6px;
|
||||
}
|
||||
&:before {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
:global .ms-Label {
|
||||
color: $ms-color-neutralPrimary;
|
||||
font-size: 22px;
|
||||
padding: 0 0 0 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.itemPeopleRow {
|
||||
display: flex;
|
||||
padding: 8px 56px 16px 56px;
|
||||
|
||||
:global .ms-DocumentCardActivity {
|
||||
@include ms-u-sm4;
|
||||
padding-left: 0px;
|
||||
|
||||
.ms-DocumentCardActivity-details {
|
||||
left: 33px;
|
||||
}
|
||||
.ms-DocumentCardActivity-name {
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.isHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isCompleted {
|
||||
border-top: 1px white solid;
|
||||
border-left: 1px $ms-color-neutralLighter solid;
|
||||
border-right: 1px $ms-color-neutralLighter solid;
|
||||
@include ms-bgColor-neutralLighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workingOnItSpinner {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
height: 33.33333%;
|
||||
line-height: 1.5em;
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
.fetchingTasksSpinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:640px) {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.propertyPaneErrorMessage {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
|
||||
padding: 20px 0;
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoWebPartProps.tsx
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for the Todo web part properties.
|
||||
*/
|
||||
export interface ITodoWebPartProps {
|
||||
/**
|
||||
* The current selected SharePoint tasks list.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* The max number of tasks showing in todo web part.
|
||||
*
|
||||
* The number of list items shown in the list will not exceed this number, and it also limits
|
||||
* the number of return items when it sends request to the sever.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the data used to render the todo component.
|
||||
*/
|
||||
export interface ITodoComponentData {
|
||||
/**
|
||||
* The selected list items rendered in Todo Component.
|
||||
*/
|
||||
selectedListItems?: ITodoTask[];
|
||||
|
||||
/**
|
||||
* The loading status of the list items.
|
||||
*/
|
||||
loadingStatus?: LoadingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of user data model related to this web part.
|
||||
*/
|
||||
export interface ITodoPerson {
|
||||
/**
|
||||
* The ID of the person which used to identify the user and fetch the user data from server.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The name of this person.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The url which representing the avator url of this person.
|
||||
*/
|
||||
Picture: string;
|
||||
|
||||
/**
|
||||
* The email address of this person.
|
||||
*
|
||||
* This field is only used in data provider to construct url for fetch user avator.
|
||||
*/
|
||||
EMail: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface of data modal for Todo task.
|
||||
*/
|
||||
export interface ITodoTask {
|
||||
/**
|
||||
* The ID of the todo item.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The title of the todo item.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The percent of the task that is completed.
|
||||
* In todo web part we use 0 to indicate task uncompleted and 1 to indicate task completed.
|
||||
*/
|
||||
PercentComplete: number;
|
||||
|
||||
/**
|
||||
* The person who created this todo task.
|
||||
*/
|
||||
Author: ITodoPerson;
|
||||
|
||||
/**
|
||||
* The person who marked this todo item as completed.
|
||||
*
|
||||
* Editor is the last person who performed editting operation.
|
||||
* In Todo web part, the only editting operation that can be performed by user is toggling complete.
|
||||
* This field is optional because it is not required for incomplete tasks.
|
||||
*/
|
||||
Editor?: ITodoPerson;
|
||||
}
|
||||
|
||||
/**
|
||||
* ITodoTaskList contains title and entity type full name which used in REST calls.
|
||||
*/
|
||||
export interface ITodoTaskList {
|
||||
/**
|
||||
* The title of the todo task list.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The ListItemEntityTypeFullName property of the list.
|
||||
*/
|
||||
ListItemEntityTypeFullName?: string;
|
||||
|
||||
/**
|
||||
* The Id property of the list.
|
||||
*/
|
||||
Id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the loading status for requesting for the items from SharePoint List.
|
||||
*/
|
||||
export enum LoadingStatus {
|
||||
/**
|
||||
* We are not loading anything.
|
||||
*/
|
||||
None,
|
||||
|
||||
/**
|
||||
* We are fetching the tasks items (Read items).
|
||||
*/
|
||||
FetchingTasks,
|
||||
|
||||
/**
|
||||
* We are updating the tasks list (Create, Update, or Delete the item).
|
||||
*/
|
||||
UpdatingTasks
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemOperationCallback is the type of callback that handle the operation on item.
|
||||
* It is used for creating, updating and deleting callbacks.
|
||||
*
|
||||
* @param {ITodoTask} item is the Todo task item that will be either created, updated, or deleted.
|
||||
*/
|
||||
export type ItemOperationCallback = (item: ITodoTask) => void;
|
|
@ -0,0 +1,14 @@
|
|||
## Todo sample web part step 3
|
||||
|
||||
In this step, we add a data provider that could interact with current SharePoint site to fetch
|
||||
task lists and CRUD task items.
|
||||
|
||||
We also add codes to use framework's property pane to configure the todo web part's properties.
|
||||
|
||||
## How to build
|
||||
|
||||
```bash
|
||||
npm install
|
||||
gulp serve
|
||||
gulp package-solution
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
|
||||
"componentType": "WebPart",
|
||||
"version": "1.0.0",
|
||||
"manifestVersion": 2,
|
||||
"id": "2625c2e1-aa64-4b86-b922-d8190661d9f9",
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "2625c2e1-aa64-4b86-b922-d8190661d9f9",
|
||||
"group": {
|
||||
"default": "Under Development"
|
||||
},
|
||||
"title": {
|
||||
"default": "Todo step 3"
|
||||
},
|
||||
"description": {
|
||||
"default": "Todo sample webpart step 3"
|
||||
},
|
||||
"officeFabricIconFontName": "BulletedList",
|
||||
"properties": {
|
||||
"selectedList": {
|
||||
"Title": "DefaultTodoList"
|
||||
},
|
||||
"shouldShowCompletedTasks": true,
|
||||
"shouldShowCompletedBy": true,
|
||||
"shouldShowCreatedBy": true,
|
||||
"maxNumberOfTasks": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoWebPart.tsx
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
import update = require('react-addons-update');
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { EnvironmentType } from '@microsoft/sp-client-base';
|
||||
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IWebPartContext,
|
||||
IPropertyPaneSettings,
|
||||
IPropertyPaneField,
|
||||
IPropertyPaneFieldType,
|
||||
IPropertyPaneDropdownOption,
|
||||
PropertyPaneDropdown,
|
||||
PropertyPaneLabel,
|
||||
PropertyPaneCheckbox,
|
||||
PropertyPaneSlider,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-client-preview';
|
||||
|
||||
import { ITodoDataProvider } from './dataProviders/ITodoDataProvider';
|
||||
import MockTodoDataProvider from './dataProviders/MockTodoDataProvider';
|
||||
import TodoDataProvider from './dataProviders/TodoDataProvider';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
ITodoTaskList,
|
||||
ITodoTask,
|
||||
LoadingStatus
|
||||
} from './ITodoWebPartProps';
|
||||
|
||||
import Todo, { ITodoProps } from './components/Todo';
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from './style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* This is the client-side todo sample web part built using the SharePoint Framework.
|
||||
* It could interact with tasks lists in SharePoint site.
|
||||
*
|
||||
* Find out more docs and tutorials at:
|
||||
* https://github.com/SharePoint/sp-dev-docs/wiki
|
||||
*/
|
||||
export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps> {
|
||||
/**
|
||||
* This is the path for the list item title property in this.properties.
|
||||
* It will be provided to property pane to update the corresponding title field in the list item.
|
||||
*/
|
||||
private static SELECTED_LIST_TITLE_PROPERTY_PATH: string = 'selectedList.Title';
|
||||
|
||||
private _dataProvider: ITodoDataProvider;
|
||||
|
||||
private _shouldGetLists: boolean;
|
||||
private _listTitleToList: { [title: string]: ITodoTaskList };
|
||||
|
||||
private _dropdownError: Error;
|
||||
private _dropdownOptions: IPropertyPaneDropdownOption[];
|
||||
|
||||
private _todoComponentData: ITodoComponentData;
|
||||
|
||||
constructor(context: IWebPartContext) {
|
||||
super(context);
|
||||
|
||||
this._shouldGetLists = true;
|
||||
|
||||
this._todoComponentData = {
|
||||
selectedListItems: [],
|
||||
loadingStatus: LoadingStatus.None
|
||||
};
|
||||
|
||||
this._renderTodoComponent = this._renderTodoComponent.bind(this);
|
||||
this._renderTodoDropdownError = this._renderTodoDropdownError.bind(this);
|
||||
this._readLists = this._readLists.bind(this);
|
||||
this._ensureSelectedList = this._ensureSelectedList.bind(this);
|
||||
this._createItem = this._createItem.bind(this);
|
||||
this._readItems = this._readItems.bind(this);
|
||||
this._updateItem = this._updateItem.bind(this);
|
||||
this._deleteItem = this._deleteItem.bind(this);
|
||||
this._toggleComplete = this._toggleComplete.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base onInit() implementation to get the persisted properties to initialize data provider.
|
||||
*/
|
||||
public onInit(): Promise<void> {
|
||||
if (UNIT_TEST || DEBUG && this.context.environment.type === EnvironmentType.Local) {
|
||||
this._dataProvider = new MockTodoDataProvider(this.properties);
|
||||
} else {
|
||||
this._dataProvider = new TodoDataProvider(this.properties, this.context);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base render() implementation to render the todo sample web part.
|
||||
*/
|
||||
public render(): void {
|
||||
this._renderTodoComponent();
|
||||
|
||||
this._ensureSelectedList();
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.domElement);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected onPropertyConfigurationComplete(): void {
|
||||
this._shouldGetLists = true;
|
||||
this._dropdownError = undefined;
|
||||
}
|
||||
|
||||
protected onPropertyPaneRendered(): void {
|
||||
if (this._shouldGetLists) {
|
||||
this._shouldGetLists = false;
|
||||
this._dropdownError = undefined;
|
||||
|
||||
this._readLists()
|
||||
.catch((error: Error) => {
|
||||
this._dropdownError = error;
|
||||
})
|
||||
.then(() => {
|
||||
this.configureStart(true /* refresh only */);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
protected onPropertyChange(propertyPath: string, newValue: any): void {
|
||||
if (propertyPath === TodoWebPart.SELECTED_LIST_TITLE_PROPERTY_PATH) {
|
||||
this.properties.selectedList = this._listTitleToList[newValue];
|
||||
this._dataProvider.selectedList = this._listTitleToList[newValue];
|
||||
this._todoComponentData.selectedListItems = [];
|
||||
|
||||
this._readItems();
|
||||
} else if (propertyPath === 'maxNumberOfTasks') {
|
||||
this._dataProvider.maxNumberOfTasks = newValue;
|
||||
|
||||
// This prevents too many unnecessary requests sended when we drag the slider.
|
||||
this._readItems();
|
||||
}
|
||||
|
||||
super.onPropertyChange(propertyPath, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The PropertyPane settings for properties to be configured in PropertyPane.
|
||||
*/
|
||||
protected get propertyPaneSettings(): IPropertyPaneSettings {
|
||||
return {
|
||||
pages: [{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescriptionSetProperties
|
||||
},
|
||||
groups: [{
|
||||
groupFields: [
|
||||
this._dropdown,
|
||||
PropertyPaneToggle('shouldShowCompletedTasks', {
|
||||
label: undefined,
|
||||
onText: strings.PropertyPaneToggleOnTextShowCompletedTasks,
|
||||
offText: strings.PropertyPaneToggleOffTextHideCompletedTasks
|
||||
}),
|
||||
PropertyPaneLabel('todoCheckboxesLabel', {
|
||||
text: strings.PropertyPaneCheckboxGroupLabel
|
||||
}),
|
||||
PropertyPaneCheckbox('shouldShowCreatedBy', {
|
||||
text: strings.PropertyPaneCheckboxCreatedByLabel
|
||||
}),
|
||||
PropertyPaneCheckbox('shouldShowCompletedBy', {
|
||||
text: strings.PropertyPaneCheckboxCompletedByLabel
|
||||
}),
|
||||
PropertyPaneSlider('maxNumberOfTasks', {
|
||||
label: strings.PropertyPaneSliderLabel,
|
||||
min: 1,
|
||||
max: 10
|
||||
})
|
||||
]
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
private _renderTodoComponent(partialData?: ITodoComponentData): void {
|
||||
lodash.extend(this._todoComponentData, partialData);
|
||||
|
||||
ReactDOM.render(
|
||||
<Todo
|
||||
{ ...this.properties }
|
||||
{ ...this._todoComponentData }
|
||||
onCreateItem={ this._createItem }
|
||||
onDeleteItem={ this._deleteItem }
|
||||
onToggleComplete={ this._toggleComplete }
|
||||
/> as React.ReactElement<ITodoProps>,
|
||||
this.domElement
|
||||
);
|
||||
}
|
||||
|
||||
private _renderTodoDropdownError(element: HTMLElement): void {
|
||||
const errorMessage: React.ReactElement<React.HTMLProps<HTMLParagraphElement>> = (
|
||||
<p className={ styles.propertyPaneErrorMessage }>
|
||||
{ this._dropdownError.message }
|
||||
</p>
|
||||
);
|
||||
|
||||
ReactDOM.render(errorMessage, element);
|
||||
}
|
||||
|
||||
private get _dropdown(): IPropertyPaneField<any> { // tslint:disable-line:no-any
|
||||
if (this._dropdownError) {
|
||||
return {
|
||||
type: IPropertyPaneFieldType.Custom,
|
||||
targetProperty: 'todoDropdownError',
|
||||
properties: {
|
||||
onRender: this._renderTodoDropdownError
|
||||
}
|
||||
};
|
||||
} else if (this._dropdownOptions === undefined) {
|
||||
return PropertyPaneLabel('todoDropdownLoadingLabel', {
|
||||
text: strings.PropertyPaneDropdownLoadingLabel
|
||||
});
|
||||
} else {
|
||||
return PropertyPaneDropdown(TodoWebPart.SELECTED_LIST_TITLE_PROPERTY_PATH, {
|
||||
label: strings.PropertyPaneDropdownLabelTasksList,
|
||||
selectedKey: this.properties.selectedList.Title,
|
||||
options: this._dropdownOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the information of all the task lists stored in the current site through data provider.
|
||||
*/
|
||||
private _readLists(): Promise<void> {
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
// Create map from list title to the list
|
||||
this._listTitleToList = {};
|
||||
lists.forEach((list: ITodoTaskList) => {
|
||||
this._listTitleToList[list.Title] = list;
|
||||
});
|
||||
|
||||
// Create dropdown options containing list titles used in property pane
|
||||
this._dropdownOptions = lists.map((list: ITodoTaskList) => {
|
||||
return {
|
||||
key: list.Title,
|
||||
text: list.Title
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no GUID for the selected task list in properties, we will read all task lists on the
|
||||
* site and retrieve the GUID. Usually it will happen at the first time we add todo web part, since
|
||||
* in manifest we could not predict the GUID for the default list added by SharePoint feature XML.
|
||||
* Finally we will read task items of the selected list.
|
||||
*/
|
||||
private _ensureSelectedList(): Promise<void> {
|
||||
if (!this.properties.selectedList.Id) {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
const selectedLists: ITodoTaskList[] = lists.filter((list: ITodoTaskList) => {
|
||||
return list.Title === this.properties.selectedList.Title;
|
||||
});
|
||||
|
||||
this.properties.selectedList = selectedLists[0] || lists[0];
|
||||
this._dataProvider.selectedList = this.properties.selectedList;
|
||||
})
|
||||
.then(this._readItems, (error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
});
|
||||
} else if (!this.renderedOnce) {
|
||||
return this._readItems();
|
||||
} else {
|
||||
// The list id exists and items have been fetched, do nothing.
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item and add it to the list through data provider.
|
||||
*/
|
||||
private _createItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.UpdatingTasks });
|
||||
|
||||
return this._dataProvider.createItem(item.Title)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the list items from the data provider.
|
||||
*/
|
||||
private _readItems(): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
if (this._dataProvider.maxNumberOfTasks > this._todoComponentData.selectedListItems.length) {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readItems()
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.slice(0, this._dataProvider.maxNumberOfTasks)
|
||||
});
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a item in the list through data provider.
|
||||
*/
|
||||
private _updateItem(newItem: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
const updatingIndex: number = lodash.findIndex(this._todoComponentData.selectedListItems,
|
||||
(item: ITodoTask) => item.Id === newItem.Id
|
||||
);
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: update(this._todoComponentData.selectedListItems, { [updatingIndex]: { $set: newItem } })
|
||||
});
|
||||
|
||||
return this._dataProvider.updateItem(newItem)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a item from the list through data provider.
|
||||
*/
|
||||
private _deleteItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.filter((task: ITodoTask) => task.Id !== item.Id)
|
||||
});
|
||||
|
||||
return this._dataProvider.deleteItem(item)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the complete state of an item by.
|
||||
*
|
||||
* Will call updateItem function to update complete state of this item.
|
||||
*/
|
||||
private _toggleComplete(item: ITodoTask): Promise<void> {
|
||||
// Create a new Item in which the PercentComplete value has been changed.
|
||||
const newItem: ITodoTask = update(item, {
|
||||
PercentComplete: { $set: item.PercentComplete >= 1 ? 0 : 1 }
|
||||
});
|
||||
|
||||
return this._updateItem(newItem);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Utils.ts
|
||||
*/
|
||||
|
||||
// Regex that finds { and } so they can be removed on a lookup for string format
|
||||
const FORMAT_ARGS_REGEX: RegExp = /[\{\}]/g;
|
||||
|
||||
// Regex that finds {#} so it can be replaced by the arguments in string format
|
||||
const FORMAT_REGEX: RegExp = /\{\d+\}/g;
|
||||
|
||||
/**
|
||||
* String Format is like C# string format.
|
||||
* Usage Example: "hello {0}!".format("mike") will return "hello mike!"
|
||||
* Calling format on a string with less arguments than specified in the format is invalid
|
||||
* Example "I love {0} every {1}".format("CXP") will result in a Debug Exception.
|
||||
*/
|
||||
/* tslint:disable:no-any no-null-keyword export-name */
|
||||
export function format(s: string, ...values: any[]): string {
|
||||
'use strict';
|
||||
|
||||
const args: any[] = values;
|
||||
// Callback match function
|
||||
function replace_func(match: string): any {
|
||||
// looks up in the args
|
||||
let replacement: any = args[match.replace(FORMAT_ARGS_REGEX, '')];
|
||||
|
||||
// catches undefined in nondebug and null in debug and nondebug
|
||||
if (replacement === null) {
|
||||
replacement = '';
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
return (s.replace(FORMAT_REGEX, replace_func));
|
||||
}
|
||||
/* tslint:enable:no-any no-null-keyword export-name */
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Todo.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
LoadingStatus,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoForm from './TodoForm';
|
||||
import TodoTabs from './TodoTabs';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for Todo component.
|
||||
*/
|
||||
export interface ITodoProps extends ITodoComponentData, ITodoWebPartProps {
|
||||
/**
|
||||
* onCreateItem callback triggered when we add a new item to the tasks list.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onCreateItem: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo component is the top level react component of this web part.
|
||||
* It uses fabric-react component <Spinner>
|
||||
*
|
||||
* Link of Spinner: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/spinner
|
||||
*/
|
||||
export default class Todo extends React.Component<ITodoProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return (
|
||||
<div className={ styles.todo }>
|
||||
<div className={ styles.topRow }>
|
||||
<h2 className={ styles.todoHeading }>{ strings.TodoListTitle }</h2>
|
||||
{ this._workingOnItSpinner }
|
||||
</div>
|
||||
<TodoForm
|
||||
onSubmit={ this.props.onCreateItem }
|
||||
/>
|
||||
<TodoTabs
|
||||
{ ...this.props }
|
||||
items={ this.props.selectedListItems }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
{ this._fetchingSpinner }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get _workingOnItSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.UpdatingTasks
|
||||
? (
|
||||
<div className={ styles.workingOnItSpinner }>
|
||||
<Spinner type={ SpinnerType.normal } />
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private get _fetchingSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.FetchingTasks
|
||||
? (
|
||||
<div className={ styles.fetchingTasksSpinner }>
|
||||
<Spinner
|
||||
type={ SpinnerType.large }
|
||||
label= { strings.FetchingTasksLabel }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoForm.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
ButtonType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormProps {
|
||||
/**
|
||||
* onSubmit callback triggered when the form is submitted.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onSubmit: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormState {
|
||||
/**
|
||||
* inputValue is the react state of input box value.
|
||||
*/
|
||||
inputValue: string;
|
||||
|
||||
/**
|
||||
* The error message will show below the input box if the title filled in is invalid.
|
||||
*/
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The form component used for adding new item to the list.
|
||||
*
|
||||
* It uses fabric-react component <TextField> <Button>
|
||||
* Link of TextField: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/textfield
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
*/
|
||||
export default class TodoForm extends React.Component<ITodoFormProps, ITodoFormState> {
|
||||
private _textField: TextField;
|
||||
|
||||
constructor(props: ITodoFormProps) {
|
||||
super(props);
|
||||
|
||||
this._handleSubmit = this._handleSubmit.bind(this);
|
||||
this._handleChanged = this._handleChanged.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
};
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoFormProps, nextState: ITodoFormState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<form className={ styles.todoForm } onSubmit={ this._handleSubmit }>
|
||||
<TextField
|
||||
className={ styles.textField }
|
||||
value={ this.state.inputValue }
|
||||
ref={(ref: TextField) => this._textField = ref}
|
||||
placeholder={ strings.InputBoxPlaceholder }
|
||||
onBeforeChange={ this._handleChanged }
|
||||
autoComplete='off'
|
||||
errorMessage={ this.state.errorMessage }
|
||||
/>
|
||||
<div className={ styles.addButtonCell }>
|
||||
<Button
|
||||
className={ styles.addButton }
|
||||
buttonType={ ButtonType.primary }
|
||||
ariaLabel={ strings.AddButton }
|
||||
>
|
||||
{ strings.AddButton }
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSubmit(event: React.FormEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this._getTitleErrorMessage(this.state.inputValue)) {
|
||||
this.setState({
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
});
|
||||
|
||||
this.props.onSubmit({
|
||||
Title: this.state.inputValue
|
||||
} as ITodoTask);
|
||||
} else {
|
||||
this.setState({
|
||||
errorMessage: this._getTitleErrorMessage(this.state.inputValue)
|
||||
} as ITodoFormState);
|
||||
|
||||
this._textField.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChanged(newValue: string): void {
|
||||
this.setState({
|
||||
inputValue: newValue
|
||||
} as ITodoFormState);
|
||||
}
|
||||
|
||||
private _getTitleErrorMessage(title: string): string {
|
||||
if (title === '') {
|
||||
return strings.TitleEmptyErrorMessage;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoItem.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Checkbox,
|
||||
Button,
|
||||
ButtonType,
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
DocumentCardActivity,
|
||||
css
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The props for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemProps {
|
||||
/**
|
||||
* The current Todo item to be rendered.
|
||||
*/
|
||||
item: ITodoTask;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of this item is checked or unchecked.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete button of this item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemState {
|
||||
/**
|
||||
* isDeleting indicates whether we are deleting this item.
|
||||
* If the item is being deleted, we will add animation to it.
|
||||
*/
|
||||
isDeleting: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TodoItem component using fabric-react component <FocusZone> <Checkbox> <Button> <DocumentCardActivity>.
|
||||
*
|
||||
* Link of FocusZone: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
* Link of Checkbox: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/checkbox
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
* Link of DocumentCardActivity: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/documentcard
|
||||
*/
|
||||
export default class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {
|
||||
private static ANIMATION_TIMEOUT: number = 200;
|
||||
|
||||
private _animationTimeoutId: number;
|
||||
|
||||
constructor(props: ITodoItemProps) {
|
||||
super(props);
|
||||
|
||||
this._handleToggleChanged = this._handleToggleChanged.bind(this);
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
|
||||
this.state = { isDeleting: false };
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoItemProps, nextState: ITodoItemState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
const className: string = css(
|
||||
styles.todoItem,
|
||||
'ms-Grid',
|
||||
'ms-u-slideDownIn20',
|
||||
{
|
||||
[styles.isCompleted]: this.props.item.PercentComplete >= 1,
|
||||
'ms-u-slideUpOut20': this.state.isDeleting
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='row'
|
||||
className={ className }
|
||||
aria-label={ this._ariaLabel }
|
||||
data-is-focusable={ true }
|
||||
>
|
||||
<FocusZone direction={ FocusZoneDirection.horizontal }>
|
||||
<div className={ css(styles.itemTaskRow, 'ms-Grid-row') }>
|
||||
<Checkbox
|
||||
className={ css(styles.checkbox, 'ms-Grid-col', 'ms-u-sm11') }
|
||||
label={ this.props.item.Title }
|
||||
onChange={ this._handleToggleChanged }
|
||||
checked={ this.props.item.PercentComplete >= 1 }
|
||||
/>
|
||||
<Button
|
||||
className={ css(styles.deleteButton, 'ms-Grid-col', 'ms-u-sm1') }
|
||||
buttonType={ ButtonType.icon }
|
||||
icon='X'
|
||||
onClick={ this._handleClick }
|
||||
ariaLabel={ strings.DeleteItemAriaLabel }
|
||||
rootProps={{
|
||||
title: strings.DeleteItemTitle
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={ css(styles.itemPeopleRow, 'ms-Grid-row') }>
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCreateLabel,
|
||||
this.props.item.Author,
|
||||
this.props.shouldShowCreatedBy
|
||||
)
|
||||
}
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCompleteLabel,
|
||||
this.props.item.Editor,
|
||||
this.props.shouldShowCompletedBy && this.props.item.PercentComplete >= 1
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FocusZone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPersona(
|
||||
activity: string,
|
||||
person: ITodoPerson,
|
||||
shouldShow: boolean
|
||||
): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return person && shouldShow
|
||||
? <DocumentCardActivity
|
||||
activity={ activity }
|
||||
people={[
|
||||
{
|
||||
name: person.Title,
|
||||
profileImageSrc: person.Picture
|
||||
}
|
||||
]}
|
||||
/>
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private get _ariaLabel(): string {
|
||||
const completeState: string = this.props.item.PercentComplete >= 1
|
||||
? strings.TodoItemAriaLabelCheckedState
|
||||
: strings.TodoItemAriaLabelUncheckedState;
|
||||
const titleString: string = format(strings.TodoItemAriaLabelTitle, this.props.item.Title);
|
||||
const createdBy: string = format(strings.TodoItemAriaLabelCreator, this.props.item.Author.Title);
|
||||
const completedBy: string = this.props.item.PercentComplete >= 1
|
||||
? format(strings.TodoItemAriaLabelEditor, this.props.item.Editor && this.props.item.Editor.Title)
|
||||
: '';
|
||||
|
||||
return format(strings.TodoItemAriaLabel, completeState, titleString, createdBy, completedBy);
|
||||
}
|
||||
|
||||
private _handleToggleChanged(ev: React.FormEvent, isChecked: boolean): void {
|
||||
this._handleWithAnimation(this.props.onToggleComplete);
|
||||
}
|
||||
|
||||
private _handleClick(event: React.MouseEvent): void {
|
||||
this._handleWithAnimation(this.props.onDeleteItem);
|
||||
}
|
||||
|
||||
private _handleWithAnimation(callback: (task: ITodoTask) => void): void {
|
||||
this.setState({ isDeleting: true });
|
||||
|
||||
// After ANIMATION_TIMEOUT milliseconds, the animation is finished and
|
||||
// we will delete the item from the task list and remove the animation from it.
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
this._animationTimeoutId = window.setTimeout(
|
||||
() => {
|
||||
this.setState({ isDeleting: false });
|
||||
|
||||
callback(this.props.item);
|
||||
},
|
||||
TodoItem.ANIMATION_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoList.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
IFocusZoneProps,
|
||||
List,
|
||||
KeyCodes,
|
||||
getRTLSafeKeyCode
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoItem, { ITodoItemProps } from './TodoItem';
|
||||
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props of TodoList component.
|
||||
*/
|
||||
export interface ITodoListProps {
|
||||
/**
|
||||
* The Todo items rendered in this List component.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoList component using fabric-react component <List> <FocusZone>
|
||||
*
|
||||
* Link of <List>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/list
|
||||
* Link of <FocusZone>: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
*/
|
||||
export default class TodoList extends React.Component<ITodoListProps, {}> {
|
||||
constructor(props: ITodoListProps) {
|
||||
super(props);
|
||||
|
||||
this._onRenderCell = this._onRenderCell.bind(this);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoListProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IFocusZoneProps> {
|
||||
return (
|
||||
<FocusZone
|
||||
direction={ FocusZoneDirection.vertical }
|
||||
isInnerZoneKeystroke={ (ev: React.KeyboardEvent) => ev.which === getRTLSafeKeyCode(KeyCodes.right) }
|
||||
>
|
||||
<List
|
||||
className={ styles.todoList }
|
||||
items={ this.props.items }
|
||||
onRenderCell={ this._onRenderCell }
|
||||
/>
|
||||
</FocusZone>
|
||||
);
|
||||
}
|
||||
|
||||
private _onRenderCell(item: ITodoTask): React.ReactElement<ITodoItemProps> {
|
||||
return (
|
||||
<TodoItem
|
||||
{ ...this.props }
|
||||
key={ item.Id }
|
||||
item={ item }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoTabs.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
Pivot,
|
||||
PivotItem,
|
||||
IPivotProps,
|
||||
PivotLinkSize
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoList from './TodoList';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The tab type used as the key of the PivotItem.
|
||||
*/
|
||||
enum TabType {
|
||||
/**
|
||||
* The tab showing the active tasks.
|
||||
*/
|
||||
Active,
|
||||
|
||||
/**
|
||||
* The tab showing the completed tasks.
|
||||
*/
|
||||
Completed,
|
||||
|
||||
/**
|
||||
* The tab showing all tasks in the list.
|
||||
*/
|
||||
All
|
||||
}
|
||||
|
||||
/**
|
||||
* Props of TodoTabs component.
|
||||
*/
|
||||
export interface ITodoTabsProps {
|
||||
/**
|
||||
* The list items rendered in TodoTabs.
|
||||
* It will be filtered in each PivotItems by needs.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoTabs component using fabric-react component <Pivot>.
|
||||
*
|
||||
* Link of <Pivot>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/pivot
|
||||
*/
|
||||
export default class TodoTabs extends React.Component<ITodoTabsProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoTabsProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IPivotProps> {
|
||||
const pivotArray: IPivotProps[] = [];
|
||||
|
||||
const activeTasks: ITodoTask[] = [];
|
||||
const completedTasks: ITodoTask[] = [];
|
||||
|
||||
this.props.items.forEach((item: ITodoTask) => {
|
||||
if (item.PercentComplete < 1) {
|
||||
activeTasks.push(item);
|
||||
} else if (this.props.shouldShowCompletedTasks) {
|
||||
completedTasks.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const allTasks: ITodoTask[] = activeTasks.concat(completedTasks);
|
||||
|
||||
if (activeTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(activeTasks, strings.TodoListTabNameActive, TabType.Active)
|
||||
);
|
||||
}
|
||||
|
||||
if (completedTasks.length > 0 && this.props.shouldShowCompletedTasks) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(completedTasks, strings.TodoListTabNameCompleted, TabType.Completed)
|
||||
);
|
||||
}
|
||||
|
||||
if (allTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(allTasks, strings.TodoListTabNameAllTasks, TabType.All)
|
||||
);
|
||||
}
|
||||
|
||||
return pivotArray.length > 0
|
||||
? (
|
||||
<div className={ styles.todoPivot }>
|
||||
<Pivot linkSize={ PivotLinkSize.large }>
|
||||
{ pivotArray }
|
||||
</Pivot>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private _renderPivotItemList(
|
||||
tasks: ITodoTask[],
|
||||
tabName: string,
|
||||
tabKey: TabType
|
||||
): React.ReactElement<IPivotProps> {
|
||||
return (
|
||||
<PivotItem
|
||||
linkText={ format(tabName, tasks.length) }
|
||||
itemKey={ tabKey }
|
||||
key={ tabKey }
|
||||
>
|
||||
<TodoList
|
||||
{ ...this.props }
|
||||
items={ tasks }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
</PivotItem>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* The data provider interface implemented by MockTodoDataProvider and TodoDataProvider.
|
||||
*/
|
||||
export interface ITodoDataProvider {
|
||||
/**
|
||||
* The current selected list.
|
||||
*
|
||||
* It should be always the same with the list in web part properties.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* The max number of tasks show in the Todo list.
|
||||
*
|
||||
* It should be always the sam with the maxNumberOfTasks in web part properties.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
|
||||
/**
|
||||
* readLists will fetch the information of all the lists in current site.
|
||||
*/
|
||||
readLists(): Promise<ITodoTaskList[]>;
|
||||
|
||||
/**
|
||||
* createItem will send REST call to add an item in the current .
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {string} title is the title of item that will be created in current list.
|
||||
*/
|
||||
createItem(title: string): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* readItems will send REST call to fetch the a number up to maxNumberOfTasks of items
|
||||
* in the current.
|
||||
*/
|
||||
readItems(): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* updateItem will send REST call to update(merge) an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemUpdated is the item which will be merged to current list.
|
||||
*/
|
||||
updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* deleteItem will send REST call to remove an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemDeleted is the item which will be deleted in current list.
|
||||
*/
|
||||
deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]>;
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file MockTodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import { ITodoDataProvider } from './ITodoDataProvider';
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* MockTodoDataProvider is used for testing page and unit test page.
|
||||
*/
|
||||
export default class MockTodoDataProvider implements ITodoDataProvider {
|
||||
private _lists: ITodoTaskList[];
|
||||
private _selectedList: ITodoTaskList;
|
||||
|
||||
private _maxNumberOfTasks: number;
|
||||
|
||||
private _itemsStore: { [listTitle: string]: ITodoTask[] };
|
||||
private _idCounter: number;
|
||||
|
||||
public get selectedList(): ITodoTaskList { return this._selectedList; }
|
||||
public set selectedList(value: ITodoTaskList) { this._selectedList = value; }
|
||||
|
||||
public get maxNumberOfTasks(): number { return this._maxNumberOfTasks; }
|
||||
public set maxNumberOfTasks(value: number) { this._maxNumberOfTasks = value; }
|
||||
|
||||
constructor(webPartProps: ITodoWebPartProps) {
|
||||
this._maxNumberOfTasks = webPartProps.maxNumberOfTasks;
|
||||
this.selectedList = webPartProps.selectedList;
|
||||
|
||||
this._idCounter = 0;
|
||||
|
||||
this._lists = [
|
||||
{
|
||||
Title: 'DefaultTodoList',
|
||||
ListItemEntityTypeFullName: '#SP.Data.DefaultTodoListListItem',
|
||||
Id: 'f3c61a58-44a8-4f87-bf5b-03668af148a6'
|
||||
},
|
||||
{
|
||||
Title: 'ListOne',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListOneListItem',
|
||||
Id: '01c78e45-06c2-4384-be9e-caa23912ebda'
|
||||
},
|
||||
{
|
||||
Title: 'ListTwo',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListTwoListItem',
|
||||
Id: '90c704fe-ab47-4d88-8b26-56f9a71e09e6'
|
||||
}
|
||||
];
|
||||
|
||||
this._itemsStore = {
|
||||
'DefaultTodoList': [
|
||||
this._generateItem('Finish Sample Todo web part before dev kitchen', false),
|
||||
this._generateItem('Finish All the work in Todo web part before dev kitchen', false),
|
||||
this._generateItem('Sharepoint API investigation for Todo web part', true),
|
||||
this._generateItem('Bug fixing of Pivot Control', true)
|
||||
],
|
||||
'ListOne': [
|
||||
this._generateItem('Item one in ListOne', false),
|
||||
this._generateItem('Item two in ListOne', false),
|
||||
this._generateItem('Item three in ListOne', true),
|
||||
this._generateItem('Item four in ListOne', true)
|
||||
],
|
||||
'ListTwo': [
|
||||
this._generateItem('Item one in ListTwo', false),
|
||||
this._generateItem('Item two in ListTwo', false),
|
||||
this._generateItem('Item three in ListTwo', true),
|
||||
this._generateItem('Item four in ListTwo', true)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the mock task lists.
|
||||
*/
|
||||
public readLists(): Promise<ITodoTaskList[]> {
|
||||
const lists: ITodoTaskList[] = this._lists;
|
||||
|
||||
return new Promise<ITodoTaskList[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(lists), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock task item using the title.
|
||||
*/
|
||||
public createItem(title: string): Promise<ITodoTask[]> {
|
||||
const newItem: ITodoTask = {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 3,
|
||||
Title: 'Lisa Andrews',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: 0
|
||||
};
|
||||
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].concat(newItem);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read task items from selected list.
|
||||
*/
|
||||
public readItems(): Promise<ITodoTask[]> {
|
||||
const items: ITodoTask[] =
|
||||
this._itemsStore[this._selectedList.Title].slice(0, this._maxNumberOfTasks);
|
||||
|
||||
return new Promise<ITodoTask[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(items), 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the task item.
|
||||
*/
|
||||
public updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]> {
|
||||
const index: number =
|
||||
lodash.findIndex(
|
||||
this._itemsStore[this._selectedList.Title],
|
||||
(item: ITodoTask) => item.Id === itemUpdated.Id
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
const editor: ITodoPerson = itemUpdated.PercentComplete >= 1
|
||||
? {
|
||||
Id: 3,
|
||||
Title: 'Chris Meyer',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
}
|
||||
: undefined;
|
||||
|
||||
this._itemsStore[this._selectedList.Title][index] = itemUpdated;
|
||||
this._itemsStore[this._selectedList.Title][index].Editor = editor;
|
||||
|
||||
return this.readItems();
|
||||
} else {
|
||||
return Promise.reject(new Error(`Item to update doesn't exist.`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the task item.
|
||||
*/
|
||||
public deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]> {
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].filter((item: ITodoTask) => item.Id !== itemDeleted.Id);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
private _generateItem(title: string, isCompleted: boolean): ITodoTask {
|
||||
return {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 1,
|
||||
Title: 'Misty Shock',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
Editor: !isCompleted ? undefined : {
|
||||
Id: 2,
|
||||
Title: 'Burton Guido',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: !isCompleted ? 0 : 1
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import { HttpClient, ODataBatch } from '@microsoft/sp-client-base';
|
||||
import { IWebPartContext } from '@microsoft/sp-client-preview';
|
||||
|
||||
import { ITodoDataProvider } from './ITodoDataProvider';
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoTask,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
|
||||
/**
|
||||
* TodoDataProvider interact with current sharepoint site to accomplish
|
||||
* item operations and data fetching interaction with backend.
|
||||
*/
|
||||
export default class TodoDataProvider implements ITodoDataProvider {
|
||||
private _httpClient: HttpClient;
|
||||
private _webAbsoluteUrl: string;
|
||||
|
||||
private _lists: ITodoTaskList[];
|
||||
|
||||
private _selectedList: ITodoTaskList;
|
||||
private _maxNumberOfTasks: number;
|
||||
private _operationId: number;
|
||||
|
||||
private _listsUrl: string;
|
||||
private _listItemsUrl: string;
|
||||
|
||||
public get selectedList(): ITodoTaskList { return this._selectedList; }
|
||||
public set selectedList(value: ITodoTaskList) {
|
||||
this._selectedList = value;
|
||||
this._listItemsUrl = `${this._listsUrl}(guid'${value.Id}')/items`;
|
||||
}
|
||||
|
||||
public get maxNumberOfTasks(): number { return this._maxNumberOfTasks; }
|
||||
public set maxNumberOfTasks(value: number) { this._maxNumberOfTasks = value; }
|
||||
|
||||
constructor(webPartProps: ITodoWebPartProps, webPartContext: IWebPartContext) {
|
||||
this._httpClient = webPartContext.httpClient as any; // tslint:disable-line:no-any
|
||||
this._webAbsoluteUrl = webPartContext.pageContext.web.absoluteUrl;
|
||||
|
||||
this._operationId = 0;
|
||||
this._maxNumberOfTasks = webPartProps.maxNumberOfTasks;
|
||||
|
||||
this._listsUrl = `${this._webAbsoluteUrl}/_api/web/lists`;
|
||||
if (webPartProps.selectedList.Id) {
|
||||
this.selectedList = webPartProps.selectedList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the Tasks lists in the site.
|
||||
*/
|
||||
public readLists(): Promise<ITodoTaskList[]> {
|
||||
// It is TasksWithTimelineAndHierarchy.
|
||||
const listTemplateId: string = '171';
|
||||
const queryString: string = `?$filter=BaseTemplate eq ${listTemplateId}`;
|
||||
|
||||
return this._httpClient.get(this._listsUrl + queryString)
|
||||
.then(this._checkStatus)
|
||||
.then((response: Response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((json: { value: ITodoTaskList[] }) => {
|
||||
if (json.value.length === 0) {
|
||||
throw new Error(strings.DropdownErrorMessageNoListAvailable);
|
||||
} else {
|
||||
return this._lists = json.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to create item and sync the latest list.
|
||||
*/
|
||||
public createItem(title: string): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._createItem(batch, title),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
public readItems(): Promise<ITodoTask[]> {
|
||||
return this._readItems(this._httpClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to update item and sync the latest list.
|
||||
*/
|
||||
public updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._updateItem(batch, itemUpdated),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to delete item and sync the latest list.
|
||||
*/
|
||||
public deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._deleteItem(batch, itemDeleted),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
private _checkStatus(response: Response): Promise<Response> {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return Promise.resolve(response);
|
||||
} else {
|
||||
return Promise.reject(new Error(JSON.stringify(response)));
|
||||
}
|
||||
}
|
||||
|
||||
private _getPictureUrl(eMail: string): string {
|
||||
return `${this._webAbsoluteUrl}/_layouts/15/userphoto.aspx?size=L&username=${eMail}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to create item in the SharePoint list.
|
||||
*/
|
||||
private _createItem(batch: ODataBatch, title: string): Promise<Response> {
|
||||
const body: {} = {
|
||||
'@data.type': `${this._selectedList.ListItemEntityTypeFullName}`,
|
||||
'Title': title
|
||||
};
|
||||
|
||||
return batch.post(this._listItemsUrl, { body: JSON.stringify(body) }).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the items from SharePoint list using HttpClient or ODataBatch.
|
||||
*
|
||||
* @param {HTTPClient | ODataBatch} requester is the type of the object which send request to the server.
|
||||
* If it is HttpClient, the request will be sent immediately.
|
||||
* If it is ODataBatch, the request will be batched until it is executed.
|
||||
*/
|
||||
private _readItems(requester: HttpClient | ODataBatch): Promise<ITodoTask[]> {
|
||||
const queryString: string = `?$select=Id,Title,PercentComplete,` +
|
||||
`Author/Id,Author/Title,Author/EMail,Editor/Id,Editor/Title,Editor/EMail&` +
|
||||
`$expand=Author,Editor&$top=${this._maxNumberOfTasks}`;
|
||||
const currentId: number = ++this._operationId;
|
||||
|
||||
return requester.get(this._listItemsUrl + queryString)
|
||||
.then(this._checkStatus)
|
||||
.then((response: Response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((json: { value: ITodoTask[] }) => {
|
||||
return json.value.map((task: ITodoTask) => {
|
||||
task.Author.Picture = this._getPictureUrl(task.Author.EMail);
|
||||
task.Editor.Picture = this._getPictureUrl(task.Editor.EMail);
|
||||
return task;
|
||||
});
|
||||
})
|
||||
.then((tasks: ITodoTask[]) => currentId === this._operationId ? tasks : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to update the item in the list.
|
||||
*/
|
||||
private _updateItem(batch: ODataBatch, item: ITodoTask): Promise<Response> {
|
||||
|
||||
const itemUpdatedUrl: string = `${this._listItemsUrl}(${item.Id})`;
|
||||
|
||||
const headers: Headers = new Headers();
|
||||
headers.append('If-Match', '*');
|
||||
|
||||
const body: {} = {
|
||||
'@data.type': `${this._selectedList.ListItemEntityTypeFullName}`,
|
||||
'PercentComplete': item.PercentComplete
|
||||
};
|
||||
|
||||
return batch.fetch(itemUpdatedUrl, {
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
method: 'PATCH' // Use PATCH http method to perform update operation.
|
||||
}).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to delete the item in the list.
|
||||
*/
|
||||
private _deleteItem(batch: ODataBatch, item: ITodoTask): Promise<Response> {
|
||||
const itemDeletedUrl: string = `${this._listItemsUrl}(${item.Id})`;
|
||||
|
||||
const headers: Headers = new Headers();
|
||||
headers.append('If-Match', '*');
|
||||
|
||||
return batch.fetch(itemDeletedUrl, {
|
||||
headers,
|
||||
method: 'DELETE' // Use DELETE http method to perform delete operation.
|
||||
}).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the batch request and return the promise of the last request.
|
||||
*/
|
||||
private _resolveBatch(batch: ODataBatch, promises: Promise<{}>[]): Promise<ITodoTask[]> {
|
||||
return batch.execute()
|
||||
.then(() => Promise.all(promises).then(values => values[values.length - 1]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"TodoListTitle": "Todo List",
|
||||
"TodoListTabNameAllTasks": "All Tasks ({0})",
|
||||
"TodoListTabNameCompleted": "Completed ({0})",
|
||||
"TodoListTabNameActive": "Active ({0})",
|
||||
"AddButton": "Add",
|
||||
"InputBoxPlaceholder": "Add a task...",
|
||||
"FetchingListsLabel": "Fetching your lists...",
|
||||
"FetchingTasksLabel": "Fetching your tasks...",
|
||||
"TodoItemCreateLabel": "Creator",
|
||||
"TodoItemCompleteLabel": "Doer",
|
||||
"TodoItemAriaLabelCheckedState": "This item is completed.",
|
||||
"TodoItemAriaLabelUncheckedState": "This item is active.",
|
||||
"TodoItemAriaLabelTitle": "The title is {0}.",
|
||||
"TodoItemAriaLabelCreator": "This item is created by {0}",
|
||||
"TodoItemAriaLabelEditor": "This item is completed by {0}",
|
||||
"TodoItemAriaLabel": "{0} {1} {2} {3}",
|
||||
"DeleteItemTitle": "Delete this item.",
|
||||
"DeleteItemAriaLabel": "Delete",
|
||||
"WorkingOnSpinnerLabel": "Working on...",
|
||||
"PropertyPaneDescriptionSetProperties": "Set the properties of your todo items.",
|
||||
"PropertyPaneHeadingConfigureSoruce": "Configure source",
|
||||
"PropertyPaneHeadingConfigureDisplay": "Configure display",
|
||||
"PropertyPaneDropdownLoadingLabel": "Loading lists...",
|
||||
"DropdownErrorMessageNoListAvailable": "There is no task list in current SharePoint site. Please check there is at least one task list in site content.",
|
||||
"PropertyPaneDropdownLabelTasksList": "Tasks list",
|
||||
"PropertyPaneToggleOnTextShowCompletedTasks": "Show completed tasks",
|
||||
"PropertyPaneToggleOffTextHideCompletedTasks": "Hide completed tasks",
|
||||
"PropertyPaneCheckboxGroupLabel": "Select display fields to show",
|
||||
"PropertyPaneCheckboxCreatedByLabel": "Created By",
|
||||
"PropertyPaneCheckboxCompletedByLabel": "Completed By",
|
||||
"PropertyPaneSliderLabel": "Show this many tasks at a time",
|
||||
"TitleEmptyErrorMessage": "You can't leave this blank."
|
||||
}
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
declare interface ITodoStrings {
|
||||
TodoListTitle: string;
|
||||
TodoListTabNameAllTasks: string;
|
||||
TodoListTabNameCompleted: string;
|
||||
TodoListTabNameActive: string;
|
||||
AddButton: string;
|
||||
InputBoxPlaceholder: string;
|
||||
FetchingListsLabel: string;
|
||||
FetchingTasksLabel: string;
|
||||
TodoItemCreateLabel: string;
|
||||
TodoItemCompleteLabel: string;
|
||||
TodoItemAriaLabelCheckedState: string;
|
||||
TodoItemAriaLabelUncheckedState: string;
|
||||
TodoItemAriaLabelTitle: string;
|
||||
TodoItemAriaLabelCreator: string;
|
||||
TodoItemAriaLabelEditor: string;
|
||||
TodoItemAriaLabel: string;
|
||||
DeleteItemTitle: string;
|
||||
DeleteItemAriaLabel: string;
|
||||
WorkingOnSpinnerLabel: string;
|
||||
PropertyPaneDescriptionSetProperties: string;
|
||||
PropertyPaneHeadingConfigureSoruce: string;
|
||||
PropertyPaneHeadingConfigureDisplay: string;
|
||||
PropertyPaneDropdownLoadingLabel: string;
|
||||
DropdownErrorMessageNoListAvailable: string;
|
||||
PropertyPaneDropdownLabelTasksList: string;
|
||||
PropertyPaneToggleOnTextShowCompletedTasks: string;
|
||||
PropertyPaneToggleOffTextHideCompletedTasks: string;
|
||||
PropertyPaneCheckboxGroupLabel: string;
|
||||
PropertyPaneCheckboxCreatedByLabel: string;
|
||||
PropertyPaneCheckboxCompletedByLabel: string;
|
||||
PropertyPaneSliderLabel: string;
|
||||
TitleEmptyErrorMessage: string;
|
||||
}
|
||||
|
||||
declare module 'todoStrings' {
|
||||
const strings: ITodoStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
@import "~office-ui-fabric/dist/sass/Fabric.Common";
|
||||
|
||||
.todo {
|
||||
padding: 28px 40px;
|
||||
|
||||
.topRow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.todoHeading {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.todoError {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
}
|
||||
|
||||
.todoPivot {
|
||||
padding-top: 24px;
|
||||
|
||||
:global li.ms-Pivot-link{
|
||||
margin-right: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.todoForm {
|
||||
display: table;
|
||||
|
||||
.textField {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
.addButtonCell {
|
||||
display: table-cell;
|
||||
|
||||
.addButton {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.todoList {
|
||||
margin-top: 20px;
|
||||
border-top: 1px lightgrey solid;
|
||||
|
||||
:global .ms-Icon {
|
||||
font-family: 'sp-MDL2';
|
||||
}
|
||||
:global .ms-Icon--X {
|
||||
transform: rotate(45deg);
|
||||
|
||||
&:before {
|
||||
content: "\E710";
|
||||
}
|
||||
}
|
||||
|
||||
.todoItem {
|
||||
border: 1px lightgrey solid;
|
||||
border-top: none;
|
||||
|
||||
.itemTaskRow {
|
||||
padding: 16px 20px 8px 20px;
|
||||
|
||||
.deleteButton {
|
||||
:global .ms-Button-icon {
|
||||
float: right;
|
||||
font-size: 21px;
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
color: $ms-color-themePrimary;
|
||||
}
|
||||
}
|
||||
.checkbox {
|
||||
:global .ms-ChoiceField-field {
|
||||
margin: 0px;
|
||||
|
||||
&:after {
|
||||
top: 6px;
|
||||
}
|
||||
&:before {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
:global .ms-Label {
|
||||
color: $ms-color-neutralPrimary;
|
||||
font-size: 22px;
|
||||
padding: 0 0 0 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.itemPeopleRow {
|
||||
display: flex;
|
||||
padding: 8px 56px 16px 56px;
|
||||
|
||||
:global .ms-DocumentCardActivity {
|
||||
@include ms-u-sm4;
|
||||
padding-left: 0px;
|
||||
|
||||
.ms-DocumentCardActivity-details {
|
||||
left: 33px;
|
||||
}
|
||||
.ms-DocumentCardActivity-name {
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.isHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isCompleted {
|
||||
border-top: 1px white solid;
|
||||
border-left: 1px $ms-color-neutralLighter solid;
|
||||
border-right: 1px $ms-color-neutralLighter solid;
|
||||
@include ms-bgColor-neutralLighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workingOnItSpinner {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
height: 33.33333%;
|
||||
line-height: 1.5em;
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
.fetchingTasksSpinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:640px) {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.propertyPaneErrorMessage {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
|
||||
padding: 20px 0;
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoWebPartProps.tsx
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for the Todo web part properties.
|
||||
*/
|
||||
export interface ITodoWebPartProps {
|
||||
/**
|
||||
* The current selected SharePoint tasks list.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* The max number of tasks showing in todo web part.
|
||||
*
|
||||
* The number of list items shown in the list will not exceed this number, and it also limits
|
||||
* the number of return items when it sends request to the sever.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for the data used to render the todo component.
|
||||
*/
|
||||
export interface ITodoComponentData {
|
||||
/**
|
||||
* The selected list items rendered in Todo Component.
|
||||
*/
|
||||
selectedListItems?: ITodoTask[];
|
||||
|
||||
/**
|
||||
* The loading status of the list items.
|
||||
*/
|
||||
loadingStatus?: LoadingStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface of user data model related to this web part.
|
||||
*/
|
||||
export interface ITodoPerson {
|
||||
/**
|
||||
* The ID of the person which used to identify the user and fetch the user data from server.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The name of this person.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The url which representing the avator url of this person.
|
||||
*/
|
||||
Picture: string;
|
||||
|
||||
/**
|
||||
* The email address of this person.
|
||||
*
|
||||
* This field is only used in data provider to construct url for fetch user avator.
|
||||
*/
|
||||
EMail: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface of data modal for Todo task.
|
||||
*/
|
||||
export interface ITodoTask {
|
||||
/**
|
||||
* The ID of the todo item.
|
||||
*/
|
||||
Id: number;
|
||||
|
||||
/**
|
||||
* The title of the todo item.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The percent of the task that is completed.
|
||||
* In todo web part we use 0 to indicate task uncompleted and 1 to indicate task completed.
|
||||
*/
|
||||
PercentComplete: number;
|
||||
|
||||
/**
|
||||
* The person who created this todo task.
|
||||
*/
|
||||
Author: ITodoPerson;
|
||||
|
||||
/**
|
||||
* The person who marked this todo item as completed.
|
||||
*
|
||||
* Editor is the last person who performed editting operation.
|
||||
* In Todo web part, the only editting operation that can be performed by user is toggling complete.
|
||||
* This field is optional because it is not required for incomplete tasks.
|
||||
*/
|
||||
Editor?: ITodoPerson;
|
||||
}
|
||||
|
||||
/**
|
||||
* ITodoTaskList contains title and entity type full name which used in REST calls.
|
||||
*/
|
||||
export interface ITodoTaskList {
|
||||
/**
|
||||
* The title of the todo task list.
|
||||
*/
|
||||
Title: string;
|
||||
|
||||
/**
|
||||
* The ListItemEntityTypeFullName property of the list.
|
||||
*/
|
||||
ListItemEntityTypeFullName?: string;
|
||||
|
||||
/**
|
||||
* The Id property of the list.
|
||||
*/
|
||||
Id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the loading status for requesting for the items from SharePoint List.
|
||||
*/
|
||||
export enum LoadingStatus {
|
||||
/**
|
||||
* We are not loading anything.
|
||||
*/
|
||||
None,
|
||||
|
||||
/**
|
||||
* We are fetching the tasks items (Read items).
|
||||
*/
|
||||
FetchingTasks,
|
||||
|
||||
/**
|
||||
* We are updating the tasks list (Create, Update, or Delete the item).
|
||||
*/
|
||||
UpdatingTasks
|
||||
}
|
||||
|
||||
/**
|
||||
* ItemOperationCallback is the type of callback that handle the operation on item.
|
||||
* It is used for creating, updating and deleting callbacks.
|
||||
*
|
||||
* @param {ITodoTask} item is the Todo task item that will be either created, updated, or deleted.
|
||||
*/
|
||||
export type ItemOperationCallback = (item: ITodoTask) => void;
|
|
@ -0,0 +1,12 @@
|
|||
## Todo sample web part step 4
|
||||
|
||||
In this step, we add the SharePoint feature xml to the todo-webpart-sample project. It will create a
|
||||
default Tasks list (DefaultTodoList) when we deploy the solution package.
|
||||
|
||||
## How to build
|
||||
|
||||
```bash
|
||||
npm install
|
||||
gulp serve
|
||||
gulp package-solution
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
|
||||
|
||||
"componentType": "WebPart",
|
||||
"version": "1.0.0",
|
||||
"manifestVersion": 2,
|
||||
"id": "fedbce0f-bed9-453c-b125-60654feffc0f",
|
||||
"preconfiguredEntries": [
|
||||
{
|
||||
"groupId": "fedbce0f-bed9-453c-b125-60654feffc0f",
|
||||
"group": {
|
||||
"default": "Under Development"
|
||||
},
|
||||
"title": {
|
||||
"default": "Todo step 4"
|
||||
},
|
||||
"description": {
|
||||
"default": "Todo sample webpart step 4"
|
||||
},
|
||||
"officeFabricIconFontName": "BulletedList",
|
||||
"properties": {
|
||||
"selectedList": {
|
||||
"Title": "DefaultTodoList"
|
||||
},
|
||||
"shouldShowCompletedTasks": true,
|
||||
"shouldShowCompletedBy": true,
|
||||
"shouldShowCreatedBy": true,
|
||||
"maxNumberOfTasks": 10
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoWebPart.tsx
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
import update = require('react-addons-update');
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
|
||||
import { EnvironmentType } from '@microsoft/sp-client-base';
|
||||
|
||||
import {
|
||||
BaseClientSideWebPart,
|
||||
IWebPartContext,
|
||||
IPropertyPaneSettings,
|
||||
IPropertyPaneField,
|
||||
IPropertyPaneFieldType,
|
||||
IPropertyPaneDropdownOption,
|
||||
PropertyPaneDropdown,
|
||||
PropertyPaneLabel,
|
||||
PropertyPaneCheckbox,
|
||||
PropertyPaneSlider,
|
||||
PropertyPaneToggle
|
||||
} from '@microsoft/sp-client-preview';
|
||||
|
||||
import { ITodoDataProvider } from './dataProviders/ITodoDataProvider';
|
||||
import MockTodoDataProvider from './dataProviders/MockTodoDataProvider';
|
||||
import TodoDataProvider from './dataProviders/TodoDataProvider';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
ITodoTaskList,
|
||||
ITodoTask,
|
||||
LoadingStatus
|
||||
} from './ITodoWebPartProps';
|
||||
|
||||
import Todo, { ITodoProps } from './components/Todo';
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from './style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* This is the client-side todo sample web part built using the SharePoint Framework.
|
||||
* It could interact with tasks lists in SharePoint site.
|
||||
*
|
||||
* Find out more docs and tutorials at:
|
||||
* https://github.com/SharePoint/sp-dev-docs/wiki
|
||||
*/
|
||||
export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps> {
|
||||
/**
|
||||
* This is the path for the list item title property in this.properties.
|
||||
* It will be provided to property pane to update the corresponding title field in the list item.
|
||||
*/
|
||||
private static SELECTED_LIST_TITLE_PROPERTY_PATH: string = 'selectedList.Title';
|
||||
|
||||
private _dataProvider: ITodoDataProvider;
|
||||
|
||||
private _shouldGetLists: boolean;
|
||||
private _listTitleToList: { [title: string]: ITodoTaskList };
|
||||
|
||||
private _dropdownError: Error;
|
||||
private _dropdownOptions: IPropertyPaneDropdownOption[];
|
||||
|
||||
private _todoComponentData: ITodoComponentData;
|
||||
|
||||
constructor(context: IWebPartContext) {
|
||||
super(context);
|
||||
|
||||
this._shouldGetLists = true;
|
||||
|
||||
this._todoComponentData = {
|
||||
selectedListItems: [],
|
||||
loadingStatus: LoadingStatus.None
|
||||
};
|
||||
|
||||
this._renderTodoComponent = this._renderTodoComponent.bind(this);
|
||||
this._renderTodoDropdownError = this._renderTodoDropdownError.bind(this);
|
||||
this._readLists = this._readLists.bind(this);
|
||||
this._ensureSelectedList = this._ensureSelectedList.bind(this);
|
||||
this._createItem = this._createItem.bind(this);
|
||||
this._readItems = this._readItems.bind(this);
|
||||
this._updateItem = this._updateItem.bind(this);
|
||||
this._deleteItem = this._deleteItem.bind(this);
|
||||
this._toggleComplete = this._toggleComplete.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base onInit() implementation to get the persisted properties to initialize data provider.
|
||||
*/
|
||||
public onInit(): Promise<void> {
|
||||
if (UNIT_TEST || DEBUG && this.context.environment.type === EnvironmentType.Local) {
|
||||
this._dataProvider = new MockTodoDataProvider(this.properties);
|
||||
} else {
|
||||
this._dataProvider = new TodoDataProvider(this.properties, this.context);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the base render() implementation to render the todo sample web part.
|
||||
*/
|
||||
public render(): void {
|
||||
this._renderTodoComponent();
|
||||
|
||||
this._ensureSelectedList();
|
||||
}
|
||||
|
||||
protected dispose(): void {
|
||||
ReactDOM.unmountComponentAtNode(this.domElement);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected onPropertyConfigurationComplete(): void {
|
||||
this._shouldGetLists = true;
|
||||
this._dropdownError = undefined;
|
||||
}
|
||||
|
||||
protected onPropertyPaneRendered(): void {
|
||||
if (this._shouldGetLists) {
|
||||
this._shouldGetLists = false;
|
||||
this._dropdownError = undefined;
|
||||
|
||||
this._readLists()
|
||||
.catch((error: Error) => {
|
||||
this._dropdownError = error;
|
||||
})
|
||||
.then(() => {
|
||||
this.configureStart(true /* refresh only */);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-any
|
||||
protected onPropertyChange(propertyPath: string, newValue: any): void {
|
||||
if (propertyPath === TodoWebPart.SELECTED_LIST_TITLE_PROPERTY_PATH) {
|
||||
this.properties.selectedList = this._listTitleToList[newValue];
|
||||
this._dataProvider.selectedList = this._listTitleToList[newValue];
|
||||
this._todoComponentData.selectedListItems = [];
|
||||
|
||||
this._readItems();
|
||||
} else if (propertyPath === 'maxNumberOfTasks') {
|
||||
this._dataProvider.maxNumberOfTasks = newValue;
|
||||
|
||||
// This prevents too many unnecessary requests sended when we drag the slider.
|
||||
this._readItems();
|
||||
}
|
||||
|
||||
super.onPropertyChange(propertyPath, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* The PropertyPane settings for properties to be configured in PropertyPane.
|
||||
*/
|
||||
protected get propertyPaneSettings(): IPropertyPaneSettings {
|
||||
return {
|
||||
pages: [{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescriptionSetProperties
|
||||
},
|
||||
groups: [{
|
||||
groupFields: [
|
||||
this._dropdown,
|
||||
PropertyPaneToggle('shouldShowCompletedTasks', {
|
||||
label: undefined,
|
||||
onText: strings.PropertyPaneToggleOnTextShowCompletedTasks,
|
||||
offText: strings.PropertyPaneToggleOffTextHideCompletedTasks
|
||||
}),
|
||||
PropertyPaneLabel('todoCheckboxesLabel', {
|
||||
text: strings.PropertyPaneCheckboxGroupLabel
|
||||
}),
|
||||
PropertyPaneCheckbox('shouldShowCreatedBy', {
|
||||
text: strings.PropertyPaneCheckboxCreatedByLabel
|
||||
}),
|
||||
PropertyPaneCheckbox('shouldShowCompletedBy', {
|
||||
text: strings.PropertyPaneCheckboxCompletedByLabel
|
||||
}),
|
||||
PropertyPaneSlider('maxNumberOfTasks', {
|
||||
label: strings.PropertyPaneSliderLabel,
|
||||
min: 1,
|
||||
max: 10
|
||||
})
|
||||
]
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
private _renderTodoComponent(partialData?: ITodoComponentData): void {
|
||||
lodash.extend(this._todoComponentData, partialData);
|
||||
|
||||
ReactDOM.render(
|
||||
<Todo
|
||||
{ ...this.properties }
|
||||
{ ...this._todoComponentData }
|
||||
onCreateItem={ this._createItem }
|
||||
onDeleteItem={ this._deleteItem }
|
||||
onToggleComplete={ this._toggleComplete }
|
||||
/> as React.ReactElement<ITodoProps>,
|
||||
this.domElement
|
||||
);
|
||||
}
|
||||
|
||||
private _renderTodoDropdownError(element: HTMLElement): void {
|
||||
const errorMessage: React.ReactElement<React.HTMLProps<HTMLParagraphElement>> = (
|
||||
<p className={ styles.propertyPaneErrorMessage }>
|
||||
{ this._dropdownError.message }
|
||||
</p>
|
||||
);
|
||||
|
||||
ReactDOM.render(errorMessage, element);
|
||||
}
|
||||
|
||||
private get _dropdown(): IPropertyPaneField<any> { // tslint:disable-line:no-any
|
||||
if (this._dropdownError) {
|
||||
return {
|
||||
type: IPropertyPaneFieldType.Custom,
|
||||
targetProperty: 'todoDropdownError',
|
||||
properties: {
|
||||
onRender: this._renderTodoDropdownError
|
||||
}
|
||||
};
|
||||
} else if (this._dropdownOptions === undefined) {
|
||||
return PropertyPaneLabel('todoDropdownLoadingLabel', {
|
||||
text: strings.PropertyPaneDropdownLoadingLabel
|
||||
});
|
||||
} else {
|
||||
return PropertyPaneDropdown(TodoWebPart.SELECTED_LIST_TITLE_PROPERTY_PATH, {
|
||||
label: strings.PropertyPaneDropdownLabelTasksList,
|
||||
selectedKey: this.properties.selectedList.Title,
|
||||
options: this._dropdownOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the information of all the task lists stored in the current site through data provider.
|
||||
*/
|
||||
private _readLists(): Promise<void> {
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
// Create map from list title to the list
|
||||
this._listTitleToList = {};
|
||||
lists.forEach((list: ITodoTaskList) => {
|
||||
this._listTitleToList[list.Title] = list;
|
||||
});
|
||||
|
||||
// Create dropdown options containing list titles used in property pane
|
||||
this._dropdownOptions = lists.map((list: ITodoTaskList) => {
|
||||
return {
|
||||
key: list.Title,
|
||||
text: list.Title
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no GUID for the selected task list in properties, we will read all task lists on the
|
||||
* site and retrieve the GUID. Usually it will happen at the first time we add todo web part, since
|
||||
* in manifest we could not predict the GUID for the default list added by SharePoint feature XML.
|
||||
* Finally we will read task items of the selected list.
|
||||
*/
|
||||
private _ensureSelectedList(): Promise<void> {
|
||||
if (!this.properties.selectedList.Id) {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readLists()
|
||||
.then((lists: ITodoTaskList[]) => {
|
||||
const selectedLists: ITodoTaskList[] = lists.filter((list: ITodoTaskList) => {
|
||||
return list.Title === this.properties.selectedList.Title;
|
||||
});
|
||||
|
||||
this.properties.selectedList = selectedLists[0] || lists[0];
|
||||
this._dataProvider.selectedList = this.properties.selectedList;
|
||||
})
|
||||
.then(this._readItems, (error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
});
|
||||
} else if (!this.renderedOnce) {
|
||||
return this._readItems();
|
||||
} else {
|
||||
// The list id exists and items have been fetched, do nothing.
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new item and add it to the list through data provider.
|
||||
*/
|
||||
private _createItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.UpdatingTasks });
|
||||
|
||||
return this._dataProvider.createItem(item.Title)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the list items from the data provider.
|
||||
*/
|
||||
private _readItems(): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
if (this._dataProvider.maxNumberOfTasks > this._todoComponentData.selectedListItems.length) {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.FetchingTasks });
|
||||
|
||||
return this._dataProvider.readItems()
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({
|
||||
selectedListItems: items,
|
||||
loadingStatus: LoadingStatus.None
|
||||
}),
|
||||
(error: Error) => {
|
||||
this._renderTodoComponent({ loadingStatus: LoadingStatus.None });
|
||||
this.renderError(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.slice(0, this._dataProvider.maxNumberOfTasks)
|
||||
});
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a item in the list through data provider.
|
||||
*/
|
||||
private _updateItem(newItem: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
const updatingIndex: number = lodash.findIndex(this._todoComponentData.selectedListItems,
|
||||
(item: ITodoTask) => item.Id === newItem.Id
|
||||
);
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: update(this._todoComponentData.selectedListItems, { [updatingIndex]: { $set: newItem } })
|
||||
});
|
||||
|
||||
return this._dataProvider.updateItem(newItem)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a item from the list through data provider.
|
||||
*/
|
||||
private _deleteItem(item: ITodoTask): Promise<void> {
|
||||
this.clearError();
|
||||
|
||||
this._renderTodoComponent({
|
||||
selectedListItems: this._todoComponentData.selectedListItems.filter((task: ITodoTask) => task.Id !== item.Id)
|
||||
});
|
||||
|
||||
return this._dataProvider.deleteItem(item)
|
||||
.then(
|
||||
(items: ITodoTask[]) => items && this._renderTodoComponent({ selectedListItems: items }),
|
||||
this.renderError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the complete state of an item by.
|
||||
*
|
||||
* Will call updateItem function to update complete state of this item.
|
||||
*/
|
||||
private _toggleComplete(item: ITodoTask): Promise<void> {
|
||||
// Create a new Item in which the PercentComplete value has been changed.
|
||||
const newItem: ITodoTask = update(item, {
|
||||
PercentComplete: { $set: item.PercentComplete >= 1 ? 0 : 1 }
|
||||
});
|
||||
|
||||
return this._updateItem(newItem);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Utils.ts
|
||||
*/
|
||||
|
||||
// Regex that finds { and } so they can be removed on a lookup for string format
|
||||
const FORMAT_ARGS_REGEX: RegExp = /[\{\}]/g;
|
||||
|
||||
// Regex that finds {#} so it can be replaced by the arguments in string format
|
||||
const FORMAT_REGEX: RegExp = /\{\d+\}/g;
|
||||
|
||||
/**
|
||||
* String Format is like C# string format.
|
||||
* Usage Example: "hello {0}!".format("mike") will return "hello mike!"
|
||||
* Calling format on a string with less arguments than specified in the format is invalid
|
||||
* Example "I love {0} every {1}".format("CXP") will result in a Debug Exception.
|
||||
*/
|
||||
/* tslint:disable:no-any no-null-keyword export-name */
|
||||
export function format(s: string, ...values: any[]): string {
|
||||
'use strict';
|
||||
|
||||
const args: any[] = values;
|
||||
// Callback match function
|
||||
function replace_func(match: string): any {
|
||||
// looks up in the args
|
||||
let replacement: any = args[match.replace(FORMAT_ARGS_REGEX, '')];
|
||||
|
||||
// catches undefined in nondebug and null in debug and nondebug
|
||||
if (replacement === null) {
|
||||
replacement = '';
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
return (s.replace(FORMAT_REGEX, replace_func));
|
||||
}
|
||||
/* tslint:enable:no-any no-null-keyword export-name */
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file Todo.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoComponentData,
|
||||
LoadingStatus,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoForm from './TodoForm';
|
||||
import TodoTabs from './TodoTabs';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for Todo component.
|
||||
*/
|
||||
export interface ITodoProps extends ITodoComponentData, ITodoWebPartProps {
|
||||
/**
|
||||
* onCreateItem callback triggered when we add a new item to the tasks list.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onCreateItem: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo component is the top level react component of this web part.
|
||||
* It uses fabric-react component <Spinner>
|
||||
*
|
||||
* Link of Spinner: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/spinner
|
||||
*/
|
||||
export default class Todo extends React.Component<ITodoProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return (
|
||||
<div className={ styles.todo }>
|
||||
<div className={ styles.topRow }>
|
||||
<h2 className={ styles.todoHeading }>{ strings.TodoListTitle }</h2>
|
||||
{ this._workingOnItSpinner }
|
||||
</div>
|
||||
<TodoForm
|
||||
onSubmit={ this.props.onCreateItem }
|
||||
/>
|
||||
<TodoTabs
|
||||
{ ...this.props }
|
||||
items={ this.props.selectedListItems }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
{ this._fetchingSpinner }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private get _workingOnItSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.UpdatingTasks
|
||||
? (
|
||||
<div className={ styles.workingOnItSpinner }>
|
||||
<Spinner type={ SpinnerType.normal } />
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private get _fetchingSpinner(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return this.props.loadingStatus === LoadingStatus.FetchingTasks
|
||||
? (
|
||||
<div className={ styles.fetchingTasksSpinner }>
|
||||
<Spinner
|
||||
type={ SpinnerType.large }
|
||||
label= { strings.FetchingTasksLabel }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoForm.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
ButtonType
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormProps {
|
||||
/**
|
||||
* onSubmit callback triggered when the form is submitted.
|
||||
* Either triggered by clicking on add button or pressed Enter key in input field.
|
||||
*/
|
||||
onSubmit: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoForm component.
|
||||
*/
|
||||
export interface ITodoFormState {
|
||||
/**
|
||||
* inputValue is the react state of input box value.
|
||||
*/
|
||||
inputValue: string;
|
||||
|
||||
/**
|
||||
* The error message will show below the input box if the title filled in is invalid.
|
||||
*/
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The form component used for adding new item to the list.
|
||||
*
|
||||
* It uses fabric-react component <TextField> <Button>
|
||||
* Link of TextField: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/textfield
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
*/
|
||||
export default class TodoForm extends React.Component<ITodoFormProps, ITodoFormState> {
|
||||
private _textField: TextField;
|
||||
|
||||
constructor(props: ITodoFormProps) {
|
||||
super(props);
|
||||
|
||||
this._handleSubmit = this._handleSubmit.bind(this);
|
||||
this._handleChanged = this._handleChanged.bind(this);
|
||||
|
||||
this.state = {
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
};
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoFormProps, nextState: ITodoFormState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<form className={ styles.todoForm } onSubmit={ this._handleSubmit }>
|
||||
<TextField
|
||||
className={ styles.textField }
|
||||
value={ this.state.inputValue }
|
||||
ref={(ref: TextField) => this._textField = ref}
|
||||
placeholder={ strings.InputBoxPlaceholder }
|
||||
onBeforeChange={ this._handleChanged }
|
||||
autoComplete='off'
|
||||
errorMessage={ this.state.errorMessage }
|
||||
/>
|
||||
<div className={ styles.addButtonCell }>
|
||||
<Button
|
||||
className={ styles.addButton }
|
||||
buttonType={ ButtonType.primary }
|
||||
ariaLabel={ strings.AddButton }
|
||||
>
|
||||
{ strings.AddButton }
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSubmit(event: React.FormEvent): void {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this._getTitleErrorMessage(this.state.inputValue)) {
|
||||
this.setState({
|
||||
inputValue: '',
|
||||
errorMessage: ''
|
||||
});
|
||||
|
||||
this.props.onSubmit({
|
||||
Title: this.state.inputValue
|
||||
} as ITodoTask);
|
||||
} else {
|
||||
this.setState({
|
||||
errorMessage: this._getTitleErrorMessage(this.state.inputValue)
|
||||
} as ITodoFormState);
|
||||
|
||||
this._textField.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChanged(newValue: string): void {
|
||||
this.setState({
|
||||
inputValue: newValue
|
||||
} as ITodoFormState);
|
||||
}
|
||||
|
||||
private _getTitleErrorMessage(title: string): string {
|
||||
if (title === '') {
|
||||
return strings.TitleEmptyErrorMessage;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoItem.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
Checkbox,
|
||||
Button,
|
||||
ButtonType,
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
DocumentCardActivity,
|
||||
css
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The props for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemProps {
|
||||
/**
|
||||
* The current Todo item to be rendered.
|
||||
*/
|
||||
item: ITodoTask;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of this item is checked or unchecked.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete button of this item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* States for TodoItem component.
|
||||
*/
|
||||
export interface ITodoItemState {
|
||||
/**
|
||||
* isDeleting indicates whether we are deleting this item.
|
||||
* If the item is being deleted, we will add animation to it.
|
||||
*/
|
||||
isDeleting: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* TodoItem component using fabric-react component <FocusZone> <Checkbox> <Button> <DocumentCardActivity>.
|
||||
*
|
||||
* Link of FocusZone: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
* Link of Checkbox: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/checkbox
|
||||
* Link of Button: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/button
|
||||
* Link of DocumentCardActivity: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/documentcard
|
||||
*/
|
||||
export default class TodoItem extends React.Component<ITodoItemProps, ITodoItemState> {
|
||||
private static ANIMATION_TIMEOUT: number = 200;
|
||||
|
||||
private _animationTimeoutId: number;
|
||||
|
||||
constructor(props: ITodoItemProps) {
|
||||
super(props);
|
||||
|
||||
this._handleToggleChanged = this._handleToggleChanged.bind(this);
|
||||
this._handleClick = this._handleClick.bind(this);
|
||||
|
||||
this.state = { isDeleting: false };
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoItemProps, nextState: ITodoItemState): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
const className: string = css(
|
||||
styles.todoItem,
|
||||
'ms-Grid',
|
||||
'ms-u-slideDownIn20',
|
||||
{
|
||||
[styles.isCompleted]: this.props.item.PercentComplete >= 1,
|
||||
'ms-u-slideUpOut20': this.state.isDeleting
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
role='row'
|
||||
className={ className }
|
||||
aria-label={ this._ariaLabel }
|
||||
data-is-focusable={ true }
|
||||
>
|
||||
<FocusZone direction={ FocusZoneDirection.horizontal }>
|
||||
<div className={ css(styles.itemTaskRow, 'ms-Grid-row') }>
|
||||
<Checkbox
|
||||
className={ css(styles.checkbox, 'ms-Grid-col', 'ms-u-sm11') }
|
||||
label={ this.props.item.Title }
|
||||
onChange={ this._handleToggleChanged }
|
||||
checked={ this.props.item.PercentComplete >= 1 }
|
||||
/>
|
||||
<Button
|
||||
className={ css(styles.deleteButton, 'ms-Grid-col', 'ms-u-sm1') }
|
||||
buttonType={ ButtonType.icon }
|
||||
icon='X'
|
||||
onClick={ this._handleClick }
|
||||
ariaLabel={ strings.DeleteItemAriaLabel }
|
||||
rootProps={{
|
||||
title: strings.DeleteItemTitle
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={ css(styles.itemPeopleRow, 'ms-Grid-row') }>
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCreateLabel,
|
||||
this.props.item.Author,
|
||||
this.props.shouldShowCreatedBy
|
||||
)
|
||||
}
|
||||
{
|
||||
this._renderPersona(
|
||||
strings.TodoItemCompleteLabel,
|
||||
this.props.item.Editor,
|
||||
this.props.shouldShowCompletedBy && this.props.item.PercentComplete >= 1
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FocusZone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPersona(
|
||||
activity: string,
|
||||
person: ITodoPerson,
|
||||
shouldShow: boolean
|
||||
): React.ReactElement<React.HTMLProps<HTMLDivElement>> {
|
||||
return person && shouldShow
|
||||
? <DocumentCardActivity
|
||||
activity={ activity }
|
||||
people={[
|
||||
{
|
||||
name: person.Title,
|
||||
profileImageSrc: person.Picture
|
||||
}
|
||||
]}
|
||||
/>
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private get _ariaLabel(): string {
|
||||
const completeState: string = this.props.item.PercentComplete >= 1
|
||||
? strings.TodoItemAriaLabelCheckedState
|
||||
: strings.TodoItemAriaLabelUncheckedState;
|
||||
const titleString: string = format(strings.TodoItemAriaLabelTitle, this.props.item.Title);
|
||||
const createdBy: string = format(strings.TodoItemAriaLabelCreator, this.props.item.Author.Title);
|
||||
const completedBy: string = this.props.item.PercentComplete >= 1
|
||||
? format(strings.TodoItemAriaLabelEditor, this.props.item.Editor && this.props.item.Editor.Title)
|
||||
: '';
|
||||
|
||||
return format(strings.TodoItemAriaLabel, completeState, titleString, createdBy, completedBy);
|
||||
}
|
||||
|
||||
private _handleToggleChanged(ev: React.FormEvent, isChecked: boolean): void {
|
||||
this._handleWithAnimation(this.props.onToggleComplete);
|
||||
}
|
||||
|
||||
private _handleClick(event: React.MouseEvent): void {
|
||||
this._handleWithAnimation(this.props.onDeleteItem);
|
||||
}
|
||||
|
||||
private _handleWithAnimation(callback: (task: ITodoTask) => void): void {
|
||||
this.setState({ isDeleting: true });
|
||||
|
||||
// After ANIMATION_TIMEOUT milliseconds, the animation is finished and
|
||||
// we will delete the item from the task list and remove the animation from it.
|
||||
window.clearTimeout(this._animationTimeoutId);
|
||||
this._animationTimeoutId = window.setTimeout(
|
||||
() => {
|
||||
this.setState({ isDeleting: false });
|
||||
|
||||
callback(this.props.item);
|
||||
},
|
||||
TodoItem.ANIMATION_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoList.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
import {
|
||||
FocusZone,
|
||||
FocusZoneDirection,
|
||||
IFocusZoneProps,
|
||||
List,
|
||||
KeyCodes,
|
||||
getRTLSafeKeyCode
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoItem, { ITodoItemProps } from './TodoItem';
|
||||
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* Props of TodoList component.
|
||||
*/
|
||||
export interface ITodoListProps {
|
||||
/**
|
||||
* The Todo items rendered in this List component.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoList component using fabric-react component <List> <FocusZone>
|
||||
*
|
||||
* Link of <List>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/list
|
||||
* Link of <FocusZone>: https://fabricreact.azurewebsites.net/fabric-react/master/#examples/focuszone
|
||||
*/
|
||||
export default class TodoList extends React.Component<ITodoListProps, {}> {
|
||||
constructor(props: ITodoListProps) {
|
||||
super(props);
|
||||
|
||||
this._onRenderCell = this._onRenderCell.bind(this);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps: ITodoListProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IFocusZoneProps> {
|
||||
return (
|
||||
<FocusZone
|
||||
direction={ FocusZoneDirection.vertical }
|
||||
isInnerZoneKeystroke={ (ev: React.KeyboardEvent) => ev.which === getRTLSafeKeyCode(KeyCodes.right) }
|
||||
>
|
||||
<List
|
||||
className={ styles.todoList }
|
||||
items={ this.props.items }
|
||||
onRenderCell={ this._onRenderCell }
|
||||
/>
|
||||
</FocusZone>
|
||||
);
|
||||
}
|
||||
|
||||
private _onRenderCell(item: ITodoTask): React.ReactElement<ITodoItemProps> {
|
||||
return (
|
||||
<TodoItem
|
||||
{ ...this.props }
|
||||
key={ item.Id }
|
||||
item={ item }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoTabs.tsx
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { Compare } from '@microsoft/sp-client-base';
|
||||
|
||||
import { format } from '../common/Utils';
|
||||
|
||||
import {
|
||||
Pivot,
|
||||
PivotItem,
|
||||
IPivotProps,
|
||||
PivotLinkSize
|
||||
} from 'office-ui-fabric-react';
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ItemOperationCallback
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import TodoList from './TodoList';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
import styles from '../style/Todo.module.scss';
|
||||
|
||||
/**
|
||||
* The tab type used as the key of the PivotItem.
|
||||
*/
|
||||
enum TabType {
|
||||
/**
|
||||
* The tab showing the active tasks.
|
||||
*/
|
||||
Active,
|
||||
|
||||
/**
|
||||
* The tab showing the completed tasks.
|
||||
*/
|
||||
Completed,
|
||||
|
||||
/**
|
||||
* The tab showing all tasks in the list.
|
||||
*/
|
||||
All
|
||||
}
|
||||
|
||||
/**
|
||||
* Props of TodoTabs component.
|
||||
*/
|
||||
export interface ITodoTabsProps {
|
||||
/**
|
||||
* The list items rendered in TodoTabs.
|
||||
* It will be filtered in each PivotItems by needs.
|
||||
*/
|
||||
items: ITodoTask[];
|
||||
|
||||
/**
|
||||
* Whether to show completed tasks.
|
||||
*
|
||||
* If it is set to false, the tab of completed tasks will be hiden, and
|
||||
* there will be no completed tasks shown in the list.
|
||||
*/
|
||||
shouldShowCompletedTasks: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who created the task.
|
||||
*/
|
||||
shouldShowCreatedBy: boolean;
|
||||
|
||||
/**
|
||||
* Whether to show who mark the task as complete.
|
||||
*/
|
||||
shouldShowCompletedBy: boolean;
|
||||
|
||||
/**
|
||||
* onToggleComplete callback triggered when checkbox of one item is checked or unchecekd.
|
||||
*/
|
||||
onToggleComplete: ItemOperationCallback;
|
||||
|
||||
/**
|
||||
* onDeleteItem callback triggered when delete of one item is triggered.
|
||||
*/
|
||||
onDeleteItem: ItemOperationCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* The TodoTabs component using fabric-react component <Pivot>.
|
||||
*
|
||||
* Link of <Pivot>: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/pivot
|
||||
*/
|
||||
export default class TodoTabs extends React.Component<ITodoTabsProps, {}> {
|
||||
public shouldComponentUpdate(nextProps: ITodoTabsProps, nextState: {}): boolean {
|
||||
return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState);
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IPivotProps> {
|
||||
const pivotArray: IPivotProps[] = [];
|
||||
|
||||
const activeTasks: ITodoTask[] = [];
|
||||
const completedTasks: ITodoTask[] = [];
|
||||
|
||||
this.props.items.forEach((item: ITodoTask) => {
|
||||
if (item.PercentComplete < 1) {
|
||||
activeTasks.push(item);
|
||||
} else if (this.props.shouldShowCompletedTasks) {
|
||||
completedTasks.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
const allTasks: ITodoTask[] = activeTasks.concat(completedTasks);
|
||||
|
||||
if (activeTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(activeTasks, strings.TodoListTabNameActive, TabType.Active)
|
||||
);
|
||||
}
|
||||
|
||||
if (completedTasks.length > 0 && this.props.shouldShowCompletedTasks) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(completedTasks, strings.TodoListTabNameCompleted, TabType.Completed)
|
||||
);
|
||||
}
|
||||
|
||||
if (allTasks.length > 0) {
|
||||
pivotArray.push(
|
||||
this._renderPivotItemList(allTasks, strings.TodoListTabNameAllTasks, TabType.All)
|
||||
);
|
||||
}
|
||||
|
||||
return pivotArray.length > 0
|
||||
? (
|
||||
<div className={ styles.todoPivot }>
|
||||
<Pivot linkSize={ PivotLinkSize.large }>
|
||||
{ pivotArray }
|
||||
</Pivot>
|
||||
</div>
|
||||
)
|
||||
: null; // tslint:disable-line:no-null-keyword
|
||||
}
|
||||
|
||||
private _renderPivotItemList(
|
||||
tasks: ITodoTask[],
|
||||
tabName: string,
|
||||
tabKey: TabType
|
||||
): React.ReactElement<IPivotProps> {
|
||||
return (
|
||||
<PivotItem
|
||||
linkText={ format(tabName, tasks.length) }
|
||||
itemKey={ tabKey }
|
||||
key={ tabKey }
|
||||
>
|
||||
<TodoList
|
||||
{ ...this.props }
|
||||
items={ tasks }
|
||||
onToggleComplete={ this.props.onToggleComplete }
|
||||
onDeleteItem={ this.props.onDeleteItem }
|
||||
/>
|
||||
</PivotItem>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file ITodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
ITodoTask,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* The data provider interface implemented by MockTodoDataProvider and TodoDataProvider.
|
||||
*/
|
||||
export interface ITodoDataProvider {
|
||||
/**
|
||||
* The current selected list.
|
||||
*
|
||||
* It should be always the same with the list in web part properties.
|
||||
*/
|
||||
selectedList: ITodoTaskList;
|
||||
|
||||
/**
|
||||
* The max number of tasks show in the Todo list.
|
||||
*
|
||||
* It should be always the sam with the maxNumberOfTasks in web part properties.
|
||||
*/
|
||||
maxNumberOfTasks: number;
|
||||
|
||||
/**
|
||||
* readLists will fetch the information of all the lists in current site.
|
||||
*/
|
||||
readLists(): Promise<ITodoTaskList[]>;
|
||||
|
||||
/**
|
||||
* createItem will send REST call to add an item in the current .
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {string} title is the title of item that will be created in current list.
|
||||
*/
|
||||
createItem(title: string): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* readItems will send REST call to fetch the a number up to maxNumberOfTasks of items
|
||||
* in the current.
|
||||
*/
|
||||
readItems(): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* updateItem will send REST call to update(merge) an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemUpdated is the item which will be merged to current list.
|
||||
*/
|
||||
updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]>;
|
||||
|
||||
/**
|
||||
* deleteItem will send REST call to remove an item in the current list.
|
||||
* And it also fetch the newest version of list items to sync the current list.
|
||||
*
|
||||
* @param {ITodoTask} itemDeleted is the item which will be deleted in current list.
|
||||
*/
|
||||
deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]>;
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file MockTodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import * as lodash from '@microsoft/sp-lodash-subset';
|
||||
|
||||
import { ITodoDataProvider } from './ITodoDataProvider';
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoTask,
|
||||
ITodoPerson,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
/**
|
||||
* MockTodoDataProvider is used for testing page and unit test page.
|
||||
*/
|
||||
export default class MockTodoDataProvider implements ITodoDataProvider {
|
||||
private _lists: ITodoTaskList[];
|
||||
private _selectedList: ITodoTaskList;
|
||||
|
||||
private _maxNumberOfTasks: number;
|
||||
|
||||
private _itemsStore: { [listTitle: string]: ITodoTask[] };
|
||||
private _idCounter: number;
|
||||
|
||||
public get selectedList(): ITodoTaskList { return this._selectedList; }
|
||||
public set selectedList(value: ITodoTaskList) { this._selectedList = value; }
|
||||
|
||||
public get maxNumberOfTasks(): number { return this._maxNumberOfTasks; }
|
||||
public set maxNumberOfTasks(value: number) { this._maxNumberOfTasks = value; }
|
||||
|
||||
constructor(webPartProps: ITodoWebPartProps) {
|
||||
this._maxNumberOfTasks = webPartProps.maxNumberOfTasks;
|
||||
this.selectedList = webPartProps.selectedList;
|
||||
|
||||
this._idCounter = 0;
|
||||
|
||||
this._lists = [
|
||||
{
|
||||
Title: 'DefaultTodoList',
|
||||
ListItemEntityTypeFullName: '#SP.Data.DefaultTodoListListItem',
|
||||
Id: 'f3c61a58-44a8-4f87-bf5b-03668af148a6'
|
||||
},
|
||||
{
|
||||
Title: 'ListOne',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListOneListItem',
|
||||
Id: '01c78e45-06c2-4384-be9e-caa23912ebda'
|
||||
},
|
||||
{
|
||||
Title: 'ListTwo',
|
||||
ListItemEntityTypeFullName: '#SP.Data.ListTwoListItem',
|
||||
Id: '90c704fe-ab47-4d88-8b26-56f9a71e09e6'
|
||||
}
|
||||
];
|
||||
|
||||
this._itemsStore = {
|
||||
'DefaultTodoList': [
|
||||
this._generateItem('Finish Sample Todo web part before dev kitchen', false),
|
||||
this._generateItem('Finish All the work in Todo web part before dev kitchen', false),
|
||||
this._generateItem('Sharepoint API investigation for Todo web part', true),
|
||||
this._generateItem('Bug fixing of Pivot Control', true)
|
||||
],
|
||||
'ListOne': [
|
||||
this._generateItem('Item one in ListOne', false),
|
||||
this._generateItem('Item two in ListOne', false),
|
||||
this._generateItem('Item three in ListOne', true),
|
||||
this._generateItem('Item four in ListOne', true)
|
||||
],
|
||||
'ListTwo': [
|
||||
this._generateItem('Item one in ListTwo', false),
|
||||
this._generateItem('Item two in ListTwo', false),
|
||||
this._generateItem('Item three in ListTwo', true),
|
||||
this._generateItem('Item four in ListTwo', true)
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the mock task lists.
|
||||
*/
|
||||
public readLists(): Promise<ITodoTaskList[]> {
|
||||
const lists: ITodoTaskList[] = this._lists;
|
||||
|
||||
return new Promise<ITodoTaskList[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(lists), 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mock task item using the title.
|
||||
*/
|
||||
public createItem(title: string): Promise<ITodoTask[]> {
|
||||
const newItem: ITodoTask = {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 3,
|
||||
Title: 'Lisa Andrews',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: 0
|
||||
};
|
||||
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].concat(newItem);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read task items from selected list.
|
||||
*/
|
||||
public readItems(): Promise<ITodoTask[]> {
|
||||
const items: ITodoTask[] =
|
||||
this._itemsStore[this._selectedList.Title].slice(0, this._maxNumberOfTasks);
|
||||
|
||||
return new Promise<ITodoTask[]>((resolve) => {
|
||||
// Using setTimeout to mimic the real server response experience.
|
||||
// It's for debugging, testing and styles development of loading component.
|
||||
setTimeout(() => resolve(items), 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the task item.
|
||||
*/
|
||||
public updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]> {
|
||||
const index: number =
|
||||
lodash.findIndex(
|
||||
this._itemsStore[this._selectedList.Title],
|
||||
(item: ITodoTask) => item.Id === itemUpdated.Id
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
const editor: ITodoPerson = itemUpdated.PercentComplete >= 1
|
||||
? {
|
||||
Id: 3,
|
||||
Title: 'Chris Meyer',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
}
|
||||
: undefined;
|
||||
|
||||
this._itemsStore[this._selectedList.Title][index] = itemUpdated;
|
||||
this._itemsStore[this._selectedList.Title][index].Editor = editor;
|
||||
|
||||
return this.readItems();
|
||||
} else {
|
||||
return Promise.reject(new Error(`Item to update doesn't exist.`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the task item.
|
||||
*/
|
||||
public deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]> {
|
||||
this._itemsStore[this._selectedList.Title] =
|
||||
this._itemsStore[this._selectedList.Title].filter((item: ITodoTask) => item.Id !== itemDeleted.Id);
|
||||
|
||||
return this.readItems();
|
||||
}
|
||||
|
||||
private _generateItem(title: string, isCompleted: boolean): ITodoTask {
|
||||
return {
|
||||
Id: this._idCounter++,
|
||||
Title: title,
|
||||
Author: {
|
||||
Id: 1,
|
||||
Title: 'Misty Shock',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
Editor: !isCompleted ? undefined : {
|
||||
Id: 2,
|
||||
Title: 'Burton Guido',
|
||||
Picture: 'http://dev.office.com/Modules/DevOffice.Fabric/Fabric/components/Persona/Persona.Person2.png',
|
||||
EMail: ''
|
||||
},
|
||||
PercentComplete: !isCompleted ? 0 : 1
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/**
|
||||
* @Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* @file TodoDataProvider.ts
|
||||
*/
|
||||
|
||||
import { HttpClient, ODataBatch } from '@microsoft/sp-client-base';
|
||||
import { IWebPartContext } from '@microsoft/sp-client-preview';
|
||||
|
||||
import { ITodoDataProvider } from './ITodoDataProvider';
|
||||
import {
|
||||
ITodoWebPartProps,
|
||||
ITodoTask,
|
||||
ITodoTaskList
|
||||
} from '../ITodoWebPartProps';
|
||||
|
||||
import * as strings from 'todoStrings';
|
||||
|
||||
/**
|
||||
* TodoDataProvider interact with current sharepoint site to accomplish
|
||||
* item operations and data fetching interaction with backend.
|
||||
*/
|
||||
export default class TodoDataProvider implements ITodoDataProvider {
|
||||
private _httpClient: HttpClient;
|
||||
private _webAbsoluteUrl: string;
|
||||
|
||||
private _lists: ITodoTaskList[];
|
||||
|
||||
private _selectedList: ITodoTaskList;
|
||||
private _maxNumberOfTasks: number;
|
||||
private _operationId: number;
|
||||
|
||||
private _listsUrl: string;
|
||||
private _listItemsUrl: string;
|
||||
|
||||
public get selectedList(): ITodoTaskList { return this._selectedList; }
|
||||
public set selectedList(value: ITodoTaskList) {
|
||||
this._selectedList = value;
|
||||
this._listItemsUrl = `${this._listsUrl}(guid'${value.Id}')/items`;
|
||||
}
|
||||
|
||||
public get maxNumberOfTasks(): number { return this._maxNumberOfTasks; }
|
||||
public set maxNumberOfTasks(value: number) { this._maxNumberOfTasks = value; }
|
||||
|
||||
constructor(webPartProps: ITodoWebPartProps, webPartContext: IWebPartContext) {
|
||||
this._httpClient = webPartContext.httpClient as any; // tslint:disable-line:no-any
|
||||
this._webAbsoluteUrl = webPartContext.pageContext.web.absoluteUrl;
|
||||
|
||||
this._operationId = 0;
|
||||
this._maxNumberOfTasks = webPartProps.maxNumberOfTasks;
|
||||
|
||||
this._listsUrl = `${this._webAbsoluteUrl}/_api/web/lists`;
|
||||
if (webPartProps.selectedList.Id) {
|
||||
this.selectedList = webPartProps.selectedList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all the Tasks lists in the site.
|
||||
*/
|
||||
public readLists(): Promise<ITodoTaskList[]> {
|
||||
// It is TasksWithTimelineAndHierarchy.
|
||||
const listTemplateId: string = '171';
|
||||
const queryString: string = `?$filter=BaseTemplate eq ${listTemplateId}`;
|
||||
|
||||
return this._httpClient.get(this._listsUrl + queryString)
|
||||
.then(this._checkStatus)
|
||||
.then((response: Response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((json: { value: ITodoTaskList[] }) => {
|
||||
if (json.value.length === 0) {
|
||||
throw new Error(strings.DropdownErrorMessageNoListAvailable);
|
||||
} else {
|
||||
return this._lists = json.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to create item and sync the latest list.
|
||||
*/
|
||||
public createItem(title: string): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._createItem(batch, title),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
public readItems(): Promise<ITodoTask[]> {
|
||||
return this._readItems(this._httpClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to update item and sync the latest list.
|
||||
*/
|
||||
public updateItem(itemUpdated: ITodoTask): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._updateItem(batch, itemUpdated),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch request to delete item and sync the latest list.
|
||||
*/
|
||||
public deleteItem(itemDeleted: ITodoTask): Promise<ITodoTask[]> {
|
||||
const batch: ODataBatch = this._httpClient.beginBatch();
|
||||
|
||||
const batchPromises: Promise<{}>[] = [
|
||||
this._deleteItem(batch, itemDeleted),
|
||||
this._readItems(batch)
|
||||
];
|
||||
|
||||
return this._resolveBatch(batch, batchPromises);
|
||||
}
|
||||
|
||||
private _checkStatus(response: Response): Promise<Response> {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return Promise.resolve(response);
|
||||
} else {
|
||||
return Promise.reject(new Error(JSON.stringify(response)));
|
||||
}
|
||||
}
|
||||
|
||||
private _getPictureUrl(eMail: string): string {
|
||||
return `${this._webAbsoluteUrl}/_layouts/15/userphoto.aspx?size=L&username=${eMail}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to create item in the SharePoint list.
|
||||
*/
|
||||
private _createItem(batch: ODataBatch, title: string): Promise<Response> {
|
||||
const body: {} = {
|
||||
'@data.type': `${this._selectedList.ListItemEntityTypeFullName}`,
|
||||
'Title': title
|
||||
};
|
||||
|
||||
return batch.post(this._listItemsUrl, { body: JSON.stringify(body) }).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the items from SharePoint list using HttpClient or ODataBatch.
|
||||
*
|
||||
* @param {HTTPClient | ODataBatch} requester is the type of the object which send request to the server.
|
||||
* If it is HttpClient, the request will be sent immediately.
|
||||
* If it is ODataBatch, the request will be batched until it is executed.
|
||||
*/
|
||||
private _readItems(requester: HttpClient | ODataBatch): Promise<ITodoTask[]> {
|
||||
const queryString: string = `?$select=Id,Title,PercentComplete,` +
|
||||
`Author/Id,Author/Title,Author/EMail,Editor/Id,Editor/Title,Editor/EMail&` +
|
||||
`$expand=Author,Editor&$top=${this._maxNumberOfTasks}`;
|
||||
const currentId: number = ++this._operationId;
|
||||
|
||||
return requester.get(this._listItemsUrl + queryString)
|
||||
.then(this._checkStatus)
|
||||
.then((response: Response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((json: { value: ITodoTask[] }) => {
|
||||
return json.value.map((task: ITodoTask) => {
|
||||
task.Author.Picture = this._getPictureUrl(task.Author.EMail);
|
||||
task.Editor.Picture = this._getPictureUrl(task.Editor.EMail);
|
||||
return task;
|
||||
});
|
||||
})
|
||||
.then((tasks: ITodoTask[]) => currentId === this._operationId ? tasks : undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to update the item in the list.
|
||||
*/
|
||||
private _updateItem(batch: ODataBatch, item: ITodoTask): Promise<Response> {
|
||||
|
||||
const itemUpdatedUrl: string = `${this._listItemsUrl}(${item.Id})`;
|
||||
|
||||
const headers: Headers = new Headers();
|
||||
headers.append('If-Match', '*');
|
||||
|
||||
const body: {} = {
|
||||
'@data.type': `${this._selectedList.ListItemEntityTypeFullName}`,
|
||||
'PercentComplete': item.PercentComplete
|
||||
};
|
||||
|
||||
return batch.fetch(itemUpdatedUrl, {
|
||||
body: JSON.stringify(body),
|
||||
headers,
|
||||
method: 'PATCH' // Use PATCH http method to perform update operation.
|
||||
}).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch the request to delete the item in the list.
|
||||
*/
|
||||
private _deleteItem(batch: ODataBatch, item: ITodoTask): Promise<Response> {
|
||||
const itemDeletedUrl: string = `${this._listItemsUrl}(${item.Id})`;
|
||||
|
||||
const headers: Headers = new Headers();
|
||||
headers.append('If-Match', '*');
|
||||
|
||||
return batch.fetch(itemDeletedUrl, {
|
||||
headers,
|
||||
method: 'DELETE' // Use DELETE http method to perform delete operation.
|
||||
}).then(this._checkStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the batch request and return the promise of the last request.
|
||||
*/
|
||||
private _resolveBatch(batch: ODataBatch, promises: Promise<{}>[]): Promise<ITodoTask[]> {
|
||||
return batch.execute()
|
||||
.then(() => Promise.all(promises).then(values => values[values.length - 1]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
define([], function() {
|
||||
return {
|
||||
"TodoListTitle": "Todo List",
|
||||
"TodoListTabNameAllTasks": "All Tasks ({0})",
|
||||
"TodoListTabNameCompleted": "Completed ({0})",
|
||||
"TodoListTabNameActive": "Active ({0})",
|
||||
"AddButton": "Add",
|
||||
"InputBoxPlaceholder": "Add a task...",
|
||||
"FetchingListsLabel": "Fetching your lists...",
|
||||
"FetchingTasksLabel": "Fetching your tasks...",
|
||||
"TodoItemCreateLabel": "Creator",
|
||||
"TodoItemCompleteLabel": "Doer",
|
||||
"TodoItemAriaLabelCheckedState": "This item is completed.",
|
||||
"TodoItemAriaLabelUncheckedState": "This item is active.",
|
||||
"TodoItemAriaLabelTitle": "The title is {0}.",
|
||||
"TodoItemAriaLabelCreator": "This item is created by {0}",
|
||||
"TodoItemAriaLabelEditor": "This item is completed by {0}",
|
||||
"TodoItemAriaLabel": "{0} {1} {2} {3}",
|
||||
"DeleteItemTitle": "Delete this item.",
|
||||
"DeleteItemAriaLabel": "Delete",
|
||||
"WorkingOnSpinnerLabel": "Working on...",
|
||||
"PropertyPaneDescriptionSetProperties": "Set the properties of your todo items.",
|
||||
"PropertyPaneHeadingConfigureSoruce": "Configure source",
|
||||
"PropertyPaneHeadingConfigureDisplay": "Configure display",
|
||||
"PropertyPaneDropdownLoadingLabel": "Loading lists...",
|
||||
"DropdownErrorMessageNoListAvailable": "There is no task list in current SharePoint site. Please check there is at least one task list in site content.",
|
||||
"PropertyPaneDropdownLabelTasksList": "Tasks list",
|
||||
"PropertyPaneToggleOnTextShowCompletedTasks": "Show completed tasks",
|
||||
"PropertyPaneToggleOffTextHideCompletedTasks": "Hide completed tasks",
|
||||
"PropertyPaneCheckboxGroupLabel": "Select display fields to show",
|
||||
"PropertyPaneCheckboxCreatedByLabel": "Created By",
|
||||
"PropertyPaneCheckboxCompletedByLabel": "Completed By",
|
||||
"PropertyPaneSliderLabel": "Show this many tasks at a time",
|
||||
"TitleEmptyErrorMessage": "You can't leave this blank."
|
||||
}
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
declare interface ITodoStrings {
|
||||
TodoListTitle: string;
|
||||
TodoListTabNameAllTasks: string;
|
||||
TodoListTabNameCompleted: string;
|
||||
TodoListTabNameActive: string;
|
||||
AddButton: string;
|
||||
InputBoxPlaceholder: string;
|
||||
FetchingListsLabel: string;
|
||||
FetchingTasksLabel: string;
|
||||
TodoItemCreateLabel: string;
|
||||
TodoItemCompleteLabel: string;
|
||||
TodoItemAriaLabelCheckedState: string;
|
||||
TodoItemAriaLabelUncheckedState: string;
|
||||
TodoItemAriaLabelTitle: string;
|
||||
TodoItemAriaLabelCreator: string;
|
||||
TodoItemAriaLabelEditor: string;
|
||||
TodoItemAriaLabel: string;
|
||||
DeleteItemTitle: string;
|
||||
DeleteItemAriaLabel: string;
|
||||
WorkingOnSpinnerLabel: string;
|
||||
PropertyPaneDescriptionSetProperties: string;
|
||||
PropertyPaneHeadingConfigureSoruce: string;
|
||||
PropertyPaneHeadingConfigureDisplay: string;
|
||||
PropertyPaneDropdownLoadingLabel: string;
|
||||
DropdownErrorMessageNoListAvailable: string;
|
||||
PropertyPaneDropdownLabelTasksList: string;
|
||||
PropertyPaneToggleOnTextShowCompletedTasks: string;
|
||||
PropertyPaneToggleOffTextHideCompletedTasks: string;
|
||||
PropertyPaneCheckboxGroupLabel: string;
|
||||
PropertyPaneCheckboxCreatedByLabel: string;
|
||||
PropertyPaneCheckboxCompletedByLabel: string;
|
||||
PropertyPaneSliderLabel: string;
|
||||
TitleEmptyErrorMessage: string;
|
||||
}
|
||||
|
||||
declare module 'todoStrings' {
|
||||
const strings: ITodoStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
@import "~office-ui-fabric/dist/sass/Fabric.Common";
|
||||
|
||||
.todo {
|
||||
padding: 28px 40px;
|
||||
|
||||
.topRow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.todoHeading {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.todoError {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
}
|
||||
|
||||
.todoPivot {
|
||||
padding-top: 24px;
|
||||
|
||||
:global li.ms-Pivot-link{
|
||||
margin-right: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.todoForm {
|
||||
display: table;
|
||||
|
||||
.textField {
|
||||
display: table-cell;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
.addButtonCell {
|
||||
display: table-cell;
|
||||
|
||||
.addButton {
|
||||
margin-left: 10px;
|
||||
white-space: nowrap;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.todoList {
|
||||
margin-top: 20px;
|
||||
border-top: 1px lightgrey solid;
|
||||
|
||||
:global .ms-Icon {
|
||||
font-family: 'sp-MDL2';
|
||||
}
|
||||
:global .ms-Icon--X {
|
||||
transform: rotate(45deg);
|
||||
|
||||
&:before {
|
||||
content: "\E710";
|
||||
}
|
||||
}
|
||||
|
||||
.todoItem {
|
||||
border: 1px lightgrey solid;
|
||||
border-top: none;
|
||||
|
||||
.itemTaskRow {
|
||||
padding: 16px 20px 8px 20px;
|
||||
|
||||
.deleteButton {
|
||||
:global .ms-Button-icon {
|
||||
float: right;
|
||||
font-size: 21px;
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
color: $ms-color-themePrimary;
|
||||
}
|
||||
}
|
||||
.checkbox {
|
||||
:global .ms-ChoiceField-field {
|
||||
margin: 0px;
|
||||
|
||||
&:after {
|
||||
top: 6px;
|
||||
}
|
||||
&:before {
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
:global .ms-Label {
|
||||
color: $ms-color-neutralPrimary;
|
||||
font-size: 22px;
|
||||
padding: 0 0 0 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.itemPeopleRow {
|
||||
display: flex;
|
||||
padding: 8px 56px 16px 56px;
|
||||
|
||||
:global .ms-DocumentCardActivity {
|
||||
@include ms-u-sm4;
|
||||
padding-left: 0px;
|
||||
|
||||
.ms-DocumentCardActivity-details {
|
||||
left: 33px;
|
||||
}
|
||||
.ms-DocumentCardActivity-name {
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.isHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isCompleted {
|
||||
border-top: 1px white solid;
|
||||
border-left: 1px $ms-color-neutralLighter solid;
|
||||
border-right: 1px $ms-color-neutralLighter solid;
|
||||
@include ms-bgColor-neutralLighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workingOnItSpinner {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
height: 33.33333%;
|
||||
line-height: 1.5em;
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
.fetchingTasksSpinner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px 0px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width:640px) {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.propertyPaneErrorMessage {
|
||||
@include ms-font-s;
|
||||
@include ms-fontColor-redDark;
|
||||
@include resetMargins;
|
||||
|
||||
padding: 20px 0;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Gulp" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{99a1fe26-363c-4980-b7c7-f14322a071ed}</ProjectGuid>
|
||||
<ProjectHome />
|
||||
<ProjectView>ProjectFiles</ProjectView>
|
||||
<StartupFile>node_modules\gulp\bin\gulp.js</StartupFile>
|
||||
<WorkingDirectory>.</WorkingDirectory>
|
||||
<OutputPath>.</OutputPath>
|
||||
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
|
||||
<TypeScriptSourceMap>true</TypeScriptSourceMap>
|
||||
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
|
||||
<EnableTypeScript>false</EnableTypeScript>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ScriptArguments>serve</ScriptArguments>
|
||||
<StartWebBrowser>True</StartWebBrowser>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'" />
|
||||
<Target Name="Gulp">
|
||||
<Message Text="Running gulp2vs.js" Importance="normal" />
|
||||
<Exec Command="CMD.EXE /c node $(MSBuildThisFileDirectory)\node_modules\gulp\bin\gulp.js bundle" />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<Content Include="*.js" />
|
||||
<Content Include="*.json" />
|
||||
<Content Include="*.md" />
|
||||
<Content Include="config\**\*.json" />
|
||||
<Content Include="docs\*.md" />
|
||||
<Content Include="sharepoint\feature_xml\**\*.*" />
|
||||
<Content Include="src\**\*.html" />
|
||||
<Content Include="src\**\*.js" />
|
||||
<Content Include="src\**\*.json" />
|
||||
<Content Include="src\**\*.less" />
|
||||
<Content Include="src\**\*.resx" />
|
||||
<Content Include="src\**\*.scss" />
|
||||
<Content Include="src\**\*.ts" />
|
||||
<Content Include="src\**\*.tsx" />
|
||||
<Content Include="typings\**\*.ts" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<!--Do not delete the following Import Project. While this appears to do nothing it is a marker for setting TypeScript properties before our import that depends on them.-->
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
|
||||
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>0</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>http://localhost:48022/</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>True</UseCustomServer>
|
||||
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
|
||||
<WebProjectProperties>
|
||||
<StartPageUrl>
|
||||
</StartPageUrl>
|
||||
<StartAction>CurrentPage</StartAction>
|
||||
<AspNetDebugging>True</AspNetDebugging>
|
||||
<SilverlightDebugging>False</SilverlightDebugging>
|
||||
<NativeDebugging>False</NativeDebugging>
|
||||
<SQLDebugging>False</SQLDebugging>
|
||||
<ExternalProgram>
|
||||
</ExternalProgram>
|
||||
<StartExternalURL>
|
||||
</StartExternalURL>
|
||||
<StartCmdLineArguments>
|
||||
</StartCmdLineArguments>
|
||||
<StartWorkingDirectory>
|
||||
</StartWorkingDirectory>
|
||||
<EnableENC>False</EnableENC>
|
||||
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
</Project>
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Type definitions for webpack in Microsoft ODSP projects
|
||||
// Project: ODSP-WEBPACK
|
||||
|
||||
/*
|
||||
* This definition of webpack require overrides all other definitions of require in our toolchain
|
||||
* Make sure all other definitions of require are commented out e.g. in node.d.ts
|
||||
*/
|
||||
declare var require: {
|
||||
(path: string): any;
|
||||
(paths: string[], callback: (...modules: any[]) => void): void;
|
||||
resolve: (id: string) => string;
|
||||
ensure: (paths: string[], callback: (require: <T>(path: string) => T) => void, path: string) => void;
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// Type definitions for Microsoft ODSP projects
|
||||
// Project: ODSP
|
||||
|
||||
/// <reference path="odsp-webpack.d.ts" />
|
||||
|
||||
/* Global definition for DEBUG builds */
|
||||
declare const DEBUG: boolean;
|
||||
|
||||
/* Global definition for UNIT_TEST builds */
|
||||
declare const UNIT_TEST: boolean;
|
|
@ -0,0 +1,15 @@
|
|||
// Type definitions for assertion-error 1.0.0
|
||||
// Project: https://github.com/chaijs/assertion-error
|
||||
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
declare module 'assertion-error' {
|
||||
class AssertionError implements Error {
|
||||
constructor(message: string, props?: any, ssf?: Function);
|
||||
name: string;
|
||||
message: string;
|
||||
showDiff: boolean;
|
||||
stack: string;
|
||||
}
|
||||
export = AssertionError;
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
// Type definitions for chai 3.2.0
|
||||
// Project: http://chaijs.com/
|
||||
// Definitions by: Jed Mao <https://github.com/jedmao/>,
|
||||
// Bart van der Schoor <https://github.com/Bartvds>,
|
||||
// Andrew Brown <https://github.com/AGBrown>,
|
||||
// Olivier Chevet <https://github.com/olivr70>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
// <reference path="../assertion-error/assertion-error.d.ts"/>
|
||||
|
||||
declare module Chai {
|
||||
|
||||
interface ChaiStatic {
|
||||
expect: ExpectStatic;
|
||||
should(): Should;
|
||||
/**
|
||||
* Provides a way to extend the internals of Chai
|
||||
*/
|
||||
use(fn: (chai: any, utils: any) => void): any;
|
||||
assert: AssertStatic;
|
||||
config: Config;
|
||||
AssertionError: AssertionError;
|
||||
}
|
||||
|
||||
export interface ExpectStatic extends AssertionStatic {
|
||||
fail(actual?: any, expected?: any, message?: string, operator?: string): void;
|
||||
}
|
||||
|
||||
export interface AssertStatic extends Assert {
|
||||
}
|
||||
|
||||
export interface AssertionStatic {
|
||||
(target: any, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface ShouldAssertion {
|
||||
equal(value1: any, value2: any, message?: string): void;
|
||||
Throw: ShouldThrow;
|
||||
throw: ShouldThrow;
|
||||
exist(value: any, message?: string): void;
|
||||
}
|
||||
|
||||
interface Should extends ShouldAssertion {
|
||||
not: ShouldAssertion;
|
||||
fail(actual: any, expected: any, message?: string, operator?: string): void;
|
||||
}
|
||||
|
||||
interface ShouldThrow {
|
||||
(actual: Function): void;
|
||||
(actual: Function, expected: string|RegExp, message?: string): void;
|
||||
(actual: Function, constructor: Error|Function, expected?: string|RegExp, message?: string): void;
|
||||
}
|
||||
|
||||
interface Assertion extends LanguageChains, NumericComparison, TypeComparison {
|
||||
not: Assertion;
|
||||
deep: Deep;
|
||||
any: KeyFilter;
|
||||
all: KeyFilter;
|
||||
a: TypeComparison;
|
||||
an: TypeComparison;
|
||||
include: Include;
|
||||
includes: Include;
|
||||
contain: Include;
|
||||
contains: Include;
|
||||
ok: Assertion;
|
||||
true: Assertion;
|
||||
false: Assertion;
|
||||
null: Assertion;
|
||||
undefined: Assertion;
|
||||
NaN: Assertion;
|
||||
exist: Assertion;
|
||||
empty: Assertion;
|
||||
arguments: Assertion;
|
||||
Arguments: Assertion;
|
||||
equal: Equal;
|
||||
equals: Equal;
|
||||
eq: Equal;
|
||||
eql: Equal;
|
||||
eqls: Equal;
|
||||
property: Property;
|
||||
ownProperty: OwnProperty;
|
||||
haveOwnProperty: OwnProperty;
|
||||
ownPropertyDescriptor: OwnPropertyDescriptor;
|
||||
haveOwnPropertyDescriptor: OwnPropertyDescriptor;
|
||||
length: Length;
|
||||
lengthOf: Length;
|
||||
match: Match;
|
||||
matches: Match;
|
||||
string(string: string, message?: string): Assertion;
|
||||
keys: Keys;
|
||||
key(string: string): Assertion;
|
||||
throw: Throw;
|
||||
throws: Throw;
|
||||
Throw: Throw;
|
||||
respondTo: RespondTo;
|
||||
respondsTo: RespondTo;
|
||||
itself: Assertion;
|
||||
satisfy: Satisfy;
|
||||
satisfies: Satisfy;
|
||||
closeTo(expected: number, delta: number, message?: string): Assertion;
|
||||
members: Members;
|
||||
increase: PropertyChange;
|
||||
increases: PropertyChange;
|
||||
decrease: PropertyChange;
|
||||
decreases: PropertyChange;
|
||||
change: PropertyChange;
|
||||
changes: PropertyChange;
|
||||
extensible: Assertion;
|
||||
sealed: Assertion;
|
||||
frozen: Assertion;
|
||||
|
||||
}
|
||||
|
||||
interface LanguageChains {
|
||||
to: Assertion;
|
||||
be: Assertion;
|
||||
been: Assertion;
|
||||
is: Assertion;
|
||||
that: Assertion;
|
||||
which: Assertion;
|
||||
and: Assertion;
|
||||
has: Assertion;
|
||||
have: Assertion;
|
||||
with: Assertion;
|
||||
at: Assertion;
|
||||
of: Assertion;
|
||||
same: Assertion;
|
||||
}
|
||||
|
||||
interface NumericComparison {
|
||||
above: NumberComparer;
|
||||
gt: NumberComparer;
|
||||
greaterThan: NumberComparer;
|
||||
least: NumberComparer;
|
||||
gte: NumberComparer;
|
||||
below: NumberComparer;
|
||||
lt: NumberComparer;
|
||||
lessThan: NumberComparer;
|
||||
most: NumberComparer;
|
||||
lte: NumberComparer;
|
||||
within(start: number, finish: number, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface NumberComparer {
|
||||
(value: number, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface TypeComparison {
|
||||
(type: string, message?: string): Assertion;
|
||||
instanceof: InstanceOf;
|
||||
instanceOf: InstanceOf;
|
||||
}
|
||||
|
||||
interface InstanceOf {
|
||||
(constructor: Object, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Deep {
|
||||
equal: Equal;
|
||||
include: Include;
|
||||
property: Property;
|
||||
members: Members;
|
||||
}
|
||||
|
||||
interface KeyFilter {
|
||||
keys: Keys;
|
||||
}
|
||||
|
||||
interface Equal {
|
||||
(value: any, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Property {
|
||||
(name: string, value?: any, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface OwnProperty {
|
||||
(name: string, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface OwnPropertyDescriptor {
|
||||
(name: string, descriptor: PropertyDescriptor, message?: string): Assertion;
|
||||
(name: string, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Length extends LanguageChains, NumericComparison {
|
||||
(length: number, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Include {
|
||||
(value: Object, message?: string): Assertion;
|
||||
(value: string, message?: string): Assertion;
|
||||
(value: number, message?: string): Assertion;
|
||||
keys: Keys;
|
||||
members: Members;
|
||||
any: KeyFilter;
|
||||
all: KeyFilter;
|
||||
}
|
||||
|
||||
interface Match {
|
||||
(regexp: RegExp|string, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Keys {
|
||||
(...keys: string[]): Assertion;
|
||||
(keys: any[]): Assertion;
|
||||
(keys: Object): Assertion;
|
||||
}
|
||||
|
||||
interface Throw {
|
||||
(): Assertion;
|
||||
(expected: string, message?: string): Assertion;
|
||||
(expected: RegExp, message?: string): Assertion;
|
||||
(constructor: Error, expected?: string, message?: string): Assertion;
|
||||
(constructor: Error, expected?: RegExp, message?: string): Assertion;
|
||||
(constructor: Function, expected?: string, message?: string): Assertion;
|
||||
(constructor: Function, expected?: RegExp, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface RespondTo {
|
||||
(method: string, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Satisfy {
|
||||
(matcher: Function, message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface Members {
|
||||
(set: any[], message?: string): Assertion;
|
||||
}
|
||||
|
||||
interface PropertyChange {
|
||||
(object: Object, prop: string, msg?: string): Assertion;
|
||||
}
|
||||
|
||||
export interface Assert {
|
||||
/**
|
||||
* @param expression Expression to test for truthiness.
|
||||
* @param message Message to display on error.
|
||||
*/
|
||||
(expression: any, message?: string): void;
|
||||
|
||||
fail(actual?: any, expected?: any, msg?: string, operator?: string): void;
|
||||
|
||||
ok(val: any, msg?: string): void;
|
||||
isOk(val: any, msg?: string): void;
|
||||
notOk(val: any, msg?: string): void;
|
||||
isNotOk(val: any, msg?: string): void;
|
||||
|
||||
equal(act: any, exp: any, msg?: string): void;
|
||||
notEqual(act: any, exp: any, msg?: string): void;
|
||||
|
||||
strictEqual(act: any, exp: any, msg?: string): void;
|
||||
notStrictEqual(act: any, exp: any, msg?: string): void;
|
||||
|
||||
deepEqual(act: any, exp: any, msg?: string): void;
|
||||
notDeepEqual(act: any, exp: any, msg?: string): void;
|
||||
|
||||
isTrue(val: any, msg?: string): void;
|
||||
isFalse(val: any, msg?: string): void;
|
||||
|
||||
isNull(val: any, msg?: string): void;
|
||||
isNotNull(val: any, msg?: string): void;
|
||||
|
||||
isUndefined(val: any, msg?: string): void;
|
||||
isDefined(val: any, msg?: string): void;
|
||||
|
||||
isNaN(val: any, msg?: string): void;
|
||||
isNotNaN(val: any, msg?: string): void;
|
||||
|
||||
isAbove(val: number, abv: number, msg?: string): void;
|
||||
isBelow(val: number, blw: number, msg?: string): void;
|
||||
|
||||
isFunction(val: any, msg?: string): void;
|
||||
isNotFunction(val: any, msg?: string): void;
|
||||
|
||||
isObject(val: any, msg?: string): void;
|
||||
isNotObject(val: any, msg?: string): void;
|
||||
|
||||
isArray(val: any, msg?: string): void;
|
||||
isNotArray(val: any, msg?: string): void;
|
||||
|
||||
isString(val: any, msg?: string): void;
|
||||
isNotString(val: any, msg?: string): void;
|
||||
|
||||
isNumber(val: any, msg?: string): void;
|
||||
isNotNumber(val: any, msg?: string): void;
|
||||
|
||||
isBoolean(val: any, msg?: string): void;
|
||||
isNotBoolean(val: any, msg?: string): void;
|
||||
|
||||
typeOf(val: any, type: string, msg?: string): void;
|
||||
notTypeOf(val: any, type: string, msg?: string): void;
|
||||
|
||||
instanceOf(val: any, type: Function, msg?: string): void;
|
||||
notInstanceOf(val: any, type: Function, msg?: string): void;
|
||||
|
||||
include(exp: string, inc: any, msg?: string): void;
|
||||
include(exp: any[], inc: any, msg?: string): void;
|
||||
|
||||
notInclude(exp: string, inc: any, msg?: string): void;
|
||||
notInclude(exp: any[], inc: any, msg?: string): void;
|
||||
|
||||
match(exp: any, re: RegExp, msg?: string): void;
|
||||
notMatch(exp: any, re: RegExp, msg?: string): void;
|
||||
|
||||
property(obj: Object, prop: string, msg?: string): void;
|
||||
notProperty(obj: Object, prop: string, msg?: string): void;
|
||||
deepProperty(obj: Object, prop: string, msg?: string): void;
|
||||
notDeepProperty(obj: Object, prop: string, msg?: string): void;
|
||||
|
||||
propertyVal(obj: Object, prop: string, val: any, msg?: string): void;
|
||||
propertyNotVal(obj: Object, prop: string, val: any, msg?: string): void;
|
||||
|
||||
deepPropertyVal(obj: Object, prop: string, val: any, msg?: string): void;
|
||||
deepPropertyNotVal(obj: Object, prop: string, val: any, msg?: string): void;
|
||||
|
||||
lengthOf(exp: any, len: number, msg?: string): void;
|
||||
//alias frenzy
|
||||
throw(fn: Function, msg?: string): void;
|
||||
throw(fn: Function, regExp: RegExp): void;
|
||||
throw(fn: Function, errType: Function, msg?: string): void;
|
||||
throw(fn: Function, errType: Function, regExp: RegExp): void;
|
||||
|
||||
throws(fn: Function, msg?: string): void;
|
||||
throws(fn: Function, regExp: RegExp): void;
|
||||
throws(fn: Function, errType: Function, msg?: string): void;
|
||||
throws(fn: Function, errType: Function, regExp: RegExp): void;
|
||||
|
||||
Throw(fn: Function, msg?: string): void;
|
||||
Throw(fn: Function, regExp: RegExp): void;
|
||||
Throw(fn: Function, errType: Function, msg?: string): void;
|
||||
Throw(fn: Function, errType: Function, regExp: RegExp): void;
|
||||
|
||||
doesNotThrow(fn: Function, msg?: string): void;
|
||||
doesNotThrow(fn: Function, regExp: RegExp): void;
|
||||
doesNotThrow(fn: Function, errType: Function, msg?: string): void;
|
||||
doesNotThrow(fn: Function, errType: Function, regExp: RegExp): void;
|
||||
|
||||
operator(val: any, operator: string, val2: any, msg?: string): void;
|
||||
closeTo(act: number, exp: number, delta: number, msg?: string): void;
|
||||
|
||||
sameMembers(set1: any[], set2: any[], msg?: string): void;
|
||||
sameDeepMembers(set1: any[], set2: any[], msg?: string): void;
|
||||
includeMembers(superset: any[], subset: any[], msg?: string): void;
|
||||
|
||||
ifError(val: any, msg?: string): void;
|
||||
|
||||
isExtensible(obj: {}, msg?: string): void;
|
||||
extensible(obj: {}, msg?: string): void;
|
||||
isNotExtensible(obj: {}, msg?: string): void;
|
||||
notExtensible(obj: {}, msg?: string): void;
|
||||
|
||||
isSealed(obj: {}, msg?: string): void;
|
||||
sealed(obj: {}, msg?: string): void;
|
||||
isNotSealed(obj: {}, msg?: string): void;
|
||||
notSealed(obj: {}, msg?: string): void;
|
||||
|
||||
isFrozen(obj: Object, msg?: string): void;
|
||||
frozen(obj: Object, msg?: string): void;
|
||||
isNotFrozen(obj: Object, msg?: string): void;
|
||||
notFrozen(obj: Object, msg?: string): void;
|
||||
|
||||
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
includeStack: boolean;
|
||||
}
|
||||
|
||||
export class AssertionError {
|
||||
constructor(message: string, _props?: any, ssf?: Function);
|
||||
name: string;
|
||||
message: string;
|
||||
showDiff: boolean;
|
||||
stack: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare var chai: Chai.ChaiStatic;
|
||||
|
||||
declare module "chai" {
|
||||
export = chai;
|
||||
}
|
||||
|
||||
interface Object {
|
||||
should: Chai.Assertion;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// Type definitions for es6-collections v0.5.1
|
||||
// Project: https://github.com/WebReflection/es6-collections/
|
||||
// Definitions by: Ron Buckton <http://github.com/rbuckton>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
/* *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
interface IteratorResult<T> {
|
||||
done: boolean;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
interface Iterator<T> {
|
||||
next(value?: any): IteratorResult<T>;
|
||||
return?(value?: any): IteratorResult<T>;
|
||||
throw?(e?: any): IteratorResult<T>;
|
||||
}
|
||||
|
||||
interface ForEachable<T> {
|
||||
forEach(callbackfn: (value: T) => void): void;
|
||||
}
|
||||
|
||||
interface Map<K, V> {
|
||||
clear(): void;
|
||||
delete(key: K): boolean;
|
||||
forEach(callbackfn: (value: V, index: K, map: Map<K, V>) => void, thisArg?: any): void;
|
||||
get(key: K): V;
|
||||
has(key: K): boolean;
|
||||
set(key: K, value?: V): Map<K, V>;
|
||||
entries(): Iterator<[K, V]>;
|
||||
keys(): Iterator<K>;
|
||||
values(): Iterator<V>;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface MapConstructor {
|
||||
new <K, V>(): Map<K, V>;
|
||||
new <K, V>(iterable: ForEachable<[K, V]>): Map<K, V>;
|
||||
prototype: Map<any, any>;
|
||||
}
|
||||
|
||||
declare var Map: MapConstructor;
|
||||
|
||||
interface Set<T> {
|
||||
add(value: T): Set<T>;
|
||||
clear(): void;
|
||||
delete(value: T): boolean;
|
||||
forEach(callbackfn: (value: T, index: T, set: Set<T>) => void, thisArg?: any): void;
|
||||
has(value: T): boolean;
|
||||
entries(): Iterator<[T, T]>;
|
||||
keys(): Iterator<T>;
|
||||
values(): Iterator<T>;
|
||||
size: number;
|
||||
}
|
||||
|
||||
interface SetConstructor {
|
||||
new <T>(): Set<T>;
|
||||
new <T>(iterable: ForEachable<T>): Set<T>;
|
||||
prototype: Set<any>;
|
||||
}
|
||||
|
||||
declare var Set: SetConstructor;
|
||||
|
||||
interface WeakMap<K, V> {
|
||||
delete(key: K): boolean;
|
||||
clear(): void;
|
||||
get(key: K): V;
|
||||
has(key: K): boolean;
|
||||
set(key: K, value?: V): WeakMap<K, V>;
|
||||
}
|
||||
|
||||
interface WeakMapConstructor {
|
||||
new <K, V>(): WeakMap<K, V>;
|
||||
new <K, V>(iterable: ForEachable<[K, V]>): WeakMap<K, V>;
|
||||
prototype: WeakMap<any, any>;
|
||||
}
|
||||
|
||||
declare var WeakMap: WeakMapConstructor;
|
||||
|
||||
interface WeakSet<T> {
|
||||
delete(value: T): boolean;
|
||||
clear(): void;
|
||||
add(value: T): WeakSet<T>;
|
||||
has(value: T): boolean;
|
||||
}
|
||||
|
||||
interface WeakSetConstructor {
|
||||
new <T>(): WeakSet<T>;
|
||||
new <T>(iterable: ForEachable<T>): WeakSet<T>;
|
||||
prototype: WeakSet<any>;
|
||||
}
|
||||
|
||||
declare var WeakSet: WeakSetConstructor;
|
||||
|
||||
declare module "es6-collections" {
|
||||
var Map: MapConstructor;
|
||||
var Set: SetConstructor;
|
||||
var WeakMap: WeakMapConstructor;
|
||||
var WeakSet: WeakSetConstructor;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Type definitions for es6-promise
|
||||
// Project: https://github.com/jakearchibald/ES6-Promise
|
||||
// Definitions by: François de Campredon <https://github.com/fdecampredon/>, vvakame <https://github.com/vvakame>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface Thenable<R> {
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Thenable<U>;
|
||||
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
|
||||
}
|
||||
|
||||
declare class Promise<R> implements Thenable<R> {
|
||||
/**
|
||||
* If you call resolve in the body of the callback passed to the constructor,
|
||||
* your promise is fulfilled with result object passed to resolve.
|
||||
* If you call reject your promise is rejected with the object passed to reject.
|
||||
* For consistency and debugging (eg stack traces), obj should be an instanceof Error.
|
||||
* Any errors thrown in the constructor callback will be implicitly passed to reject().
|
||||
*/
|
||||
constructor(callback: (resolve : (value?: R | Thenable<R>) => void, reject: (error?: any) => void) => void);
|
||||
|
||||
/**
|
||||
* onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects.
|
||||
* Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called.
|
||||
* Both callbacks have a single parameter , the fulfillment value or rejection reason.
|
||||
* "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve.
|
||||
* If an error is thrown in the callback, the returned promise rejects with that error.
|
||||
*
|
||||
* @param onFulfilled called when/if "promise" resolves
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
then<U>(onFulfilled?: (value: R) => U | Thenable<U>, onRejected?: (error: any) => void): Promise<U>;
|
||||
|
||||
/**
|
||||
* Sugar for promise.then(undefined, onRejected)
|
||||
*
|
||||
* @param onRejected called when/if "promise" rejects
|
||||
*/
|
||||
catch<U>(onRejected?: (error: any) => U | Thenable<U>): Promise<U>;
|
||||
}
|
||||
|
||||
declare module Promise {
|
||||
/**
|
||||
* Make a new promise from the thenable.
|
||||
* A thenable is promise-like in as far as it has a "then" method.
|
||||
*/
|
||||
function resolve<R>(value?: R | Thenable<R>): Promise<R>;
|
||||
|
||||
/**
|
||||
* Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
|
||||
*/
|
||||
function reject(error: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects.
|
||||
* the array passed to all can be a mixture of promise-like objects and other objects.
|
||||
* The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
|
||||
*/
|
||||
function all<R>(promises: (R | Thenable<R>)[]): Promise<R[]>;
|
||||
|
||||
/**
|
||||
* Make a Promise that fulfills when any item fulfills, and rejects if any item rejects.
|
||||
*/
|
||||
function race<R>(promises: (R | Thenable<R>)[]): Promise<R>;
|
||||
}
|
||||
|
||||
declare module 'es6-promise' {
|
||||
var foo: typeof Promise; // Temp variable to reference Promise in local context
|
||||
module rsvp {
|
||||
export var Promise: typeof foo;
|
||||
}
|
||||
export = rsvp;
|
||||
}
|
|
@ -0,0 +1,631 @@
|
|||
// Type definitions for Knockout v3.2.0
|
||||
// Project: http://knockoutjs.com
|
||||
// Definitions by: Boris Yankov <https://github.com/borisyankov/>, Igor Oleinikov <https://github.com/Igorbek/>, Clément Bourgeois <https://github.com/moonpyk/>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
|
||||
interface KnockoutSubscribableFunctions<T> {
|
||||
[key: string]: KnockoutBindingHandler;
|
||||
|
||||
notifySubscribers(valueToWrite?: T, event?: string): void;
|
||||
}
|
||||
|
||||
interface KnockoutComputedFunctions<T> {
|
||||
[key: string]: KnockoutBindingHandler;
|
||||
}
|
||||
|
||||
interface KnockoutObservableFunctions<T> {
|
||||
[key: string]: KnockoutBindingHandler;
|
||||
|
||||
equalityComparer(a: any, b: any): boolean;
|
||||
}
|
||||
|
||||
interface KnockoutObservableArrayFunctions<T> {
|
||||
// General Array functions
|
||||
indexOf(searchElement: T, fromIndex?: number): number;
|
||||
slice(start: number, end?: number): T[];
|
||||
splice(start: number): T[];
|
||||
splice(start: number, deleteCount: number, ...items: T[]): T[];
|
||||
pop(): T;
|
||||
push(...items: T[]): void;
|
||||
shift(): T;
|
||||
unshift(...items: T[]): number;
|
||||
reverse(): KnockoutObservableArray<T>;
|
||||
sort(): KnockoutObservableArray<T>;
|
||||
sort(compareFunction: (left: T, right: T) => number): KnockoutObservableArray<T>;
|
||||
|
||||
// Ko specific
|
||||
[key: string]: KnockoutBindingHandler;
|
||||
|
||||
replace(oldItem: T, newItem: T): void;
|
||||
|
||||
remove(item: T): T[];
|
||||
remove(removeFunction: (item: T) => boolean): T[];
|
||||
removeAll(items: T[]): T[];
|
||||
removeAll(): T[];
|
||||
|
||||
destroy(item: T): void;
|
||||
destroy(destroyFunction: (item: T) => boolean): void;
|
||||
destroyAll(items: T[]): void;
|
||||
destroyAll(): void;
|
||||
}
|
||||
|
||||
interface KnockoutSubscribableStatic {
|
||||
fn: KnockoutSubscribableFunctions<any>;
|
||||
|
||||
new <T>(): KnockoutSubscribable<T>;
|
||||
}
|
||||
|
||||
interface KnockoutSubscription {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
interface KnockoutSubscribable<T> extends KnockoutSubscribableFunctions<T> {
|
||||
subscribe(callback: (newValue: T) => void, target?: any, event?: string): KnockoutSubscription;
|
||||
subscribe<TEvent>(callback: (newValue: TEvent) => void, target: any, event: string): KnockoutSubscription;
|
||||
extend(requestedExtenders: { [key: string]: any; }): KnockoutSubscribable<T>;
|
||||
getSubscriptionsCount(): number;
|
||||
}
|
||||
|
||||
interface KnockoutComputedStatic {
|
||||
fn: KnockoutComputedFunctions<any>;
|
||||
|
||||
<T>(): KnockoutComputed<T>;
|
||||
<T>(func: () => T, context?: any, options?: any): KnockoutComputed<T>;
|
||||
<T>(def: KnockoutComputedDefine<T>, context?: any): KnockoutComputed<T>;
|
||||
}
|
||||
|
||||
interface KnockoutComputed<T> extends KnockoutObservable<T>, KnockoutComputedFunctions<T> {
|
||||
fn: KnockoutComputedFunctions<any>;
|
||||
|
||||
dispose(): void;
|
||||
isActive(): boolean;
|
||||
getDependenciesCount(): number;
|
||||
extend(requestedExtenders: { [key: string]: any; }): KnockoutComputed<T>;
|
||||
}
|
||||
|
||||
interface KnockoutObservableArrayStatic {
|
||||
fn: KnockoutObservableArrayFunctions<any>;
|
||||
|
||||
<T>(value?: T[]): KnockoutObservableArray<T>;
|
||||
}
|
||||
|
||||
interface KnockoutObservableArray<T> extends KnockoutObservable<T[]>, KnockoutObservableArrayFunctions<T> {
|
||||
extend(requestedExtenders: { [key: string]: any; }): KnockoutObservableArray<T>;
|
||||
}
|
||||
|
||||
interface KnockoutObservableStatic {
|
||||
fn: KnockoutObservableFunctions<any>;
|
||||
|
||||
<T>(value?: T): KnockoutObservable<T>;
|
||||
}
|
||||
|
||||
interface KnockoutObservable<T> extends KnockoutSubscribable<T>, KnockoutObservableFunctions<T> {
|
||||
(): T;
|
||||
(value: T): void;
|
||||
|
||||
peek(): T;
|
||||
valueHasMutated?:{(): void;};
|
||||
valueWillMutate?:{(): void;};
|
||||
extend(requestedExtenders: { [key: string]: any; }): KnockoutObservable<T>;
|
||||
}
|
||||
|
||||
interface KnockoutComputedDefine<T> {
|
||||
read(): T;
|
||||
write? (value: T): void;
|
||||
disposeWhenNodeIsRemoved?: Node;
|
||||
disposeWhen? (): boolean;
|
||||
owner?: any;
|
||||
deferEvaluation?: boolean;
|
||||
pure?: boolean;
|
||||
}
|
||||
|
||||
interface KnockoutBindingContext {
|
||||
$parent: any;
|
||||
$parents: any[];
|
||||
$root: any;
|
||||
$data: any;
|
||||
$rawData: any | KnockoutObservable<any>;
|
||||
$index?: KnockoutObservable<number>;
|
||||
$parentContext?: KnockoutBindingContext;
|
||||
$component: any;
|
||||
$componentTemplateNodes: Node[];
|
||||
|
||||
extend(properties: any): any;
|
||||
createChildContext(dataItemOrAccessor: any, dataItemAlias?: any, extendCallback?: Function): any;
|
||||
}
|
||||
|
||||
interface KnockoutAllBindingsAccessor {
|
||||
(): any;
|
||||
get(name: string): any;
|
||||
has(name: string): boolean;
|
||||
}
|
||||
|
||||
interface KnockoutBindingHandler {
|
||||
after?: Array<string>;
|
||||
init?: (element: any, valueAccessor: () => any, allBindingsAccessor?: KnockoutAllBindingsAccessor, viewModel?: any, bindingContext?: KnockoutBindingContext) => void | { controlsDescendantBindings: boolean; };
|
||||
update?: (element: any, valueAccessor: () => any, allBindingsAccessor?: KnockoutAllBindingsAccessor, viewModel?: any, bindingContext?: KnockoutBindingContext) => void;
|
||||
options?: any;
|
||||
preprocess?: (value: string, name: string, addBindingCallback?: (name: string, value: string) => void) => string;
|
||||
}
|
||||
|
||||
interface KnockoutBindingHandlers {
|
||||
[bindingHandler: string]: KnockoutBindingHandler;
|
||||
|
||||
// Controlling text and appearance
|
||||
visible: KnockoutBindingHandler;
|
||||
text: KnockoutBindingHandler;
|
||||
html: KnockoutBindingHandler;
|
||||
css: KnockoutBindingHandler;
|
||||
style: KnockoutBindingHandler;
|
||||
attr: KnockoutBindingHandler;
|
||||
|
||||
// Control Flow
|
||||
foreach: KnockoutBindingHandler;
|
||||
if: KnockoutBindingHandler;
|
||||
ifnot: KnockoutBindingHandler;
|
||||
with: KnockoutBindingHandler;
|
||||
|
||||
// Working with form fields
|
||||
click: KnockoutBindingHandler;
|
||||
event: KnockoutBindingHandler;
|
||||
submit: KnockoutBindingHandler;
|
||||
enable: KnockoutBindingHandler;
|
||||
disable: KnockoutBindingHandler;
|
||||
value: KnockoutBindingHandler;
|
||||
textInput: KnockoutBindingHandler;
|
||||
hasfocus: KnockoutBindingHandler;
|
||||
checked: KnockoutBindingHandler;
|
||||
options: KnockoutBindingHandler;
|
||||
selectedOptions: KnockoutBindingHandler;
|
||||
uniqueName: KnockoutBindingHandler;
|
||||
|
||||
// Rendering templates
|
||||
template: KnockoutBindingHandler;
|
||||
|
||||
// Components (new for v3.2)
|
||||
component: KnockoutBindingHandler;
|
||||
}
|
||||
|
||||
interface KnockoutMemoization {
|
||||
memoize(callback: () => string): string;
|
||||
unmemoize(memoId: string, callbackParams: any[]): boolean;
|
||||
unmemoizeDomNodeAndDescendants(domNode: any, extraCallbackParamsArray: any[]): boolean;
|
||||
parseMemoText(memoText: string): string;
|
||||
}
|
||||
|
||||
interface KnockoutVirtualElement {}
|
||||
|
||||
interface KnockoutVirtualElements {
|
||||
allowedBindings: { [bindingName: string]: boolean; };
|
||||
emptyNode(node: KnockoutVirtualElement ): void;
|
||||
firstChild(node: KnockoutVirtualElement ): KnockoutVirtualElement;
|
||||
insertAfter( container: KnockoutVirtualElement, nodeToInsert: Node, insertAfter: Node ): void;
|
||||
nextSibling(node: KnockoutVirtualElement): Node;
|
||||
prepend(node: KnockoutVirtualElement, toInsert: Node ): void;
|
||||
setDomNodeChildren(node: KnockoutVirtualElement, newChildren: { length: number;[index: number]: Node; } ): void;
|
||||
childNodes(node: KnockoutVirtualElement ): Node[];
|
||||
}
|
||||
|
||||
interface KnockoutExtenders {
|
||||
throttle(target: any, timeout: number): KnockoutComputed<any>;
|
||||
notify(target: any, notifyWhen: string): any;
|
||||
|
||||
rateLimit(target: any, timeout: number): any;
|
||||
rateLimit(target: any, options: { timeout: number; method?: string; }): any;
|
||||
|
||||
trackArrayChanges(target: any): any;
|
||||
}
|
||||
|
||||
//
|
||||
// NOTE TO MAINTAINERS AND CONTRIBUTORS : pay attention to only include symbols that are
|
||||
// publicly exported in the minified version of ko, without that you can give the false
|
||||
// impression that some functions will be available in production builds.
|
||||
//
|
||||
interface KnockoutUtils {
|
||||
//////////////////////////////////
|
||||
// utils.domData.js
|
||||
//////////////////////////////////
|
||||
|
||||
domData: {
|
||||
get (node: Element, key: string): any;
|
||||
|
||||
set (node: Element, key: string, value: any): void;
|
||||
|
||||
getAll(node: Element, createIfNotFound: boolean): any;
|
||||
|
||||
clear(node: Element): boolean;
|
||||
};
|
||||
|
||||
//////////////////////////////////
|
||||
// utils.domNodeDisposal.js
|
||||
//////////////////////////////////
|
||||
|
||||
domNodeDisposal: {
|
||||
addDisposeCallback(node: Element, callback: Function): void;
|
||||
|
||||
removeDisposeCallback(node: Element, callback: Function): void;
|
||||
|
||||
cleanNode(node: Node): Element;
|
||||
|
||||
removeNode(node: Node): void;
|
||||
};
|
||||
|
||||
addOrRemoveItem<T>(array: T[] | KnockoutObservable<T>, value: T, included: T): void;
|
||||
|
||||
arrayFilter<T>(array: T[], predicate: (item: T) => boolean): T[];
|
||||
|
||||
arrayFirst<T>(array: T[], predicate: (item: T) => boolean, predicateOwner?: any): T;
|
||||
|
||||
arrayForEach<T>(array: T[], action: (item: T, index: number) => void): void;
|
||||
|
||||
arrayGetDistinctValues<T>(array: T[]): T[];
|
||||
|
||||
arrayIndexOf<T>(array: T[], item: T): number;
|
||||
|
||||
arrayMap<T, U>(array: T[], mapping: (item: T) => U): U[];
|
||||
|
||||
arrayPushAll<T>(array: T[] | KnockoutObservableArray<T>, valuesToPush: T[]): T[];
|
||||
|
||||
arrayRemoveItem(array: any[], itemToRemove: any): void;
|
||||
|
||||
compareArrays<T>(a: T[], b: T[]): Array<KnockoutArrayChange<T>>;
|
||||
|
||||
extend(target: Object, source: Object): Object;
|
||||
|
||||
fieldsIncludedWithJsonPost: any[];
|
||||
|
||||
getFormFields(form: any, fieldName: string): any[];
|
||||
|
||||
objectForEach(obj: any, action: (key: any, value: any) => void): void;
|
||||
|
||||
parseHtmlFragment(html: string): any[];
|
||||
|
||||
parseJson(jsonString: string): any;
|
||||
|
||||
postJson(urlOrForm: any, data: any, options: any): void;
|
||||
|
||||
peekObservable<T>(value: KnockoutObservable<T>): T;
|
||||
|
||||
range(min: any, max: any): any;
|
||||
|
||||
registerEventHandler(element: any, eventType: any, handler: Function): void;
|
||||
|
||||
setHtml(node: Element, html: () => string): void;
|
||||
|
||||
setHtml(node: Element, html: string): void;
|
||||
|
||||
setTextContent(element: any, textContent: string | KnockoutObservable<string>): void;
|
||||
|
||||
stringifyJson(data: any, replacer?: Function, space?: string): string;
|
||||
|
||||
toggleDomNodeCssClass(node: any, className: string, shouldHaveClass: boolean): void;
|
||||
|
||||
triggerEvent(element: any, eventType: any): void;
|
||||
|
||||
unwrapObservable<T>(value: KnockoutObservable<T> | T): T;
|
||||
|
||||
// NOT PART OF THE MINIFIED API SURFACE (ONLY IN knockout-{version}.debug.js) https://github.com/SteveSanderson/knockout/issues/670
|
||||
// forceRefresh(node: any): void;
|
||||
// ieVersion: number;
|
||||
// isIe6: boolean;
|
||||
// isIe7: boolean;
|
||||
// jQueryHtmlParse(html: string): any[];
|
||||
// makeArray(arrayLikeObject: any): any[];
|
||||
// moveCleanedNodesToContainerElement(nodes: any[]): HTMLElement;
|
||||
// replaceDomNodes(nodeToReplaceOrNodeArray: any, newNodesArray: any[]): void;
|
||||
// setDomNodeChildren(domNode: any, childNodes: any[]): void;
|
||||
// setElementName(element: any, name: string): void;
|
||||
// setOptionNodeSelectionState(optionNode: any, isSelected: boolean): void;
|
||||
// simpleHtmlParse(html: string): any[];
|
||||
// stringStartsWith(str: string, startsWith: string): boolean;
|
||||
// stringTokenize(str: string, delimiter: string): string[];
|
||||
// stringTrim(str: string): string;
|
||||
// tagNameLower(element: any): string;
|
||||
}
|
||||
|
||||
interface KnockoutArrayChange<T> {
|
||||
status: string;
|
||||
value: T;
|
||||
index: number;
|
||||
moved?: number;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// templateSources.js
|
||||
//////////////////////////////////
|
||||
|
||||
interface KnockoutTemplateSourcesDomElement {
|
||||
text(): any;
|
||||
text(value: any): void;
|
||||
|
||||
data(key: string): any;
|
||||
data(key: string, value: any): any;
|
||||
}
|
||||
|
||||
interface KnockoutTemplateAnonymous extends KnockoutTemplateSourcesDomElement {
|
||||
nodes(): any;
|
||||
nodes(value: any): void;
|
||||
}
|
||||
|
||||
interface KnockoutTemplateSources {
|
||||
|
||||
domElement: {
|
||||
prototype: KnockoutTemplateSourcesDomElement
|
||||
new (element: Element): KnockoutTemplateSourcesDomElement
|
||||
};
|
||||
|
||||
anonymousTemplate: {
|
||||
prototype: KnockoutTemplateAnonymous;
|
||||
new (element: Element): KnockoutTemplateAnonymous;
|
||||
};
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// nativeTemplateEngine.js
|
||||
//////////////////////////////////
|
||||
|
||||
interface KnockoutNativeTemplateEngine {
|
||||
|
||||
renderTemplateSource(templateSource: Object, bindingContext?: KnockoutBindingContext, options?: Object): any[];
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// templateEngine.js
|
||||
//////////////////////////////////
|
||||
|
||||
interface KnockoutTemplateEngine extends KnockoutNativeTemplateEngine {
|
||||
|
||||
createJavaScriptEvaluatorBlock(script: string): string;
|
||||
|
||||
makeTemplateSource(template: any, templateDocument?: Document): any;
|
||||
|
||||
renderTemplate(template: any, bindingContext: KnockoutBindingContext, options: Object, templateDocument: Document): any;
|
||||
|
||||
isTemplateRewritten(template: any, templateDocument: Document): boolean;
|
||||
|
||||
rewriteTemplate(template: any, rewriterCallback: Function, templateDocument: Document): void;
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
|
||||
interface KnockoutStatic {
|
||||
utils: KnockoutUtils;
|
||||
memoization: KnockoutMemoization;
|
||||
|
||||
bindingHandlers: KnockoutBindingHandlers;
|
||||
getBindingHandler(handler: string): KnockoutBindingHandler;
|
||||
|
||||
virtualElements: KnockoutVirtualElements;
|
||||
extenders: KnockoutExtenders;
|
||||
|
||||
applyBindings(viewModelOrBindingContext?: any, rootNode?: any): void;
|
||||
applyBindingsToDescendants(viewModelOrBindingContext: any, rootNode: any): void;
|
||||
applyBindingAccessorsToNode(node: Node, bindings: (bindingContext: KnockoutBindingContext, node: Node) => {}, bindingContext: KnockoutBindingContext): void;
|
||||
applyBindingAccessorsToNode(node: Node, bindings: {}, bindingContext: KnockoutBindingContext): void;
|
||||
applyBindingAccessorsToNode(node: Node, bindings: (bindingContext: KnockoutBindingContext, node: Node) => {}, viewModel: any): void;
|
||||
applyBindingAccessorsToNode(node: Node, bindings: {}, viewModel: any): void;
|
||||
applyBindingsToNode(node: Node, bindings: any, viewModelOrBindingContext?: any): any;
|
||||
|
||||
subscribable: KnockoutSubscribableStatic;
|
||||
observable: KnockoutObservableStatic;
|
||||
|
||||
computed: KnockoutComputedStatic;
|
||||
pureComputed<T>(evaluatorFunction: () => T, context?: any): KnockoutComputed<T>;
|
||||
pureComputed<T>(options: KnockoutComputedDefine<T>, context?: any): KnockoutComputed<T>;
|
||||
|
||||
observableArray: KnockoutObservableArrayStatic;
|
||||
|
||||
contextFor(node: any): any;
|
||||
isSubscribable(instance: any): boolean;
|
||||
toJSON(viewModel: any, replacer?: Function, space?: any): string;
|
||||
toJS(viewModel: any): any;
|
||||
isObservable(instance: any): boolean;
|
||||
isWriteableObservable(instance: any): boolean;
|
||||
isComputed(instance: any): boolean;
|
||||
dataFor(node: any): any;
|
||||
removeNode(node: Element): void;
|
||||
cleanNode(node: Element): Element;
|
||||
renderTemplate(template: Function, viewModel: any, options?: any, target?: any, renderMode?: any): any;
|
||||
renderTemplate(template: string, viewModel: any, options?: any, target?: any, renderMode?: any): any;
|
||||
unwrap<T>(value: KnockoutObservable<T> | T): T;
|
||||
|
||||
computedContext: KnockoutComputedContext;
|
||||
|
||||
//////////////////////////////////
|
||||
// templateSources.js
|
||||
//////////////////////////////////
|
||||
|
||||
templateSources: KnockoutTemplateSources;
|
||||
|
||||
//////////////////////////////////
|
||||
// templateEngine.js
|
||||
//////////////////////////////////
|
||||
|
||||
templateEngine: {
|
||||
|
||||
prototype: KnockoutTemplateEngine;
|
||||
|
||||
new (): KnockoutTemplateEngine;
|
||||
};
|
||||
|
||||
//////////////////////////////////
|
||||
// templateRewriting.js
|
||||
//////////////////////////////////
|
||||
|
||||
templateRewriting: {
|
||||
|
||||
ensureTemplateIsRewritten(template: Node, templateEngine: KnockoutTemplateEngine, templateDocument: Document): any;
|
||||
ensureTemplateIsRewritten(template: string, templateEngine: KnockoutTemplateEngine, templateDocument: Document): any;
|
||||
|
||||
memoizeBindingAttributeSyntax(htmlString: string, templateEngine: KnockoutTemplateEngine): any;
|
||||
|
||||
applyMemoizedBindingsToNextSibling(bindings: any, nodeName: string): string;
|
||||
};
|
||||
|
||||
//////////////////////////////////
|
||||
// nativeTemplateEngine.js
|
||||
//////////////////////////////////
|
||||
|
||||
nativeTemplateEngine: {
|
||||
|
||||
prototype: KnockoutNativeTemplateEngine;
|
||||
|
||||
new (): KnockoutNativeTemplateEngine;
|
||||
|
||||
instance: KnockoutNativeTemplateEngine;
|
||||
};
|
||||
|
||||
//////////////////////////////////
|
||||
// jqueryTmplTemplateEngine.js
|
||||
//////////////////////////////////
|
||||
|
||||
jqueryTmplTemplateEngine: {
|
||||
|
||||
prototype: KnockoutTemplateEngine;
|
||||
|
||||
renderTemplateSource(templateSource: Object, bindingContext: KnockoutBindingContext, options: Object): Node[];
|
||||
|
||||
createJavaScriptEvaluatorBlock(script: string): string;
|
||||
|
||||
addTemplate(templateName: string, templateMarkup: string): void;
|
||||
};
|
||||
|
||||
//////////////////////////////////
|
||||
// templating.js
|
||||
//////////////////////////////////
|
||||
|
||||
setTemplateEngine(templateEngine: KnockoutNativeTemplateEngine): void;
|
||||
|
||||
renderTemplate(template: Function, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
|
||||
renderTemplate(template: any, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
|
||||
renderTemplate(template: Function, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
|
||||
renderTemplate(template: any, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node, renderMode: string): any;
|
||||
renderTemplate(template: Function, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
|
||||
renderTemplate(template: any, dataOrBindingContext: KnockoutBindingContext, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
|
||||
renderTemplate(template: Function, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
|
||||
renderTemplate(template: any, dataOrBindingContext: any, options: Object, targetNodeOrNodeArray: Node[], renderMode: string): any;
|
||||
|
||||
renderTemplateForEach(template: Function, arrayOrObservableArray: any[], options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
|
||||
renderTemplateForEach(template: any, arrayOrObservableArray: any[], options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
|
||||
renderTemplateForEach(template: Function, arrayOrObservableArray: KnockoutObservable<any>, options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
|
||||
renderTemplateForEach(template: any, arrayOrObservableArray: KnockoutObservable<any>, options: Object, targetNode: Node, parentBindingContext: KnockoutBindingContext): any;
|
||||
|
||||
expressionRewriting: {
|
||||
bindingRewriteValidators: any;
|
||||
parseObjectLiteral: { (objectLiteralString: string): any[] }
|
||||
};
|
||||
|
||||
/////////////////////////////////
|
||||
|
||||
bindingProvider: {
|
||||
instance: KnockoutBindingProvider;
|
||||
new (): KnockoutBindingProvider;
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
// selectExtensions.js
|
||||
/////////////////////////////////
|
||||
|
||||
selectExtensions: {
|
||||
|
||||
readValue(element: HTMLElement): any;
|
||||
|
||||
writeValue(element: HTMLElement, value: any): void;
|
||||
};
|
||||
|
||||
components: KnockoutComponents;
|
||||
}
|
||||
|
||||
interface KnockoutBindingProvider {
|
||||
nodeHasBindings(node: Node): boolean;
|
||||
getBindings(node: Node, bindingContext: KnockoutBindingContext): {};
|
||||
getBindingAccessors?(node: Node, bindingContext: KnockoutBindingContext): { [key: string]: string; };
|
||||
}
|
||||
|
||||
interface KnockoutComputedContext {
|
||||
getDependenciesCount(): number;
|
||||
isInitial: () => boolean;
|
||||
isSleeping: boolean;
|
||||
}
|
||||
|
||||
//
|
||||
// refactored types into a namespace to reduce global pollution
|
||||
// and used Union Types to simplify overloads (requires TypeScript 1.4)
|
||||
//
|
||||
declare module KnockoutComponentTypes {
|
||||
|
||||
interface Config {
|
||||
viewModel?: ViewModelFunction | ViewModelSharedInstance | ViewModelFactoryFunction | AMDModule;
|
||||
template: string | Node[]| DocumentFragment | TemplateElement | AMDModule;
|
||||
synchronous?: boolean;
|
||||
}
|
||||
|
||||
interface ComponentConfig {
|
||||
viewModel?: ViewModelFunction | ViewModelSharedInstance | ViewModelFactoryFunction | AMDModule;
|
||||
template: any;
|
||||
createViewModel?: any;
|
||||
}
|
||||
|
||||
interface EmptyConfig {
|
||||
}
|
||||
|
||||
// common AMD type
|
||||
interface AMDModule {
|
||||
require: string;
|
||||
}
|
||||
|
||||
// viewmodel types
|
||||
interface ViewModelFunction {
|
||||
(params?: any): any;
|
||||
}
|
||||
|
||||
interface ViewModelSharedInstance {
|
||||
instance: any;
|
||||
}
|
||||
|
||||
interface ViewModelFactoryFunction {
|
||||
createViewModel: (params?: any, componentInfo?: ComponentInfo) => any;
|
||||
}
|
||||
|
||||
interface ComponentInfo {
|
||||
element: Node;
|
||||
templateNodes: Node[];
|
||||
}
|
||||
|
||||
interface TemplateElement {
|
||||
element: string | Node;
|
||||
}
|
||||
|
||||
interface Loader {
|
||||
getConfig? (componentName: string, callback: (result: ComponentConfig) => void): void;
|
||||
loadComponent? (componentName: string, config: ComponentConfig, callback: (result: Definition) => void): void;
|
||||
loadTemplate? (componentName: string, templateConfig: any, callback: (result: Node[]) => void): void;
|
||||
loadViewModel? (componentName: string, viewModelConfig: any, callback: (result: any) => void): void;
|
||||
suppressLoaderExceptions?: boolean;
|
||||
}
|
||||
|
||||
interface Definition {
|
||||
template: Node[];
|
||||
createViewModel? (params: any, options: { element: Node; }): any;
|
||||
}
|
||||
}
|
||||
|
||||
interface KnockoutComponents {
|
||||
// overloads for register method:
|
||||
register(componentName: string, config: KnockoutComponentTypes.Config | KnockoutComponentTypes.EmptyConfig): void;
|
||||
|
||||
isRegistered(componentName: string): boolean;
|
||||
unregister(componentName: string): void;
|
||||
get(componentName: string, callback: (definition: KnockoutComponentTypes.Definition) => void): void;
|
||||
clearCachedDefinition(componentName: string): void
|
||||
defaultLoader: KnockoutComponentTypes.Loader;
|
||||
loaders: KnockoutComponentTypes.Loader[];
|
||||
getComponentNameForNode(node: Node): string;
|
||||
}
|
||||
|
||||
declare var ko: KnockoutStatic;
|
||||
|
||||
declare module "knockout" {
|
||||
export = ko;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,214 @@
|
|||
// Type definitions for mocha 2.2.5
|
||||
// Project: http://mochajs.org/
|
||||
// Definitions by: Kazi Manzur Rashid <https://github.com/kazimanzurrashid/>, otiai10 <https://github.com/otiai10>, jt000 <https://github.com/jt000>, Vadim Macagon <https://github.com/enlight>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface MochaSetupOptions {
|
||||
//milliseconds to wait before considering a test slow
|
||||
slow?: number;
|
||||
|
||||
// timeout in milliseconds
|
||||
timeout?: number;
|
||||
|
||||
// ui name "bdd", "tdd", "exports" etc
|
||||
ui?: string;
|
||||
|
||||
//array of accepted globals
|
||||
globals?: any[];
|
||||
|
||||
// reporter instance (function or string), defaults to `mocha.reporters.Spec`
|
||||
reporter?: any;
|
||||
|
||||
// bail on the first test failure
|
||||
bail?: boolean;
|
||||
|
||||
// ignore global leaks
|
||||
ignoreLeaks?: boolean;
|
||||
|
||||
// grep string or regexp to filter tests with
|
||||
grep?: any;
|
||||
}
|
||||
|
||||
interface MochaDone {
|
||||
(error?: Error): void;
|
||||
}
|
||||
|
||||
declare var mocha: Mocha;
|
||||
declare var describe: Mocha.IContextDefinition;
|
||||
declare var xdescribe: Mocha.IContextDefinition;
|
||||
// alias for `describe`
|
||||
declare var context: Mocha.IContextDefinition;
|
||||
// alias for `describe`
|
||||
declare var suite: Mocha.IContextDefinition;
|
||||
declare var it: Mocha.ITestDefinition;
|
||||
declare var xit: Mocha.ITestDefinition;
|
||||
// alias for `it`
|
||||
declare var test: Mocha.ITestDefinition;
|
||||
|
||||
declare function before(action: () => void): void;
|
||||
|
||||
declare function before(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function setup(action: () => void): void;
|
||||
|
||||
declare function setup(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function after(action: () => void): void;
|
||||
|
||||
declare function after(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function teardown(action: () => void): void;
|
||||
|
||||
declare function teardown(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function beforeEach(action: () => void): void;
|
||||
|
||||
declare function beforeEach(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function suiteSetup(action: () => void): void;
|
||||
|
||||
declare function suiteSetup(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function afterEach(action: () => void): void;
|
||||
|
||||
declare function afterEach(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare function suiteTeardown(action: () => void): void;
|
||||
|
||||
declare function suiteTeardown(action: (done: MochaDone) => void): void;
|
||||
|
||||
declare class Mocha {
|
||||
constructor(options?: {
|
||||
grep?: RegExp;
|
||||
ui?: string;
|
||||
reporter?: string;
|
||||
timeout?: number;
|
||||
bail?: boolean;
|
||||
});
|
||||
|
||||
/** Setup mocha with the given options. */
|
||||
setup(options: MochaSetupOptions): Mocha;
|
||||
bail(value?: boolean): Mocha;
|
||||
addFile(file: string): Mocha;
|
||||
/** Sets reporter by name, defaults to "spec". */
|
||||
reporter(name: string): Mocha;
|
||||
/** Sets reporter constructor, defaults to mocha.reporters.Spec. */
|
||||
reporter(reporter: (runner: Mocha.IRunner, options: any) => any): Mocha;
|
||||
ui(value: string): Mocha;
|
||||
grep(value: string): Mocha;
|
||||
grep(value: RegExp): Mocha;
|
||||
invert(): Mocha;
|
||||
ignoreLeaks(value: boolean): Mocha;
|
||||
checkLeaks(): Mocha;
|
||||
/** Enables growl support. */
|
||||
growl(): Mocha;
|
||||
globals(value: string): Mocha;
|
||||
globals(values: string[]): Mocha;
|
||||
useColors(value: boolean): Mocha;
|
||||
useInlineDiffs(value: boolean): Mocha;
|
||||
timeout(value: number): Mocha;
|
||||
slow(value: number): Mocha;
|
||||
enableTimeouts(value: boolean): Mocha;
|
||||
asyncOnly(value: boolean): Mocha;
|
||||
noHighlighting(value: boolean): Mocha;
|
||||
/** Runs tests and invokes `onComplete()` when finished. */
|
||||
run(onComplete?: (failures: number) => void): Mocha.IRunner;
|
||||
}
|
||||
|
||||
// merge the Mocha class declaration with a module
|
||||
declare module Mocha {
|
||||
/** Partial interface for Mocha's `Runnable` class. */
|
||||
interface IRunnable {
|
||||
title: string;
|
||||
fn: Function;
|
||||
async: boolean;
|
||||
sync: boolean;
|
||||
timedOut: boolean;
|
||||
}
|
||||
|
||||
/** Partial interface for Mocha's `Suite` class. */
|
||||
interface ISuite {
|
||||
parent: ISuite;
|
||||
title: string;
|
||||
|
||||
fullTitle(): string;
|
||||
}
|
||||
|
||||
/** Partial interface for Mocha's `Test` class. */
|
||||
interface ITest extends IRunnable {
|
||||
parent: ISuite;
|
||||
pending: boolean;
|
||||
|
||||
fullTitle(): string;
|
||||
}
|
||||
|
||||
/** Partial interface for Mocha's `Runner` class. */
|
||||
interface IRunner {}
|
||||
|
||||
interface IContextDefinition {
|
||||
(description: string, spec: () => void): ISuite;
|
||||
only(description: string, spec: () => void): ISuite;
|
||||
skip(description: string, spec: () => void): void;
|
||||
timeout(ms: number): void;
|
||||
}
|
||||
|
||||
interface ITestDefinition {
|
||||
(expectation: string, assertion?: () => void): ITest;
|
||||
(expectation: string, assertion?: (done: MochaDone) => void): ITest;
|
||||
only(expectation: string, assertion?: () => void): ITest;
|
||||
only(expectation: string, assertion?: (done: MochaDone) => void): ITest;
|
||||
skip(expectation: string, assertion?: () => void): void;
|
||||
skip(expectation: string, assertion?: (done: MochaDone) => void): void;
|
||||
timeout(ms: number): void;
|
||||
}
|
||||
|
||||
export module reporters {
|
||||
export class Base {
|
||||
stats: {
|
||||
suites: number;
|
||||
tests: number;
|
||||
passes: number;
|
||||
pending: number;
|
||||
failures: number;
|
||||
};
|
||||
|
||||
constructor(runner: IRunner);
|
||||
}
|
||||
|
||||
export class Doc extends Base {}
|
||||
export class Dot extends Base {}
|
||||
export class HTML extends Base {}
|
||||
export class HTMLCov extends Base {}
|
||||
export class JSON extends Base {}
|
||||
export class JSONCov extends Base {}
|
||||
export class JSONStream extends Base {}
|
||||
export class Landing extends Base {}
|
||||
export class List extends Base {}
|
||||
export class Markdown extends Base {}
|
||||
export class Min extends Base {}
|
||||
export class Nyan extends Base {}
|
||||
export class Progress extends Base {
|
||||
/**
|
||||
* @param options.open String used to indicate the start of the progress bar.
|
||||
* @param options.complete String used to indicate a complete test on the progress bar.
|
||||
* @param options.incomplete String used to indicate an incomplete test on the progress bar.
|
||||
* @param options.close String used to indicate the end of the progress bar.
|
||||
*/
|
||||
constructor(runner: IRunner, options?: {
|
||||
open?: string;
|
||||
complete?: string;
|
||||
incomplete?: string;
|
||||
close?: string;
|
||||
});
|
||||
}
|
||||
export class Spec extends Base {}
|
||||
export class TAP extends Base {}
|
||||
export class XUnit extends Base {
|
||||
constructor(runner: IRunner, options?: any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module "mocha" {
|
||||
export = Mocha;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
// Type definitions for React v0.14 (react-addons-css-transition-group)
|
||||
// Project: http://facebook.github.io/react/
|
||||
// Definitions by: Asana <https://asana.com>, AssureSign <http://www.assuresign.com>, Microsoft <https://microsoft.com>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
/// <reference path="react.d.ts" />
|
||||
|
||||
declare namespace __React {
|
||||
namespace __Addons {
|
||||
export function shallowCompare<P, S>(
|
||||
component: __React.Component<P, S>,
|
||||
nextProps: P,
|
||||
nextState: S): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-addons-shallow-compare" {
|
||||
export = __React.__Addons.shallowCompare;
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
// Type definitions for React v0.14 (react-addons-test-utils)
|
||||
// Project: http://facebook.github.io/react/
|
||||
// Definitions by: Asana <https://asana.com>, AssureSign <http://www.assuresign.com>, Microsoft <https://microsoft.com>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
/// <reference path="react.d.ts" />
|
||||
|
||||
declare namespace __React {
|
||||
interface SyntheticEventData {
|
||||
altKey?: boolean;
|
||||
button?: number;
|
||||
buttons?: number;
|
||||
clientX?: number;
|
||||
clientY?: number;
|
||||
changedTouches?: TouchList;
|
||||
charCode?: boolean;
|
||||
clipboardData?: DataTransfer;
|
||||
ctrlKey?: boolean;
|
||||
deltaMode?: number;
|
||||
deltaX?: number;
|
||||
deltaY?: number;
|
||||
deltaZ?: number;
|
||||
detail?: number;
|
||||
getModifierState?(key: string): boolean;
|
||||
key?: string;
|
||||
keyCode?: number;
|
||||
locale?: string;
|
||||
location?: number;
|
||||
metaKey?: boolean;
|
||||
pageX?: number;
|
||||
pageY?: number;
|
||||
relatedTarget?: EventTarget;
|
||||
repeat?: boolean;
|
||||
screenX?: number;
|
||||
screenY?: number;
|
||||
shiftKey?: boolean;
|
||||
targetTouches?: TouchList;
|
||||
touches?: TouchList;
|
||||
view?: AbstractView;
|
||||
which?: number;
|
||||
}
|
||||
|
||||
interface EventSimulator {
|
||||
(element: Element, eventData?: SyntheticEventData): void;
|
||||
(component: Component<any, any>, eventData?: SyntheticEventData): void;
|
||||
}
|
||||
|
||||
interface MockedComponentClass {
|
||||
new(): any;
|
||||
}
|
||||
|
||||
class ShallowRenderer {
|
||||
getRenderOutput<E extends ReactElement<any>>(): E;
|
||||
getRenderOutput(): ReactElement<any>;
|
||||
render(element: ReactElement<any>, context?: any): void;
|
||||
unmount(): void;
|
||||
}
|
||||
|
||||
namespace __Addons {
|
||||
namespace TestUtils {
|
||||
namespace Simulate {
|
||||
export var blur: EventSimulator;
|
||||
export var change: EventSimulator;
|
||||
export var click: EventSimulator;
|
||||
export var cut: EventSimulator;
|
||||
export var doubleClick: EventSimulator;
|
||||
export var drag: EventSimulator;
|
||||
export var dragEnd: EventSimulator;
|
||||
export var dragEnter: EventSimulator;
|
||||
export var dragExit: EventSimulator;
|
||||
export var dragLeave: EventSimulator;
|
||||
export var dragOver: EventSimulator;
|
||||
export var dragStart: EventSimulator;
|
||||
export var drop: EventSimulator;
|
||||
export var focus: EventSimulator;
|
||||
export var input: EventSimulator;
|
||||
export var keyDown: EventSimulator;
|
||||
export var keyPress: EventSimulator;
|
||||
export var keyUp: EventSimulator;
|
||||
export var mouseDown: EventSimulator;
|
||||
export var mouseEnter: EventSimulator;
|
||||
export var mouseLeave: EventSimulator;
|
||||
export var mouseMove: EventSimulator;
|
||||
export var mouseOut: EventSimulator;
|
||||
export var mouseOver: EventSimulator;
|
||||
export var mouseUp: EventSimulator;
|
||||
export var paste: EventSimulator;
|
||||
export var scroll: EventSimulator;
|
||||
export var submit: EventSimulator;
|
||||
export var touchCancel: EventSimulator;
|
||||
export var touchEnd: EventSimulator;
|
||||
export var touchMove: EventSimulator;
|
||||
export var touchStart: EventSimulator;
|
||||
export var wheel: EventSimulator;
|
||||
}
|
||||
|
||||
export function renderIntoDocument(
|
||||
element: DOMElement<any>): Element;
|
||||
export function renderIntoDocument<P>(
|
||||
element: ReactElement<P>): Component<P, any>;
|
||||
export function renderIntoDocument<C extends Component<any, any>>(
|
||||
element: ReactElement<any>): C;
|
||||
|
||||
export function mockComponent(
|
||||
mocked: MockedComponentClass, mockTagName?: string): typeof TestUtils;
|
||||
|
||||
export function isElementOfType(
|
||||
element: ReactElement<any>, type: ReactType): boolean;
|
||||
export function isDOMComponent(instance: ReactInstance): boolean;
|
||||
export function isCompositeComponent(instance: ReactInstance): boolean;
|
||||
export function isCompositeComponentWithType(
|
||||
instance: ReactInstance,
|
||||
type: ComponentClass<any>): boolean;
|
||||
|
||||
export function findAllInRenderedTree(
|
||||
root: Component<any, any>,
|
||||
fn: (i: ReactInstance) => boolean): ReactInstance[];
|
||||
|
||||
export function scryRenderedDOMComponentsWithClass(
|
||||
root: Component<any, any>,
|
||||
className: string): Element[];
|
||||
export function findRenderedDOMComponentWithClass(
|
||||
root: Component<any, any>,
|
||||
className: string): Element;
|
||||
|
||||
export function scryRenderedDOMComponentsWithTag(
|
||||
root: Component<any, any>,
|
||||
tagName: string): Element[];
|
||||
export function findRenderedDOMComponentWithTag(
|
||||
root: Component<any, any>,
|
||||
tagName: string): Element;
|
||||
|
||||
export function scryRenderedComponentsWithType<P>(
|
||||
root: Component<any, any>,
|
||||
type: ComponentClass<P>): Component<P, {}>[];
|
||||
export function scryRenderedComponentsWithType<C extends Component<any, any>>(
|
||||
root: Component<any, any>,
|
||||
type: ComponentClass<any>): C[];
|
||||
|
||||
export function findRenderedComponentWithType<P>(
|
||||
root: Component<any, any>,
|
||||
type: ComponentClass<P>): Component<P, {}>;
|
||||
export function findRenderedComponentWithType<C extends Component<any, any>>(
|
||||
root: Component<any, any>,
|
||||
type: ComponentClass<any>): C;
|
||||
|
||||
export function createRenderer(): ShallowRenderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-addons-test-utils" {
|
||||
import TestUtils = __React.__Addons.TestUtils;
|
||||
export = TestUtils;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Type definitions for React v0.14 (react-addons-update)
|
||||
// Project: http://facebook.github.io/react/
|
||||
// Definitions by: Asana <https://asana.com>, AssureSign <http://www.assuresign.com>, Microsoft <https://microsoft.com>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
/// <reference path="react.d.ts" />
|
||||
|
||||
declare namespace __React {
|
||||
interface UpdateSpecCommand {
|
||||
$set?: any;
|
||||
$merge?: {};
|
||||
$apply?(value: any): any;
|
||||
}
|
||||
|
||||
interface UpdateSpecPath {
|
||||
[key: string]: UpdateSpec;
|
||||
}
|
||||
|
||||
type UpdateSpec = UpdateSpecCommand | UpdateSpecPath;
|
||||
|
||||
interface UpdateArraySpec extends UpdateSpecCommand {
|
||||
$push?: any[];
|
||||
$unshift?: any[];
|
||||
$splice?: any[][];
|
||||
}
|
||||
|
||||
namespace __Addons {
|
||||
export function update(value: any[], spec: UpdateArraySpec): any[];
|
||||
export function update(value: {}, spec: UpdateSpec): any;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-addons-update" {
|
||||
export = __React.__Addons.update;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Type definitions for React v0.14 (react-dom)
|
||||
// Project: http://facebook.github.io/react/
|
||||
// Definitions by: Asana <https://asana.com>, AssureSign <http://www.assuresign.com>, Microsoft <https://microsoft.com>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
/// <reference path="react.d.ts" />
|
||||
|
||||
declare namespace __React {
|
||||
namespace __DOM {
|
||||
function findDOMNode<E extends Element>(instance: ReactInstance): E;
|
||||
function findDOMNode(instance: ReactInstance): Element;
|
||||
|
||||
function render<P>(
|
||||
element: DOMElement<P>,
|
||||
container: Element,
|
||||
callback?: (element: Element) => any): Element;
|
||||
function render<P, S>(
|
||||
element: ClassicElement<P>,
|
||||
container: Element,
|
||||
callback?: (component: ClassicComponent<P, S>) => any): ClassicComponent<P, S>;
|
||||
function render<P, S>(
|
||||
element: ReactElement<P>,
|
||||
container: Element,
|
||||
callback?: (component: Component<P, S>) => any): Component<P, S>;
|
||||
|
||||
function unmountComponentAtNode(container: Element): boolean;
|
||||
|
||||
var version: string;
|
||||
|
||||
function unstable_batchedUpdates<A, B>(callback: (a: A, b: B) => any, a: A, b: B): void;
|
||||
function unstable_batchedUpdates<A>(callback: (a: A) => any, a: A): void;
|
||||
function unstable_batchedUpdates(callback: () => any): void;
|
||||
|
||||
function unstable_renderSubtreeIntoContainer<P>(
|
||||
parentComponent: Component<any, any>,
|
||||
nextElement: DOMElement<P>,
|
||||
container: Element,
|
||||
callback?: (element: Element) => any): Element;
|
||||
function unstable_renderSubtreeIntoContainer<P, S>(
|
||||
parentComponent: Component<any, any>,
|
||||
nextElement: ClassicElement<P>,
|
||||
container: Element,
|
||||
callback?: (component: ClassicComponent<P, S>) => any): ClassicComponent<P, S>;
|
||||
function unstable_renderSubtreeIntoContainer<P, S>(
|
||||
parentComponent: Component<any, any>,
|
||||
nextElement: ReactElement<P>,
|
||||
container: Element,
|
||||
callback?: (component: Component<P, S>) => any): Component<P, S>;
|
||||
}
|
||||
|
||||
namespace __DOMServer {
|
||||
function renderToString(element: ReactElement<any>): string;
|
||||
function renderToStaticMarkup(element: ReactElement<any>): string;
|
||||
var version: string;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "react-dom" {
|
||||
import DOM = __React.__DOM;
|
||||
export = DOM;
|
||||
}
|
||||
|
||||
declare module "react-dom/server" {
|
||||
import DOMServer = __React.__DOMServer;
|
||||
export = DOMServer;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
// Type definitions for System.js 0.18.4
|
||||
// Project: https://github.com/systemjs/systemjs
|
||||
// Definitions by: Ludovic HENIN <https://github.com/ludohenin/>, Nathan Walker <https://github.com/NathanWalker/>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
interface System {
|
||||
import(name: string): any;
|
||||
defined: any;
|
||||
amdDefine: () => void;
|
||||
amdRequire: () => void;
|
||||
baseURL: string;
|
||||
paths: { [key: string]: string };
|
||||
meta: { [key: string]: Object };
|
||||
config: any;
|
||||
}
|
||||
|
||||
declare var System: System;
|
||||
|
||||
declare module "systemjs" {
|
||||
export = System;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/// <reference path="@ms/odsp.d.ts" />
|
||||
/// <reference path="@ms/odsp-webpack.d.ts" />
|
||||
/// <reference path="assertion-error/assertion-error.d.ts" />
|
||||
/// <reference path="chai/chai.d.ts" />
|
||||
/// <reference path="es6-collections/es6-collections.d.ts" />
|
||||
/// <reference path="es6-promise/es6-promise.d.ts" />
|
||||
/// <reference path="lodash/lodash.d.ts" />
|
||||
/// <reference path="mocha/mocha.d.ts" />
|
||||
/// <reference path="node/node.d.ts" />
|
||||
/// <reference path="react/react.d.ts" />
|
||||
/// <reference path="react/react-addons-shallow-compare.d.ts" />
|
||||
/// <reference path="react/react-addons-test-utils.d.ts" />
|
||||
/// <reference path="react/react-addons-update.d.ts" />
|
||||
/// <reference path="react/react-dom.d.ts" />
|
||||
/// <reference path="systemjs/systemjs.d.ts" />
|
||||
/// <reference path="whatwg-fetch/whatwg-fetch.d.ts" />
|
||||
/// <reference path="knockout/knockout.d.ts" />
|
|
@ -0,0 +1,87 @@
|
|||
// Type definitions for fetch API
|
||||
// Project: https://github.com/github/fetch
|
||||
// Definitions by: Ryan Graham <https://github.com/ryan-codingintrigue>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
/// <reference path="../es6-promise/es6-promise.d.ts" />
|
||||
|
||||
declare class Request extends Body {
|
||||
constructor(input: string|Request, init?:RequestInit);
|
||||
method: string;
|
||||
url: string;
|
||||
headers: Headers;
|
||||
context: string|RequestContext;
|
||||
referrer: string;
|
||||
mode: string|RequestMode;
|
||||
credentials: string|RequestCredentials;
|
||||
cache: string|RequestCache;
|
||||
}
|
||||
|
||||
interface RequestInit {
|
||||
method?: string;
|
||||
headers?: HeaderInit|{ [index: string]: string };
|
||||
body?: BodyInit;
|
||||
mode?: string|RequestMode;
|
||||
credentials?: string|RequestCredentials;
|
||||
cache?: string|RequestCache;
|
||||
}
|
||||
|
||||
declare enum RequestContext {
|
||||
"audio", "beacon", "cspreport", "download", "embed", "eventsource", "favicon", "fetch",
|
||||
"font", "form", "frame", "hyperlink", "iframe", "image", "imageset", "import",
|
||||
"internal", "location", "manifest", "object", "ping", "plugin", "prefetch", "script",
|
||||
"serviceworker", "sharedworker", "subresource", "style", "track", "video", "worker",
|
||||
"xmlhttprequest", "xslt"
|
||||
}
|
||||
declare enum RequestMode { "same-origin", "no-cors", "cors" }
|
||||
declare enum RequestCredentials { "omit", "same-origin", "include" }
|
||||
declare enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" }
|
||||
|
||||
declare class Headers {
|
||||
append(name: string, value: string): void;
|
||||
delete(name: string):void;
|
||||
get(name: string): string;
|
||||
getAll(name: string): Array<string>;
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string): void;
|
||||
}
|
||||
|
||||
declare class Body {
|
||||
bodyUsed: boolean;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
blob(): Promise<Blob>;
|
||||
formData(): Promise<FormData>;
|
||||
json(): Promise<any>;
|
||||
json<T>(): Promise<T>;
|
||||
text(): Promise<string>;
|
||||
}
|
||||
declare class Response extends Body {
|
||||
constructor(body?: BodyInit, init?: ResponseInit);
|
||||
error(): Response;
|
||||
redirect(url: string, status: number): Response;
|
||||
type: string|ResponseType;
|
||||
url: string;
|
||||
status: number;
|
||||
ok: boolean;
|
||||
statusText: string;
|
||||
headers: Headers;
|
||||
clone(): Response;
|
||||
}
|
||||
|
||||
declare enum ResponseType { "basic", "cors", "default", "error", "opaque" }
|
||||
|
||||
interface ResponseInit {
|
||||
status: number;
|
||||
statusText?: string;
|
||||
headers?: HeaderInit;
|
||||
}
|
||||
|
||||
declare type HeaderInit = Headers|Array<string>;
|
||||
declare type BodyInit = Blob|FormData|string;
|
||||
declare type RequestInfo = Request|string;
|
||||
|
||||
interface Window {
|
||||
fetch(url: string|Request, init?: RequestInit): Promise<Response>;
|
||||
}
|
||||
|
||||
declare var fetch: typeof window.fetch;
|
Loading…
Reference in New Issue