Upgraded Vue SFC sample to the latest SPFx drop. Updated dependency versions. Fixed build issues.

This commit is contained in:
Sergei Sergeev 2020-04-28 00:18:08 +03:00
parent 426b91707d
commit 231f87f714
17 changed files with 9580 additions and 9614 deletions

View File

@ -19,7 +19,7 @@ Sample Todo web part demonstrating how you can utilize [Vue](https://vuejs.org/v
## Used SharePoint Framework Version
![1.4.0](https://img.shields.io/badge/drop-1.4.0-green.svg)
![1.10.0](https://img.shields.io/badge/drop-1.10.0-green.svg)
## Applies to
@ -42,6 +42,7 @@ Version|Date|Comments
0.0.4|October 7, 2017|Updated packages to latest versions, misc fixing
0.0.5|November 15, 2017|Added data provider that demonstrates the CRUD operations
0.0.6|December 11, 2018|Updated sample to SPFx 1.4 and Vue 2.5.x
0.0.7|April 28, 2020|Updated sample to SPFx 1.10 and Vue 2.6.x, fixed issues with batch loader
## Disclaimer

View File

@ -1,5 +1,5 @@
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"to-do-bundle": {
@ -14,4 +14,4 @@
"localizedResources": {
"toDoStrings": "lib/webparts/toDo/loc/{locale}.js"
}
}
}

View File

@ -1,3 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "temp/deploy"
}

View File

@ -1,8 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "vue-todo-client-side-solution",
"id": "93e1bc8f-a063-4617-aa40-837e3b39f958",
"version": "1.0.0.0"
"version": "1.0.0.0",
"isDomainIsolated": false
},
"paths": {
"zippedPackage": "solution/vue-todo.sppkg"

View File

@ -1,4 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"initialPage": "https://localhost:5432/workbench",
"https": true,

View File

@ -1,46 +0,0 @@
{
// Display errors as warnings
"displayAsWarning": true,
// The TSLint task may have been configured with several custom lint rules
// before this config file is read (for example lint rules from the tslint-microsoft-contrib
// project). If true, this flag will deactivate any of these rules.
"removeExistingRules": true,
// When true, the TSLint task is configured with some default TSLint "rules.":
"useDefaultConfigAsBase": false,
// Since removeExistingRules=true and useDefaultConfigAsBase=false, there will be no lint rules
// which are active, other than the list of rules below.
"lintConfig": {
// Opt-in to Lint rules which help to eliminate bugs in JavaScript
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-case": true,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-unused-imports": 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": false
}
}
}

View File

@ -1,3 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}
}

View File

@ -3,41 +3,47 @@
const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
var merge = require('webpack-merge');
const VueLoaderPlugin = require('vue-loader/lib/plugin')
build.sass.setConfig({
sassMatch: []
sassMatch: []
});
build.configureWebpack.setConfig({
additionalConfiguration: function (config) {
var vueConfig = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
esModule: true
}
}]
}]
},
};
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
return merge(config, vueConfig);
}
generatedConfiguration.plugins.push(new VueLoaderPlugin());
generatedConfiguration.resolve.alias = {
'vue$': 'vue/dist/vue.esm.js'
};
generatedConfiguration.module.rules.push({
test: /\.vue$/,
use: [
{
loader: 'vue-loader',
options: {
esModule: true
}
}]
});
generatedConfiguration.module.rules.push({
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
});
return generatedConfiguration;
}
});
let copyOtherFiles = build.subTask('copy-other-files', function(gulp, buildOptions, done){
return gulp.src(['src/**/*.vue', 'src/**/*.scss'])
.pipe(gulp.dest(buildOptions.libFolder))
let copyOtherFiles = build.subTask('copy-other-files', function (gulp, buildOptions, done) {
return gulp.src(['src/**/*.vue', 'src/**/*.scss'])
.pipe(gulp.dest(buildOptions.libFolder))
});
build.task('copy-other-files', copyOtherFiles);
build.rig.addPostTypescriptTask(copyOtherFiles);

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +1,39 @@
{
"name": "vue-todo",
"version": "0.0.6",
"version": "0.0.7",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"main": "lib/index.js",
"dependencies": {
"@microsoft/sp-core-library": "~1.4.0",
"@microsoft/sp-lodash-subset": "~1.4.0",
"@microsoft/sp-office-ui-fabric-core": "~1.4.0",
"@microsoft/sp-webpart-base": "~1.4.0",
"@types/webpack-env": ">=1.12.1 <1.14.0",
"vue": "^2.5.13",
"vue-class-component": "^6.1.2",
"vue-property-decorator": "^6.0.0",
"vue-template-compiler": "^2.5.13",
"minimist": ">=1.2.3",
"acorn": ">=5.7.4"
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@types/es6-promise": "0.0.33",
"@types/webpack-env": "1.13.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.2",
"vue-template-compiler": "^2.6.11"
},
"devDependencies": {
"@microsoft/sp-build-web": "~1.4.0",
"@microsoft/sp-module-interfaces": "~1.4.0",
"@microsoft/sp-webpart-workbench": "~1.4.0",
"@types/chai": ">=3.4.34 <3.6.0",
"@types/mocha": ">=2.2.33 <2.6.0",
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"css-loader": "^3.5.3",
"gulp": "~3.9.1",
"webpack-merge": "^4.1.1",
"node-sass": "~4.5.3",
"sass-loader": "~6.0.6",
"vue-loader": "^13.7.0"
"node-sass": "~4.14.0",
"sass-loader": "~8.0.2",
"tslint-microsoft-contrib": "5.0.0",
"vue-loader": "^15.9.1"
},
"scripts": {
"build": "gulp bundle",

View File

@ -1,7 +1,7 @@
import { ITaskList } from '../models/ICommonObjects';
import {
IPropertyPaneDropdownOption,
} from '@microsoft/sp-webpart-base';
} from '@microsoft/sp-property-pane';
export class Utils {
@ -27,4 +27,4 @@ export class Utils {
}
return result;
}
}
}

View File

@ -1,12 +1,10 @@
import {
SPHttpClient,
SPHttpClientBatch,
SPHttpClientResponse
} from '@microsoft/sp-http';
import { IWebPartContext } from '@microsoft/sp-webpart-base';
import ITodoDataProvider from '../dataProviders/ITodoDataProvider';
import { ITodoItem, ITaskList } from '../models/ICommonObjects';
import { debounce } from '@microsoft/sp-lodash-subset';
export default class SharePointDataProvider implements ITodoDataProvider {
@ -45,60 +43,13 @@ export default class SharePointDataProvider implements ITodoDataProvider {
}
public getItems(): Promise<ITodoItem[]> {
return this._getItems(this.webPartContext.spHttpClient);
}
public createItem(title: string): Promise<ITodoItem[]> {
const batch: SPHttpClientBatch = this.webPartContext.spHttpClient.beginBatch();
const batchPromises: Promise<{}>[] = [
this._createItem(batch, title),
this._getItemsBatched(batch)
];
return this._resolveBatch(batch, batchPromises);
}
public deleteItem(itemToBeDeleted: ITodoItem): Promise<ITodoItem[]> {
//Approach 1: it is not working here with DELETE
// const batch: SPHttpClientBatch = this.webPartContext.spHttpClient.beginBatch();
// const batchPromises: Promise<{}>[] = [
// this._deleteItem(batch, itemToBeDeleted),
// this._getItemsBatched(batch)
// ];
// return this._resolveBatch(batch, batchPromises);
//Approach 2:
return this._deleteItem2(this.webPartContext.spHttpClient, itemToBeDeleted);
}
public updateItem(itemUpdated: ITodoItem): Promise<ITodoItem[]> {
const batch: SPHttpClientBatch = this.webPartContext.spHttpClient.beginBatch();
const batchPromises: Promise<{}>[] = [
this._updateItem(batch, itemUpdated),
this._getItemsBatched(batch)
];
return this._resolveBatch(batch, batchPromises);
}
private _getItems(requester: SPHttpClient): Promise<ITodoItem[]> {
const queryUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}` +
`/_api/web/lists(guid'${this._selectedList.Id}')/items?$select=Id,Title,PercentComplete`;
return requester.get(queryUrl, SPHttpClient.configurations.v1)
return this.webPartContext.spHttpClient.get(queryUrl, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
})
//Approach 1: Of the property names of ITodoItem are equal to the internal names of the list, this will work
// .then((json: { value: ITodoItem[] }) => {
// debugger;
// return json.value;
// // .map((task: ITodoItem) => { debugger; return task; });
// });
//Approach 2: manually create the ITodoItem object; useful when the properties are different form the internal names of the list
.then((json: any) => {
return json.value.map((item: any) => {
let newItem: ITodoItem = {
@ -111,57 +62,26 @@ export default class SharePointDataProvider implements ITodoDataProvider {
});
}
private _getItemsBatched(requester: SPHttpClientBatch): Promise<ITodoItem[]> {
const queryUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}` +
`/_api/web/lists(guid'${this._selectedList.Id}')/items?$select=Id,Title,PercentComplete`;
return requester.get(queryUrl, SPHttpClientBatch.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
})
.then((json: { value: ITodoItem[] }) => {
return json.value.map((task: ITodoItem) => {
return task;
});
});
}
private _createItem(batch: SPHttpClientBatch, title: string): Promise<SPHttpClientResponse> {
public createItem(title: string): Promise<ITodoItem[]> {
const body: {} = {
'@data.type': this._selectedList.ListItemEntityTypeFullName,
'Title': title
};
return batch.post(
`${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items`,
SPHttpClientBatch.configurations.v1,
{ body: JSON.stringify(body) }
);
return this._webPartContext.spHttpClient.post(`${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items`, SPHttpClient.configurations.v1, {
body: JSON.stringify(body)
})
.then(() => {
return this.getItems();
});
}
private _deleteItem(batch: SPHttpClientBatch, item: ITodoItem): Promise<SPHttpClientResponse> {
const itemDeletedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${item.Id})`;
public deleteItem(itemToBeDeleted: ITodoItem): Promise<ITodoItem[]> {
const itemDeletedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${itemToBeDeleted.Id})`;
const headers: Headers = new Headers();
headers.append('If-Match', '*');
return batch.fetch(itemDeletedUrl,
SPHttpClientBatch.configurations.v1,
{
headers,
method: 'DELETE'
}
);
}
private _deleteItem2(requester: SPHttpClient, item: ITodoItem): Promise<ITodoItem[]> {
const itemDeletedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${item.Id})`;
const headers: Headers = new Headers();
headers.append('If-Match', '*');
return requester.fetch(itemDeletedUrl,
return this.webPartContext.spHttpClient.fetch(itemDeletedUrl,
SPHttpClient.configurations.v1,
{
headers,
@ -174,42 +94,26 @@ export default class SharePointDataProvider implements ITodoDataProvider {
return Promise.reject(new Error(JSON.stringify(response)));
}
}).then(() => {
return this._getItems(requester);
return this.getItems();
});
}
private _updateItem(batch: SPHttpClientBatch, item: ITodoItem): Promise<SPHttpClientResponse> {
const itemUpdatedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${item.Id})`;
public updateItem(itemUpdated: ITodoItem): Promise<ITodoItem[]> {
const itemUpdatedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${itemUpdated.Id})`;
const headers: Headers = new Headers();
headers.append('If-Match', '*');
const body: {} = {
'@data.type': this._selectedList.ListItemEntityTypeFullName,
'PercentComplete': item.PercentComplete
'PercentComplete': itemUpdated.PercentComplete
};
return batch.fetch(itemUpdatedUrl,
SPHttpClientBatch.configurations.v1,
{
body: JSON.stringify(body),
headers,
method: 'PATCH'
}
);
}
private _resolveBatch(batch: SPHttpClientBatch, promises: Promise<{}>[]): Promise<ITodoItem[]> {
return batch.execute()
.then(() => {
return Promise.all(promises);
}).then((values: any) => {
return Promise.resolve(values[values.length - 1]);
// return values[values.length - 1];
}).catch((ex) => {
throw ex;
});
return this._webPartContext.spHttpClient.fetch(itemUpdatedUrl, SPHttpClient.configurations.v1, {
body: JSON.stringify(body),
headers,
method: 'PATCH'
}).then(() => {
return this.getItems();
});
}
}

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -1,11 +1,11 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "a0e1eddd-ea67-4775-a52b-0141c5807146",
"alias": "ToDoWebPart",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{
"groupId": "a0e1eddd-ea67-4775-a52b-0141c5807146",

View File

@ -1,15 +1,15 @@
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown,
IPropertyPaneField,
PropertyPaneLabel,
IPropertyPaneDropdownOption
BaseClientSideWebPart
} from '@microsoft/sp-webpart-base';
import { findIndex } from '@microsoft/sp-lodash-subset';
import {
IPropertyPaneConfiguration,
IPropertyPaneDropdownOption,
PropertyPaneDropdown
} from "@microsoft/sp-property-pane";
import Vue from 'vue';
import TodoComponent from './components/todo/Todo.vue';
import { ITodoProps } from './components/todo/ITodoProps';
@ -46,9 +46,9 @@ export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps
this._dataProvider = new SharePointDataProvider();
this._dataProvider.webPartContext = this.context;
}
/*
If we have serialized list in the webpart properties, use it
If we have serialized list in the webpart properties, use it
*/
if (this.properties.SelectedList) {
this._dataProvider.selectedList = this.properties.SelectedList;
@ -57,9 +57,9 @@ export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps
/*
Approach 1:
Get the list of tasks lists from the current site and store them in the variable _dropdownOptions
_dropdownOptions will be used to populate the property pane dropdown field when pane opens.
Approach 2:
_dropdownOptions will be used to populate the property pane dropdown field when pane opens.
Approach 2:
Get the list of tasks lists from the current site and load them in the property pane only when the property pane is open.
For this approach please review the sample: react-custompropertypanecontrols
@ -131,16 +131,16 @@ export default class TodoWebPart extends BaseClientSideWebPart<ITodoWebPartProps
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
/*
Check the property path to see which property pane feld changed.
If the property path matches the dropdown, then we set that list as the selected list for the web part.
Check the property path to see which property pane feld changed.
If the property path matches the dropdown, then we set that list as the selected list for the web part.
*/
if (propertyPath === 'selectedList') {
this._setSelectedList(newValue);
}
/*
Tell property pane to re-render the web part.
This is valid for reactive property pane.
Tell property pane to re-render the web part.
This is valid for reactive property pane.
*/
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}

View File

@ -1,11 +1,23 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env"
@ -15,5 +27,12 @@
"dom",
"es2015.collection"
]
}
},
"include": [
"src/**/*.ts", "src/webparts/themed/index.tsx"
],
"exclude": [
"node_modules",
"lib"
]
}

View File

@ -1,3 +1,30 @@
{
"rulesDirectory": "./config"
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}