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 ## 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 ## Applies to
@ -42,6 +42,7 @@ Version|Date|Comments
0.0.4|October 7, 2017|Updated packages to latest versions, misc fixing 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.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.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 ## 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", "version": "2.0",
"bundles": { "bundles": {
"to-do-bundle": { "to-do-bundle": {

View File

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

View File

@ -1,8 +1,10 @@
{ {
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": { "solution": {
"name": "vue-todo-client-side-solution", "name": "vue-todo-client-side-solution",
"id": "93e1bc8f-a063-4617-aa40-837e3b39f958", "id": "93e1bc8f-a063-4617-aa40-837e3b39f958",
"version": "1.0.0.0" "version": "1.0.0.0",
"isDomainIsolated": false
}, },
"paths": { "paths": {
"zippedPackage": "solution/vue-todo.sppkg" "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, "port": 4321,
"initialPage": "https://localhost:5432/workbench", "initialPage": "https://localhost:5432/workbench",
"https": true, "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 -->" "cdnBasePath": "<!-- PATH TO CDN -->"
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,12 +1,10 @@
import { import {
SPHttpClient, SPHttpClient,
SPHttpClientBatch,
SPHttpClientResponse SPHttpClientResponse
} from '@microsoft/sp-http'; } from '@microsoft/sp-http';
import { IWebPartContext } from '@microsoft/sp-webpart-base'; import { IWebPartContext } from '@microsoft/sp-webpart-base';
import ITodoDataProvider from '../dataProviders/ITodoDataProvider'; import ITodoDataProvider from '../dataProviders/ITodoDataProvider';
import { ITodoItem, ITaskList } from '../models/ICommonObjects'; import { ITodoItem, ITaskList } from '../models/ICommonObjects';
import { debounce } from '@microsoft/sp-lodash-subset';
export default class SharePointDataProvider implements ITodoDataProvider { export default class SharePointDataProvider implements ITodoDataProvider {
@ -45,60 +43,13 @@ export default class SharePointDataProvider implements ITodoDataProvider {
} }
public getItems(): Promise<ITodoItem[]> { 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}` + const queryUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}` +
`/_api/web/lists(guid'${this._selectedList.Id}')/items?$select=Id,Title,PercentComplete`; `/_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) => { .then((response: SPHttpClientResponse) => {
return response.json(); 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) => { .then((json: any) => {
return json.value.map((item: any) => { return json.value.map((item: any) => {
let newItem: ITodoItem = { let newItem: ITodoItem = {
@ -111,57 +62,26 @@ export default class SharePointDataProvider implements ITodoDataProvider {
}); });
} }
private _getItemsBatched(requester: SPHttpClientBatch): Promise<ITodoItem[]> { public createItem(title: string): 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> {
const body: {} = { const body: {} = {
'@data.type': this._selectedList.ListItemEntityTypeFullName, '@data.type': this._selectedList.ListItemEntityTypeFullName,
'Title': title 'Title': title
}; };
return batch.post( return this._webPartContext.spHttpClient.post(`${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items`, SPHttpClient.configurations.v1, {
`${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items`, body: JSON.stringify(body)
SPHttpClientBatch.configurations.v1, })
{ body: JSON.stringify(body) } .then(() => {
); return this.getItems();
});
} }
private _deleteItem(batch: SPHttpClientBatch, item: ITodoItem): Promise<SPHttpClientResponse> { public deleteItem(itemToBeDeleted: ITodoItem): Promise<ITodoItem[]> {
const itemDeletedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${item.Id})`; const itemDeletedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${itemToBeDeleted.Id})`;
const headers: Headers = new Headers(); const headers: Headers = new Headers();
headers.append('If-Match', '*'); headers.append('If-Match', '*');
return batch.fetch(itemDeletedUrl, return this.webPartContext.spHttpClient.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,
SPHttpClient.configurations.v1, SPHttpClient.configurations.v1,
{ {
headers, headers,
@ -174,42 +94,26 @@ export default class SharePointDataProvider implements ITodoDataProvider {
return Promise.reject(new Error(JSON.stringify(response))); return Promise.reject(new Error(JSON.stringify(response)));
} }
}).then(() => { }).then(() => {
return this._getItems(requester); return this.getItems();
}); });
} }
private _updateItem(batch: SPHttpClientBatch, item: ITodoItem): Promise<SPHttpClientResponse> { 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 itemUpdatedUrl: string = `${this._webPartContext.pageContext.web.absoluteUrl}/_api/web/lists(guid'${this._selectedList.Id}')/items(${item.Id})`;
const headers: Headers = new Headers(); const headers: Headers = new Headers();
headers.append('If-Match', '*'); headers.append('If-Match', '*');
const body: {} = { const body: {} = {
'@data.type': this._selectedList.ListItemEntityTypeFullName, '@data.type': this._selectedList.ListItemEntityTypeFullName,
'PercentComplete': item.PercentComplete 'PercentComplete': itemUpdated.PercentComplete
}; };
return batch.fetch(itemUpdatedUrl, return this._webPartContext.spHttpClient.fetch(itemUpdatedUrl, SPHttpClient.configurations.v1, {
SPHttpClientBatch.configurations.v1, body: JSON.stringify(body),
{ headers,
body: JSON.stringify(body), method: 'PATCH'
headers, }).then(() => {
method: 'PATCH' return this.getItems();
} });
);
}
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;
});
} }
} }

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", "id": "a0e1eddd-ea67-4775-a52b-0141c5807146",
"alias": "ToDoWebPart", "alias": "ToDoWebPart",
"componentType": "WebPart", "componentType": "WebPart",
"version": "0.0.1", "version": "0.0.1",
"manifestVersion": 2, "manifestVersion": 2,
"supportedHosts": ["SharePointWebPart"],
"preconfiguredEntries": [{ "preconfiguredEntries": [{
"groupId": "a0e1eddd-ea67-4775-a52b-0141c5807146", "groupId": "a0e1eddd-ea67-4775-a52b-0141c5807146",

View File

@ -1,15 +1,15 @@
import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library'; import { Version, Environment, EnvironmentType } from '@microsoft/sp-core-library';
import { import {
BaseClientSideWebPart, BaseClientSideWebPart
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown,
IPropertyPaneField,
PropertyPaneLabel,
IPropertyPaneDropdownOption
} from '@microsoft/sp-webpart-base'; } from '@microsoft/sp-webpart-base';
import { findIndex } from '@microsoft/sp-lodash-subset'; import { findIndex } from '@microsoft/sp-lodash-subset';
import {
IPropertyPaneConfiguration,
IPropertyPaneDropdownOption,
PropertyPaneDropdown
} from "@microsoft/sp-property-pane";
import Vue from 'vue'; import Vue from 'vue';
import TodoComponent from './components/todo/Todo.vue'; import TodoComponent from './components/todo/Todo.vue';
import { ITodoProps } from './components/todo/ITodoProps'; import { ITodoProps } from './components/todo/ITodoProps';

View File

@ -1,11 +1,23 @@
{ {
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "es5",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "commonjs", "module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [ "types": [
"es6-promise", "es6-promise",
"webpack-env" "webpack-env"
@ -15,5 +27,12 @@
"dom", "dom",
"es2015.collection" "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
}
} }