add todo-webpart-sample which contains 4 steps web part.

This commit is contained in:
ysliu 2016-09-13 15:59:26 +08:00
parent 9e131acb18
commit 1a547030cc
97 changed files with 33772 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1 @@
* text=auto

35
samples/todo-webpart-sample/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,14 @@
# Folders
.vscode
coverage
node_modules
solution
src
temp
# Files
*.csproj
.git*
.yo-rc.json
gulpfile.js
tsconfig.json

View File

@ -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
}
}

View File

@ -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"
]
}
]
}

View File

@ -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

View File

@ -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"
}
}

View File

@ -0,0 +1,6 @@
{
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "todo-webpart-sample",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
{
"deployCdnPath": "temp/deploy"
}

View File

@ -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/"
}
}

View File

@ -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
}
}
}

View File

@ -0,0 +1,3 @@
{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,6 @@
'use strict';
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
build.initialize(gulp);

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
var context = require.context('.', true, /.+\.test\.js?$/);
context.keys().forEach(context);
module.exports = context;

View File

@ -0,0 +1,3 @@
export interface ITodoWebPartProps {
description: string;
}

View File

@ -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
```

View File

@ -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;
}
}

View File

@ -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"
}
}]
}

View File

@ -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
})
]
}
]
}
]
};
}
}

View File

@ -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>
);
}
}

View File

@ -0,0 +1,7 @@
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field"
}
});

View File

@ -0,0 +1,10 @@
declare interface ITodoStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
}
declare module 'todoStrings' {
const strings: ITodoStrings;
export = strings;
}

View File

@ -0,0 +1,7 @@
import * as assert from 'assert';
describe('TodoWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});

View File

@ -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;

View File

@ -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
```

View File

@ -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
}
}
]
}

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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
}
}

View File

@ -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 '';
}
}
}

View File

@ -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
);
}
}

View File

@ -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 }
/>
);
}
}

View File

@ -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>
);
}
}

View File

@ -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[]>;
}

View File

@ -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
};
}
}

View File

@ -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."
}
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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
```

View File

@ -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
}
}
]
}

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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
}
}

View File

@ -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 '';
}
}
}

View File

@ -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
);
}
}

View File

@ -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 }
/>
);
}
}

View File

@ -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>
);
}
}

View File

@ -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[]>;
}

View File

@ -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
};
}
}

View File

@ -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]));
}
}

View File

@ -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."
}
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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
```

View File

@ -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
}
}
]
}

View File

@ -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);
}
}

View File

@ -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 */

View File

@ -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
}
}

View File

@ -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 '';
}
}
}

View File

@ -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
);
}
}

View File

@ -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 }
/>
);
}
}

View File

@ -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>
);
}
}

View File

@ -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[]>;
}

View File

@ -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
};
}
}

View File

@ -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]));
}
}

View File

@ -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."
}
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true
}
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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" />

View File

@ -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;