diff --git a/samples/todo-webpart-sample/.editorconfig b/samples/todo-webpart-sample/.editorconfig new file mode 100644 index 000000000..8ffcdc4ec --- /dev/null +++ b/samples/todo-webpart-sample/.editorconfig @@ -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 \ No newline at end of file diff --git a/samples/todo-webpart-sample/.gitattributes b/samples/todo-webpart-sample/.gitattributes new file mode 100644 index 000000000..212566614 --- /dev/null +++ b/samples/todo-webpart-sample/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/samples/todo-webpart-sample/.gitignore b/samples/todo-webpart-sample/.gitignore new file mode 100644 index 000000000..29d9f19b2 --- /dev/null +++ b/samples/todo-webpart-sample/.gitignore @@ -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 diff --git a/samples/todo-webpart-sample/.npmignore b/samples/todo-webpart-sample/.npmignore new file mode 100644 index 000000000..8a1c38d7f --- /dev/null +++ b/samples/todo-webpart-sample/.npmignore @@ -0,0 +1,14 @@ +# Folders +.vscode +coverage +node_modules +solution +src +temp + +# Files +*.csproj +.git* +.yo-rc.json +gulpfile.js +tsconfig.json diff --git a/samples/todo-webpart-sample/.vscode/settings.json b/samples/todo-webpart-sample/.vscode/settings.json new file mode 100644 index 000000000..ab46aaa4f --- /dev/null +++ b/samples/todo-webpart-sample/.vscode/settings.json @@ -0,0 +1,21 @@ +{ + // The number of spaces a tab is equal to. + "editor.tabSize": 2, + + // When enabled, will trim trailing whitespace when you save a file. + "files.trimTrailingWhitespace": true, + + // Controls if the editor should automatically close brackets after opening them + "editor.autoClosingBrackets": false, + + // Configure glob patterns for excluding files and folders. + "search.exclude": { + "**/bower_components": true, + "**/node_modules": true, + "coverage": true, + "dist": true, + "lib-amd": true, + "lib": true, + "temp": true + } +} diff --git a/samples/todo-webpart-sample/.vscode/tasks.json b/samples/todo-webpart-sample/.vscode/tasks.json new file mode 100644 index 000000000..5204908d6 --- /dev/null +++ b/samples/todo-webpart-sample/.vscode/tasks.json @@ -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" + ] + } + ] +} diff --git a/samples/todo-webpart-sample/README.md b/samples/todo-webpart-sample/README.md new file mode 100644 index 000000000..8f1f88d64 --- /dev/null +++ b/samples/todo-webpart-sample/README.md @@ -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 diff --git a/samples/todo-webpart-sample/config/config.json b/samples/todo-webpart-sample/config/config.json new file mode 100644 index 000000000..a49d571f2 --- /dev/null +++ b/samples/todo-webpart-sample/config/config.json @@ -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" + } +} diff --git a/samples/todo-webpart-sample/config/deploy-azure-storage.json b/samples/todo-webpart-sample/config/deploy-azure-storage.json new file mode 100644 index 000000000..f97d7620d --- /dev/null +++ b/samples/todo-webpart-sample/config/deploy-azure-storage.json @@ -0,0 +1,6 @@ +{ + "workingDir": "./temp/deploy/", + "account": "", + "container": "todo-webpart-sample", + "accessKey": "" +} \ No newline at end of file diff --git a/samples/todo-webpart-sample/config/package-solution.json b/samples/todo-webpart-sample/config/package-solution.json new file mode 100644 index 000000000..3468c445e --- /dev/null +++ b/samples/todo-webpart-sample/config/package-solution.json @@ -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" + } +} diff --git a/samples/todo-webpart-sample/config/prepare-deploy.json b/samples/todo-webpart-sample/config/prepare-deploy.json new file mode 100644 index 000000000..6aca63656 --- /dev/null +++ b/samples/todo-webpart-sample/config/prepare-deploy.json @@ -0,0 +1,3 @@ +{ + "deployCdnPath": "temp/deploy" +} diff --git a/samples/todo-webpart-sample/config/serve.json b/samples/todo-webpart-sample/config/serve.json new file mode 100644 index 000000000..087899637 --- /dev/null +++ b/samples/todo-webpart-sample/config/serve.json @@ -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/" + } +} diff --git a/samples/todo-webpart-sample/config/tslint.json b/samples/todo-webpart-sample/config/tslint.json new file mode 100644 index 000000000..bf3362c87 --- /dev/null +++ b/samples/todo-webpart-sample/config/tslint.json @@ -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 + } + } +} \ No newline at end of file diff --git a/samples/todo-webpart-sample/config/write-manifests.json b/samples/todo-webpart-sample/config/write-manifests.json new file mode 100644 index 000000000..0a4bafb06 --- /dev/null +++ b/samples/todo-webpart-sample/config/write-manifests.json @@ -0,0 +1,3 @@ +{ + "cdnBasePath": "" +} \ No newline at end of file diff --git a/samples/todo-webpart-sample/gulpfile.js b/samples/todo-webpart-sample/gulpfile.js new file mode 100644 index 000000000..7d36ddb1c --- /dev/null +++ b/samples/todo-webpart-sample/gulpfile.js @@ -0,0 +1,6 @@ +'use strict'; + +const gulp = require('gulp'); +const build = require('@microsoft/sp-build-web'); + +build.initialize(gulp); diff --git a/samples/todo-webpart-sample/package.json b/samples/todo-webpart-sample/package.json new file mode 100644 index 000000000..c715e6b2a --- /dev/null +++ b/samples/todo-webpart-sample/package.json @@ -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" + } +} diff --git a/samples/todo-webpart-sample/sharepoint/feature_xml/List/Elements.xml b/samples/todo-webpart-sample/sharepoint/feature_xml/List/Elements.xml new file mode 100644 index 000000000..b53761dc0 --- /dev/null +++ b/samples/todo-webpart-sample/sharepoint/feature_xml/List/Elements.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml b/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml new file mode 100644 index 000000000..f727ea9b4 --- /dev/null +++ b/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml @@ -0,0 +1,10 @@ + + diff --git a/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml.config.xml b/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml.config.xml new file mode 100644 index 000000000..7939afc73 --- /dev/null +++ b/samples/todo-webpart-sample/sharepoint/feature_xml/TodoWebPartFeature.xml.config.xml @@ -0,0 +1,8 @@ + + + 771d8fd4-46ce-421d-a61c-79c55e1fba19 + diff --git a/samples/todo-webpart-sample/sharepoint/feature_xml/_rels/TodoWebPartFeature.xml.rels b/samples/todo-webpart-sample/sharepoint/feature_xml/_rels/TodoWebPartFeature.xml.rels new file mode 100644 index 000000000..5b2b52304 --- /dev/null +++ b/samples/todo-webpart-sample/sharepoint/feature_xml/_rels/TodoWebPartFeature.xml.rels @@ -0,0 +1,13 @@ + + + + + diff --git a/samples/todo-webpart-sample/src/tests.js b/samples/todo-webpart-sample/src/tests.js new file mode 100644 index 000000000..cb4bb5cf2 --- /dev/null +++ b/samples/todo-webpart-sample/src/tests.js @@ -0,0 +1,5 @@ +var context = require.context('.', true, /.+\.test\.js?$/); + +context.keys().forEach(context); + +module.exports = context; diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/ITodoWebPartProps.ts b/samples/todo-webpart-sample/src/webparts/todo-step-1/ITodoWebPartProps.ts new file mode 100644 index 000000000..cc6e54492 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/ITodoWebPartProps.ts @@ -0,0 +1,3 @@ +export interface ITodoWebPartProps { + description: string; +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/README.md b/samples/todo-webpart-sample/src/webparts/todo-step-1/README.md new file mode 100644 index 000000000..077fdf36f --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/README.md @@ -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 +``` diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/Todo.module.scss b/samples/todo-webpart-sample/src/webparts/todo-step-1/Todo.module.scss new file mode 100644 index 000000000..e0c9d7157 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/Todo.module.scss @@ -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; + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.manifest.json b/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.manifest.json new file mode 100644 index 000000000..c55831f4d --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.manifest.json @@ -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" + } + }] +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.ts b/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.ts new file mode 100644 index 000000000..035d16c4a --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/TodoWebPart.ts @@ -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 { + /** + * Override the base render() implementation to render the todo sample web part. + */ + public render(): void { + const element: React.ReactElement = 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 + }) + ] + } + ] + } + ] + }; + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/components/Todo.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-1/components/Todo.tsx new file mode 100644 index 000000000..21c383ca5 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/components/Todo.tsx @@ -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 { + public render(): JSX.Element { + return ( +
+
+
+
+ + Welcome to SharePoint! + +

+ Customize SharePoint experiences using Web Parts. +

+

+ {this.props.description} +

+ + Learn more + +
+
+
+
+ ); + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/en-us.js b/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/en-us.js new file mode 100644 index 000000000..89f98bc1e --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/en-us.js @@ -0,0 +1,7 @@ +define([], function() { + return { + "PropertyPaneDescription": "Description", + "BasicGroupName": "Group Name", + "DescriptionFieldLabel": "Description Field" + } +}); \ No newline at end of file diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/mystrings.d.ts b/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/mystrings.d.ts new file mode 100644 index 000000000..5b94b8d6f --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/loc/mystrings.d.ts @@ -0,0 +1,10 @@ +declare interface ITodoStrings { + PropertyPaneDescription: string; + BasicGroupName: string; + DescriptionFieldLabel: string; +} + +declare module 'todoStrings' { + const strings: ITodoStrings; + export = strings; +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-1/tests/Todo.test.ts b/samples/todo-webpart-sample/src/webparts/todo-step-1/tests/Todo.test.ts new file mode 100644 index 000000000..c5b8423b3 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-1/tests/Todo.test.ts @@ -0,0 +1,7 @@ +import * as assert from 'assert'; + +describe('TodoWebPart', () => { + it('should do something', () => { + assert.ok(true); + }); +}); diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/ITodoWebPartProps.ts b/samples/todo-webpart-sample/src/webparts/todo-step-2/ITodoWebPartProps.ts new file mode 100644 index 000000000..d5336c939 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/ITodoWebPartProps.ts @@ -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; diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/README.md b/samples/todo-webpart-sample/src/webparts/todo-step-2/README.md new file mode 100644 index 000000000..deedec70e --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/README.md @@ -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 +``` diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.manifest.json b/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.manifest.json new file mode 100644 index 000000000..981ab82ad --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.manifest.json @@ -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 + } + } + ] +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.tsx new file mode 100644 index 000000000..3754feb86 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/TodoWebPart.tsx @@ -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 { + 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 { + 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( + as React.ReactElement, + this.domElement + ); + } + + /** + * Read the information of all the task lists stored in the current site through data provider. + */ + private _readLists(): Promise { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + // 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); + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/common/Utils.ts b/samples/todo-webpart-sample/src/webparts/todo-step-2/common/Utils.ts new file mode 100644 index 000000000..e81f1590f --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/common/Utils.ts @@ -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 */ diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/components/Todo.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/Todo.tsx new file mode 100644 index 000000000..701d6ae23 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/Todo.tsx @@ -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 + * + * Link of Spinner: https://fabricreact.azurewebsites.net/fabric-react/master/#/examples/spinner + */ +export default class Todo extends React.Component { + public shouldComponentUpdate(nextProps: ITodoProps, nextState: {}): boolean { + return !Compare.shallowCompare(this.props, nextProps) || !Compare.shallowCompare(this.state, nextState); + } + + public render(): React.ReactElement> { + return ( +
+
+

{ strings.TodoListTitle }

+ { this._workingOnItSpinner } +
+ + + { this._fetchingSpinner } +
+ ); + } + + private get _workingOnItSpinner(): React.ReactElement> { + return this.props.loadingStatus === LoadingStatus.UpdatingTasks + ? ( +
+ +
+ ) + : null; // tslint:disable-line:no-null-keyword + } + + private get _fetchingSpinner(): React.ReactElement> { + return this.props.loadingStatus === LoadingStatus.FetchingTasks + ? ( +
+ +
+ ) + : null; // tslint:disable-line:no-null-keyword + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoForm.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoForm.tsx new file mode 100644 index 000000000..8ea8167b1 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoForm.tsx @@ -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 + + + ); + } + + 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 ''; + } + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoItem.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoItem.tsx new file mode 100644 index 000000000..30ec05de6 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-2/components/TodoItem.tsx @@ -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 + + + ); + } + + 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 ''; + } + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-3/components/TodoItem.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-3/components/TodoItem.tsx new file mode 100644 index 000000000..30ec05de6 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-3/components/TodoItem.tsx @@ -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 + + + ); + } + + 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 ''; + } + } +} diff --git a/samples/todo-webpart-sample/src/webparts/todo-step-4/components/TodoItem.tsx b/samples/todo-webpart-sample/src/webparts/todo-step-4/components/TodoItem.tsx new file mode 100644 index 000000000..30ec05de6 --- /dev/null +++ b/samples/todo-webpart-sample/src/webparts/todo-step-4/components/TodoItem.tsx @@ -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