updated react-list-form to SPFx 1.7.1, Added RichText Mode and Tinymce Editor (#791)

* Updated to SPFx 1.7.1 and dependencies, add Turkish translation

* Reformatting space&line

* Bug&Fix reset itemId

* Added RichText Mode and Tinymce Editor
This commit is contained in:
Özgür ERSOY 2019-03-10 19:20:18 +03:00 committed by Vesa Juvonen
parent 060dd3438a
commit 97efa6e8cf
56 changed files with 36242 additions and 31985 deletions

View File

@ -71,5 +71,6 @@
],
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/copyStaticAssets/copy-static-assets.schema.json"
}
]
],
"editor.formatOnSave": true
}

View File

@ -1,8 +1,11 @@
{
"@microsoft/generator-sharepoint": {
"version": "1.4.1",
"isCreatingSolution": true,
"environment": "spo",
"version": "1.7.1",
"libraryName": "react-form-webpart",
"libraryId": "373a20ef-dfc6-456a-95ec-171de3c94581",
"environment": "spo"
"libraryId": "b092661d-5730-49ea-be27-14ee4a84eb33",
"packageManager": "npm",
"componentType": "webpart"
}
}
}

3
samples/react-list-form/README.md Normal file → Executable file
View File

@ -25,7 +25,8 @@ react-list-form|Dany Wyss
Version|Date|Comments
-------|----|--------
1.0|November 24, 2017|Initial release
1.0.0|November 24, 2017|Initial release
1.0.1|February 22, 2019|Updated to SPFx 1.7.1 and dependencies, Added Turkish translation, Added RichText Mode and Tinymce Editor
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**

2
samples/react-list-form/config/config.json Normal file → Executable file
View File

@ -12,7 +12,7 @@
}
},
"externals": {
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.0/moment-with-locales.min.js"
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min.js"
},
"localizedResources": {
"ListFormWebPartStrings": "lib/webparts/listForm/loc/{locale}.js",

0
samples/react-list-form/config/copy-assets.json Normal file → Executable file
View File

View File

5
samples/react-list-form/config/package-solution.json Normal file → Executable file
View File

@ -3,11 +3,12 @@
"solution": {
"name": "react-form-webpart-client-side-solution",
"id": "373a20ef-dfc6-456a-95ec-171de3c94581",
"version": "1.0.0.0",
"version": "1.0.1.0",
"title": "List form",
"supportedLocales": [
"en-US",
"fr-FR"
"fr-FR",
"tr-TR"
],
"skipFeatureDeployment": true,
"includeClientSideAssets": true

0
samples/react-list-form/config/serve.json Normal file → Executable file
View File

View File

@ -1,45 +0,0 @@
{
"$schema": "https://dev.office.com/json-schemas/core-build/tslint.schema.json",
// 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-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
}
}
}

0
samples/react-list-form/config/write-manifests.json Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

31
samples/react-list-form/package.json Normal file → Executable file
View File

@ -1,38 +1,37 @@
{
"name": "react-form-webpart",
"version": "0.0.1",
"version": "1.0.1",
"private": true,
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"serve": "gulp serve",
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.6.0",
"@microsoft/sp-office-ui-fabric-core": "1.6.0",
"@microsoft/sp-webpart-base": "1.6.0",
"@microsoft/sp-core-library": "1.7.1",
"@microsoft/sp-lodash-subset": "1.7.1",
"@microsoft/sp-office-ui-fabric-core": "1.7.1",
"@microsoft/sp-webpart-base": "1.7.1",
"@tinymce/tinymce-react": "^3.0.1",
"@types/es6-promise": "0.0.33",
"@types/react": "15.6.6",
"@types/react-addons-shallow-compare": "0.14.17",
"@types/react-addons-test-utils": "0.14.15",
"@types/react-addons-update": "0.14.14",
"@types/react-dnd": "~2.0.34",
"@types/react-dom": "15.5.6",
"@types/webpack-env": "1.13.1",
"moment": "~2.22.0",
"react": "15.6.2",
"moment": "^2.24.0",
"react-dnd": "~2.5.4",
"react-dnd-html5-backend": "~2.5.4",
"react-dom": "15.6.2",
"spfx-uifabric-themes": "~0.1.3"
"react-html-parser": "^2.0.2",
"spfx-uifabric-themes": "~0.1.3",
"tinymce": "^5.0.1"
},
"devDependencies": {
"@microsoft/sp-build-web": "1.6.0",
"@microsoft/sp-module-interfaces": "1.6.0",
"@microsoft/sp-webpart-workbench": "1.6.0",
"@microsoft/sp-build-web": "1.7.1",
"@microsoft/sp-tslint-rules": "1.7.1",
"@microsoft/sp-module-interfaces": "1.7.1",
"@microsoft/sp-webpart-workbench": "1.7.1",
"@types/chai": "3.4.34",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",

View File

@ -1,53 +1,53 @@
export const Locales = {
1025: 'ar-SA',
1026: 'bg-BG',
1027: 'ca-ES',
1028: 'zh-TW',
1029: 'cs-CZ',
1030: 'da-DK',
1031: 'de-DE',
1032: 'el-GR',
1033: 'en-US',
1035: 'fi-FI',
1036: 'fr-FR',
1037: 'he-IL',
1038: 'hu-HU',
1040: 'it-IT',
1041: 'ja-JP',
1042: 'ko-KR',
1043: 'nl-NL',
1044: 'nb-NO',
1045: 'pl-PL',
1046: 'pt-BR',
1048: 'ro-RO',
1049: 'ru-RU',
1050: 'hr-HR',
1051: 'sk-SK',
1053: 'sv-SE',
1054: 'th-TH',
1055: 'tr-TR',
1057: 'id-ID',
1058: 'uk-UA',
1060: 'sl-SI',
1061: 'et-EE',
1062: 'lv-LV',
1063: 'lt-LT',
1066: 'vi-VN',
1068: 'az-Latn-AZ',
1069: 'eu-ES',
1071: 'mk-MK',
1081: 'hi-IN',
1086: 'ms-MY',
1087: 'kk-KZ',
1106: 'cy-GB',
1110: 'gl-ES',
1164: 'prs-AF',
2052: 'zh-CN',
2070: 'pt-PT',
2074: 'sr-Latn-CS',
2108: 'ga-IE',
3082: 'es-ES',
5146: 'bs-Latn-BA',
9242: 'sr-Latn-RS',
10266: 'sr-Cyrl-RS',
};
1025: 'ar-SA',
1026: 'bg-BG',
1027: 'ca-ES',
1028: 'zh-TW',
1029: 'cs-CZ',
1030: 'da-DK',
1031: 'de-DE',
1032: 'el-GR',
1033: 'en-US',
1035: 'fi-FI',
1036: 'fr-FR',
1037: 'he-IL',
1038: 'hu-HU',
1040: 'it-IT',
1041: 'ja-JP',
1042: 'ko-KR',
1043: 'nl-NL',
1044: 'nb-NO',
1045: 'pl-PL',
1046: 'pt-BR',
1048: 'ro-RO',
1049: 'ru-RU',
1050: 'hr-HR',
1051: 'sk-SK',
1053: 'sv-SE',
1054: 'th-TH',
1055: 'tr-TR',
1057: 'id-ID',
1058: 'uk-UA',
1060: 'sl-SI',
1061: 'et-EE',
1062: 'lv-LV',
1063: 'lt-LT',
1066: 'vi-VN',
1068: 'az-Latn-AZ',
1069: 'eu-ES',
1071: 'mk-MK',
1081: 'hi-IN',
1086: 'ms-MY',
1087: 'kk-KZ',
1106: 'cy-GB',
1110: 'gl-ES',
1164: 'prs-AF',
2052: 'zh-CN',
2070: 'pt-PT',
2074: 'sr-Latn-CS',
2108: 'ga-IE',
3082: 'es-ES',
5146: 'bs-Latn-BA',
9242: 'sr-Latn-RS',
10266: 'sr-Cyrl-RS',
};

View File

@ -1,11 +1,10 @@
import * as React from 'react';
import { IWebPartContext} from '@microsoft/sp-webpart-base';
import { IWebPartContext } from '@microsoft/sp-webpart-base';
import { MessageBar, MessageBarType } from 'office-ui-fabric-react/lib/MessageBar';
import { PrimaryButton } from 'office-ui-fabric-react/lib/Button';
import styles from './ConfigureWebPart.module.scss';
export interface IConfigureWebPartProps {
webPartContext: IWebPartContext;
title: string;
@ -13,31 +12,28 @@ export interface IConfigureWebPartProps {
buttonText?: string;
}
const ConfigureWebPart: React.SFC<IConfigureWebPartProps> = (props) => {
const {
webPartContext,
title,
description,
buttonText,
} = props;
} = props;
return (
<div className={styles.container}>
<div className={styles.title}>{title}</div>
<div className={styles.description}>
<MessageBar messageBarType={MessageBarType.info} >
{description ? description : 'Please configure this web part\'s properties first.'}
</MessageBar>
</div>
<div className={styles.button}>
<PrimaryButton iconProps={ { iconName: 'Edit' } } onClick={ (e) => {e.preventDefault(); webPartContext.propertyPane.open(); } }>
{buttonText ? buttonText : 'Configure Web Part'}
</PrimaryButton>
</div>
<div className={styles.container}>
<div className={styles.title}>{title}</div>
<div className={styles.description}>
<MessageBar messageBarType={MessageBarType.info} >
{description ? description : 'Please configure this web part\'s properties first.'}
</MessageBar>
</div>
);
<div className={styles.button}>
<PrimaryButton iconProps={{ iconName: 'Edit' }} onClick={(e) => { e.preventDefault(); webPartContext.propertyPane.open(); }}>
{buttonText ? buttonText : 'Configure Web Part'}
</PrimaryButton>
</div>
</div>
);
};
export default ConfigureWebPart;

View File

@ -1,12 +1,11 @@
import { ControlMode } from '../datatypes/ControlMode';
import { IFieldSchema } from './datatypes/RenderListData';
export interface IListFormService {
getFieldSchemasForForm: (webUrl: string, listUrl: string, formType: ControlMode) => Promise<IFieldSchema[]>;
getDataForForm: (webUrl: string, listUrl: string, itemId: number, formType: ControlMode) => Promise<any>;
updateItem: (webUrl: string, listUrl: string, itemId: number,
fieldsSchema: IFieldSchema[],
data: any, originalData: any) => Promise<any>;
fieldsSchema: IFieldSchema[],
data: any, originalData: any) => Promise<any>;
createItem: (webUrl: string, listUrl: string, fieldsSchema: IFieldSchema[], data: any) => Promise<any>;
}

View File

@ -21,7 +21,7 @@ export class ListFormService implements IListFormService {
* @param formType The type of form (Display, New, Edit)
* @returns Promise object represents the array of field schema for all relevant fields for this list form.
*/
public getFieldSchemasForForm( webUrl: string, listUrl: string, formType: ControlMode ): Promise<IFieldSchema[]> {
public getFieldSchemasForForm(webUrl: string, listUrl: string, formType: ControlMode): Promise<IFieldSchema[]> {
return new Promise<IFieldSchema[]>((resolve, reject) => {
const httpClientOptions: ISPHttpClientOptions = {
headers: {
@ -38,24 +38,24 @@ export class ListFormService implements IListFormService {
ViewXml: '<View><ViewFields><FieldRef Name="ID"/></ViewFields></View>',
RenderOptions: RenderListDataOptions.clientFormSchema,
},
}),
}),
};
const endpoint = `${webUrl}/_api/web/GetList(@listUrl)/RenderListDataAsStream`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions)
.then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
reject( this.getErrorMessage(webUrl, response) );
reject(this.getErrorMessage(webUrl, response));
}
})
.then((data) => {
const form = (formType === ControlMode.New) ? data.ClientForms.New : data.ClientForms.Edit;
resolve( form[ Object.keys( form )[0] ] );
resolve(form[Object.keys(form)[0]]);
})
.catch((error) => {
reject( this.getErrorMessage(webUrl, error) );
reject(this.getErrorMessage(webUrl, error));
});
});
}
@ -69,28 +69,28 @@ export class ListFormService implements IListFormService {
* @param formType The type of form (Display, New, Edit)
* @returns Promise representing an object containing all the field values for the list item.
*/
public getDataForForm( webUrl: string, listUrl: string, itemId: number, formType: ControlMode ): Promise<any> {
public getDataForForm(webUrl: string, listUrl: string, itemId: number, formType: ControlMode): Promise<any> {
if (!listUrl || (!itemId) || (itemId === 0)) {
return Promise.resolve({}); // no data, so returns empty
}
return new Promise<any>((resolve, reject) => {
const httpClientOptions: ISPHttpClientOptions = {
headers: {
'Accept': 'application/json;odata=verbose',
'Content-type': 'application/json;odata=verbose',
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
'odata-version': '',
'Accept': 'application/json;odata=verbose',
'Content-type': 'application/json;odata=verbose',
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
'odata-version': '',
},
};
const endpoint = `${webUrl}/_api/web/GetList(@listUrl)/RenderExtendedListFormData`
+ `(itemId=${itemId},formId='editform',mode='2',options=7)`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
+ `(itemId=${itemId},formId='editform',mode='2',options=7)`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions)
.then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
reject( this.getErrorMessage(webUrl, response) );
reject(this.getErrorMessage(webUrl, response));
}
})
.then((data) => {
@ -102,7 +102,7 @@ export class ListFormService implements IListFormService {
}
})
.catch((error) => {
reject( this.getErrorMessage(webUrl, error) );
reject(this.getErrorMessage(webUrl, error));
});
});
}
@ -118,8 +118,8 @@ export class ListFormService implements IListFormService {
* @param originalData An object containing all the field values retrieved on loading from list item.
* @returns Promise object represents the updated or erroneous form field values.
*/
public updateItem( webUrl: string, listUrl: string, itemId: number,
fieldsSchema: IFieldSchema[], data: any, originalData: any ): Promise<any> {
public updateItem(webUrl: string, listUrl: string, itemId: number,
fieldsSchema: IFieldSchema[], data: any, originalData: any): Promise<any> {
return new Promise<any>((resolve, reject) => {
const httpClientOptions: ISPHttpClientOptions = {
headers: {
@ -127,7 +127,7 @@ export class ListFormService implements IListFormService {
'Content-type': 'application/json;odata=verbose',
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
'odata-version': '',
},
},
};
const formValues = this.GetFormValues(fieldsSchema, data, originalData);
@ -137,20 +137,20 @@ export class ListFormService implements IListFormService {
formValues,
});
const endpoint = `${webUrl}/_api/web/GetList(@listUrl)/items(@itemId)/ValidateUpdateListItem()`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}&@itemId=%27${itemId}%27`;
this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions )
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}&@itemId=%27${itemId}%27`;
this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions)
.then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
reject( this.getErrorMessage(webUrl, response) );
reject(this.getErrorMessage(webUrl, response));
}
})
.then((respData) => {
resolve( respData.d.ValidateUpdateListItem.results );
resolve(respData.d.ValidateUpdateListItem.results);
})
.catch((error) => {
reject( this.getErrorMessage(webUrl, error) );
reject(this.getErrorMessage(webUrl, error));
});
});
}
@ -164,7 +164,7 @@ export class ListFormService implements IListFormService {
* @param data An object containing all the field values to set on creating item.
* @returns Promise object represents the updated or erroneous form field values.
*/
public createItem( webUrl: string, listUrl: string, fieldsSchema: IFieldSchema[], data: any ): Promise<any> {
public createItem(webUrl: string, listUrl: string, fieldsSchema: IFieldSchema[], data: any): Promise<any> {
return new Promise<any>((resolve, reject) => {
const formValues = this.GetFormValues(fieldsSchema, data, {});
const httpClientOptions: ISPHttpClientOptions = {
@ -188,9 +188,9 @@ export class ListFormService implements IListFormService {
}),
};
const endpoint = `${webUrl}/_api/web/GetList(@listUrl)/AddValidateUpdateItemUsingPath`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
this.spHttpClient.post( endpoint, SPHttpClient.configurations.v1, httpClientOptions )
.then( (response: SPHttpClientResponse) => {
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}`;
this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions)
.then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
@ -198,25 +198,25 @@ export class ListFormService implements IListFormService {
}
})
.then((respData) => {
resolve( respData.d.AddValidateUpdateItemUsingPath.results );
resolve(respData.d.AddValidateUpdateItemUsingPath.results);
})
.catch((error) => {
reject( this.getErrorMessage(webUrl, error) );
reject(this.getErrorMessage(webUrl, error));
});
});
}
private GetFormValues( fieldsSchema: IFieldSchema[], data: any, originalData: any )
: Array<{ FieldName: string, FieldValue: any, HasException: boolean, ErrorMessage: string }> {
private GetFormValues(fieldsSchema: IFieldSchema[], data: any, originalData: any)
: Array<{ FieldName: string, FieldValue: any, HasException: boolean, ErrorMessage: string }> {
return fieldsSchema.filter(
(field) => (
(!field.ReadOnlyField)
&& (field.InternalName in data)
&& (data[field.InternalName] !== null)
&& (data[field.InternalName] !== originalData[field.InternalName])
),
)
.map( (field) => {
(field) => (
(!field.ReadOnlyField)
&& (field.InternalName in data)
&& (data[field.InternalName] !== null)
&& (data[field.InternalName] !== originalData[field.InternalName])
),
)
.map((field) => {
return {
ErrorMessage: null,
FieldName: field.InternalName,
@ -224,14 +224,14 @@ export class ListFormService implements IListFormService {
HasException: false,
};
},
);
);
}
/**
* Returns an error message based on the specified error object
* @param error : An error string/object
*/
private getErrorMessage( webUrl: string, error: any ): string {
private getErrorMessage(webUrl: string, error: any): string {
let errorMessage: string = error.statusText ? error.statusText : error.statusMessage ? error.statusMessage : error;
const serverUrl = `{window.location.protocol}//{window.location.hostname}`;
const webServerRelativeUrl = webUrl.replace(serverUrl, '');

View File

@ -1,34 +1,30 @@
import { Text } from '@microsoft/sp-core-library';
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
export class ListService {
private spHttpClient: SPHttpClient;
constructor(spHttpClient: SPHttpClient) {
this.spHttpClient = spHttpClient;
}
public getListsFromWeb(webUrl: string): Promise<Array<{url: string, title: string}>> {
return new Promise<Array<{url: string, title: string}>>((resolve, reject) => {
public getListsFromWeb(webUrl: string): Promise<Array<{ url: string, title: string }>> {
return new Promise<Array<{ url: string, title: string }>>((resolve, reject) => {
const endpoint = Text.format('{0}/_api/web/lists?$select=Title,RootFolder/ServerRelativeUrl&$filter=(IsPrivate eq false) and (IsCatalog eq false) and (Hidden eq false)&$expand=RootFolder', webUrl);
this.spHttpClient.get(endpoint, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
if (response.ok) {
response.json().then((data: any) => {
const listTitles: Array<{url: string, title: string}> = data.value.map((list) => {
return {url: list.RootFolder.ServerRelativeUrl, title: list.Title};
});
resolve( listTitles.sort( (a, b) => a.title.localeCompare(b.title)) );
const listTitles: Array<{ url: string, title: string }> = data.value.map((list) => {
return { url: list.RootFolder.ServerRelativeUrl, title: list.Title };
});
resolve(listTitles.sort((a, b) => a.title.localeCompare(b.title)));
})
.catch((error) => { reject(error); });
.catch((error) => { reject(error); });
} else {
reject(response);
}
})
.catch((error) => { reject(error); });
.catch((error) => { reject(error); });
});
}
}

View File

@ -4,16 +4,16 @@ export enum RenderListDataOptions {
listData = 2,
listSchema = 4,
menuView = 8,
listContentType= 16,
fileSystemItemId= 32,
clientFormSchema= 64,
quickLaunch= 128,
spotlight= 256,
visualization= 512,
viewMetadata= 1024,
disableAutoHyperlink= 2048,
enableMediaTAUrls= 4096,
parentInfo= 8192,
listContentType = 16,
fileSystemItemId = 32,
clientFormSchema = 64,
quickLaunch = 128,
spotlight = 256,
visualization = 512,
viewMetadata = 1024,
disableAutoHyperlink = 2048,
enableMediaTAUrls = 4096,
parentInfo = 8192,
}
export interface IChoice {

View File

@ -1,6 +1,6 @@
define([], function() {
return {
ErrorWebAccessDenied: "You do not have access to the previously configured web url '{0}'. Either leave the WebPart properties as is or select another web url.",
ErrorWebNotFound: "The previously configured web url '{0}' is not found anymore. Either leave the WebPart properties as is or select another web url.",
}
});
define([], function () {
return {
ErrorWebAccessDenied: "You do not have access to the previously configured web url '{0}'. Either leave the WebPart properties as is or select another web url.",
ErrorWebNotFound: "The previously configured web url '{0}' is not found anymore. Either leave the WebPart properties as is or select another web url.",
}
});

View File

@ -7,4 +7,4 @@ declare interface IServicesStrings {
declare module 'servicesStrings' {
const strings: IServicesStrings;
export = strings;
}
}

View File

@ -0,0 +1,6 @@
define([], function () {
return {
ErrorWebAccessDenied: "'{0}' önceden yapılandırılmış web url'ne erişiminiz yok. Web bölümü özelliklerini olduğu gibi bırakın veya başka bir web url seçin.",
ErrorWebNotFound: "Önceden yapılandırılmış web url '{0}' artık bulunamadı. Web bölümü özelliklerini olduğu gibi bırakın veya başka bir web url seçin.",
}
});

View File

@ -1,5 +1,4 @@
import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';
import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps {
}
export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps { }

View File

@ -4,7 +4,6 @@ import { Spinner } from 'office-ui-fabric-react/lib/components/Spinner';
import { IAsyncDropdownProps } from './IAsyncDropdownProps';
import { IAsyncDropdownState } from './IAsyncDropdownState';
export default class AsyncDropdown extends React.Component<IAsyncDropdownProps, IAsyncDropdownState> {
private selectedKey: React.ReactText;
@ -33,7 +32,7 @@ export default class AsyncDropdown extends React.Component<IAsyncDropdownProps,
public render(): JSX.Element {
const loading = this.state.loading;
const error: JSX.Element = this.state.error !== undefined
? <div className={'ms-TextField-errorMessage ms-u-slideDownIn20'}>Error while loading items: {this.state.error}</div> : <div />;
? <div className={'ms-TextField-errorMessage ms-u-slideDownIn20'}>Error while loading items: {this.state.error}</div> : <div />;
return (
<div>
@ -42,7 +41,7 @@ export default class AsyncDropdown extends React.Component<IAsyncDropdownProps,
onChanged={this.onChanged.bind(this)}
selectedKey={this.selectedKey}
options={this.state.options}
{...loading ? {onRenderCaretDown: () => <Spinner />} : {}} />
{...loading ? { onRenderCaretDown: () => <Spinner /> } : {}} />
{error}
</div>
);

View File

@ -3,35 +3,34 @@
"id": "48e2d130-7eb7-4ee9-aa23-5ddbdfd175b1",
"alias": "ListFormWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [{
"groupId": "48e2d130-7eb7-4ee9-aa23-5ddbdfd175b1",
"group": {
"default": "Under Development"
},
"title": {
"default": "List Form",
"fr-fr": "Formulaire de liste"
},
"description": {
"default": "Shows a form for the selected list",
"fr-fr": "Affiche un formulaire pour la liste sélectionnée"
},
"officeFabricIconFontName": "PreviewLink",
"properties": {
"title": "List Form",
"description": "",
"listUrl": "",
"formType": 3
"preconfiguredEntries": [
{
"groupId": "48e2d130-7eb7-4ee9-aa23-5ddbdfd175b1",
"group": {
"default": "Under Development"
},
"title": {
"default": "List Form",
"fr-fr": "Formulaire de liste"
},
"description": {
"default": "Shows a form for the selected list",
"fr-fr": "Affiche un formulaire pour la liste sélectionnée"
},
"officeFabricIconFontName": "PreviewLink",
"properties": {
"title": "List Form",
"description": "",
"listUrl": "",
"formType": 3
}
}
}]
}
]
}

View File

@ -30,14 +30,12 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
private listService: ListService;
private cachedLists = null;
protected onInit(): Promise<void> {
return super.onInit().then( ( _ ) => {
return super.onInit().then((_) => {
this.listService = new ListService(this.context.spHttpClient);
});
}
public render(): void {
let itemId;
@ -55,7 +53,7 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
// show message that local worbench is not supported
element = React.createElement(
MessageBar,
{messageBarType: MessageBarType.blocked},
{ messageBarType: MessageBarType.blocked },
strings.LocalWorkbenchUnsupported
);
} else if (this.properties.listUrl) {
@ -63,7 +61,7 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
element = React.createElement(
ListForm,
{
inDesignMode: this.displayMode === DisplayMode.Edit ,
inDesignMode: this.displayMode === DisplayMode.Edit,
spHttpClient: this.context.spHttpClient,
title: this.properties.title,
description: this.properties.description,
@ -93,47 +91,48 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
ReactDom.render(element, this.domElement);
}
protected get dataVersion(): Version {
return Version.parse('1.0');
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
const mainGroup = {
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
multiline: true
}),
new PropertyPaneAsyncDropdown('listUrl', {
label: strings.ListFieldLabel,
loadOptions: this.loadLists.bind(this),
onPropertyChange: this.onListChange.bind(this),
selectedKey: this.properties.listUrl
}),
PropertyPaneDropdown('formType', {
label: strings.FormTypeFieldLabel,
options: Object.keys(ControlMode)
.map( (k) => ControlMode[k]).filter( (v) => typeof v === 'string' )
.map( (n) => ({key: ControlMode[n], text: n}) ),
disabled: !this.properties.listUrl
}),
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
multiline: true
}),
new PropertyPaneAsyncDropdown('listUrl', {
label: strings.ListFieldLabel,
loadOptions: this.loadLists.bind(this),
onPropertyChange: this.onListChange.bind(this),
selectedKey: this.properties.listUrl
}),
PropertyPaneDropdown('formType', {
label: strings.FormTypeFieldLabel,
options: Object.keys(ControlMode)
.map((k) => ControlMode[k]).filter((v) => typeof v === 'string')
.map((n) => ({ key: ControlMode[n], text: n })),
disabled: !this.properties.listUrl
}),
]
};
]
};
if (this.properties.formType !== ControlMode.New) {
mainGroup.groupFields.push(
PropertyPaneTextField( 'itemId', {
PropertyPaneTextField('itemId', {
label: strings.ItemIdFieldLabel,
deferredValidationTime: 2000,
description: strings.ItemIdFieldDescription
}));
} else {
this.properties.itemId = null;
}
mainGroup.groupFields.push(
PropertyPaneToggle('showUnsupportedFields', {
label: strings.ShowUnsupportedFieldsLabel,
@ -159,66 +158,61 @@ export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebP
};
}
private loadLists(): Promise<IDropdownOption[]> {
return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
if (Environment.type === EnvironmentType.Local) {
resolve( [{
key: 'sharedDocuments',
text: 'Shared Documents',
},
{
key: 'someList',
text: 'Some List',
}] );
resolve([{
key: 'sharedDocuments',
text: 'Shared Documents',
},
{
key: 'someList',
text: 'Some List',
}]);
} else if (Environment.type === EnvironmentType.SharePoint ||
Environment.type === EnvironmentType.ClassicSharePoint) {
Environment.type === EnvironmentType.ClassicSharePoint) {
try {
if (!this.cachedLists) {
return this.listService.getListsFromWeb(this.context.pageContext.web.absoluteUrl)
.then( (lists) => {
this.cachedLists = lists.map( (l) => ({ key: l.url, text: l.title } as IDropdownOption) );
resolve( this.cachedLists );
} );
.then((lists) => {
this.cachedLists = lists.map((l) => ({ key: l.url, text: l.title } as IDropdownOption));
resolve(this.cachedLists);
});
} else {
// using cached lists if available to avoid loading spinner every time property pane is refreshed
return resolve( this.cachedLists );
return resolve(this.cachedLists);
}
} catch (error) {
alert( strings.ErrorOnLoadingLists + error );
alert(strings.ErrorOnLoadingLists + error);
}
}
});
}
private onListChange(propertyPath: string, newValue: any): void {
const oldValue: any = get(this.properties, propertyPath);
if (oldValue !== newValue) {
this.properties.fields = null;
}
// store new value in web part properties
update( this.properties, propertyPath, (): any => newValue );
update(this.properties, propertyPath, (): any => newValue);
// refresh property Pane
this.context.propertyPane.refresh();
// refresh web part
this.render();
}
private updateField(fields: IFieldConfiguration[]): any {
this.properties.fields = fields;
// render web part again so that React List Form component is rerendered with changed fields
this.render();
}
private formSubmitted(id: number) {
if (this.properties.redirectUrl) {
// redirect to configured URL after successfully submitting form
window.location.href = this.properties.redirectUrl.replace('[ID]', id.toString() );
window.location.href = this.properties.redirectUrl.replace('[ID]', id.toString());
}
}
}

View File

@ -9,10 +9,10 @@ import * as strings from 'ListFormStrings';
const dragSource = {
beginDrag(props: IDraggableComponentProps) {
return {
key: props.itemKey,
originalIndex: props.index,
};
return {
key: props.itemKey,
originalIndex: props.index,
};
},
endDrag(props: IDraggableComponentProps, monitor) {
@ -25,33 +25,27 @@ const dragSource = {
},
};
const dragTarget = {
hover(props: IDraggableComponentProps, monitor) {
const { key: draggedKey } = monitor.getItem();
if (draggedKey !== props.itemKey) {
props.moveField(draggedKey, props.index);
}
},
};
export interface IDraggableComponentProps {
index: number;
itemKey: string;
isDragging?: boolean;
connectDragSource?(child: any): any;
connectDropTarget?(child: any): any;
moveField(fieldKey: string, toIndex: number): void;
removeField(index: number): void;
index: number;
itemKey: string;
isDragging?: boolean;
connectDragSource?(child: any): any;
connectDropTarget?(child: any): any;
moveField(fieldKey: string, toIndex: number): void;
removeField(index: number): void;
}
@DropTarget('Fields', dragTarget, (connect) => ({
connectDropTarget: connect.dropTarget(),
}))
@ -81,4 +75,3 @@ export default class DraggableComponent extends React.Component<IDraggableCompon
));
}
}

View File

@ -1,6 +1,5 @@
import { IFieldSchema } from '../../../common/services/datatypes/RenderListData';
export interface IListFormState {
isLoadingSchema: boolean;
isLoadingData: boolean;
@ -10,6 +9,6 @@ export interface IListFormState {
originalData: any;
errors: string[];
notifications: string[];
fieldErrors: {[fieldName: string]: string};
fieldErrors: { [fieldName: string]: string };
showUnsupportedFields?: boolean;
}

View File

@ -24,7 +24,6 @@ import * as strings from 'ListFormStrings';
import styles from './ListForm.module.scss';
/*************************************************************************************
* React Component to render a SharePoint list form on any page.
* The list form can be configured to be either a new form for adding a new list item,
@ -34,11 +33,8 @@ import styles from './ListForm.module.scss';
*************************************************************************************/
class ListForm extends React.Component<IListFormProps, IListFormState> {
private listFormService: IListFormService;
constructor( props: IListFormProps ) {
constructor(props: IListFormProps) {
super(props);
// set initial state
@ -52,11 +48,9 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
notifications: [],
fieldErrors: {}
};
this.listFormService = new ListFormService(props.spHttpClient);
}
public render() {
let menuProps;
if (this.state.fieldsSchema) {
@ -64,51 +58,51 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
shouldFocusOnMount: true,
directionalHint: DirectionalHint.topCenter,
items: this.state.fieldsSchema.map(
(fld) => ({ key: fld.InternalName, name: fld.Title, onClick: (ev, item) => this.appendField(fld.InternalName) })
)
(fld) => ({ key: fld.InternalName, name: fld.Title, onClick: (ev, item) => this.appendField(fld.InternalName) })
)
};
}
return (
<div className={styles.listForm}>
<div className={css(styles.title, 'ms-font-xl')}>{this.props.title}</div>
{ (this.props.description) && <div className={styles.description}>{this.props.description}</div> }
{ this.renderNotifications() }
{ this.renderErrors() }
{ (!this.props.listUrl)
{(this.props.description) && <div className={styles.description}>{this.props.description}</div>}
{this.renderNotifications()}
{this.renderErrors()}
{(!this.props.listUrl)
? <MessageBar messageBarType={MessageBarType.warning}>Please configure a list for this component first.</MessageBar>
: '' }
{ (this.state.isLoadingSchema)
? (<Spinner size={ SpinnerSize.large } label={strings.LoadingFormIndicator} />)
: ''}
{(this.state.isLoadingSchema)
? (<Spinner size={SpinnerSize.large} label={strings.LoadingFormIndicator} />)
: ((this.state.fieldsSchema) &&
<div>
<div className={css(styles.formFieldsContainer, this.state.isLoadingData ? styles.isDataLoading : null)}>
{ this.renderFields() }
{ this.props.inDesignMode &&
<DefaultButton aria-haspopup='true' aria-label={strings.AddNewFieldAction} className={styles.addFieldToolbox}
title={strings.AddNewFieldAction} menuProps={menuProps} data-is-focusable='false' >
<div className={styles.addFieldToolboxPlusButton}>
<i aria-hidden='true' className='ms-Icon ms-Icon--CircleAdditionSolid' />
</div>
</DefaultButton>
}
</div>
<div className={styles.formButtonsContainer}>
{(this.props.formType !== ControlMode.Display) &&
<PrimaryButton
disabled={ false }
text={strings.SaveButtonText}
onClick={ () => this.saveItem() }
/>
}
<DefaultButton
disabled={ false }
text={strings.CancelButtonText}
onClick={ () => this.readData(this.props.listUrl, this.props.formType, this.props.id) }
/>
</div>
<div>
<div className={css(styles.formFieldsContainer, this.state.isLoadingData ? styles.isDataLoading : null)}>
{this.renderFields()}
{this.props.inDesignMode &&
<DefaultButton aria-haspopup='true' aria-label={strings.AddNewFieldAction} className={styles.addFieldToolbox}
title={strings.AddNewFieldAction} menuProps={menuProps} data-is-focusable='false' >
<div className={styles.addFieldToolboxPlusButton}>
<i aria-hidden='true' className='ms-Icon ms-Icon--CircleAdditionSolid' />
</div>
</DefaultButton>
}
</div>
)
}
<div className={styles.formButtonsContainer}>
{(this.props.formType !== ControlMode.Display) &&
<PrimaryButton
disabled={false}
text={strings.SaveButtonText}
onClick={() => this.saveItem()}
/>
}
<DefaultButton
disabled={false}
text={strings.CancelButtonText}
onClick={() => this.readData(this.props.listUrl, this.props.formType, this.props.id)}
/>
</div>
</div>
)
}
</div>
);
}
@ -117,84 +111,83 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
if (this.state.notifications.length === 0) {
return null;
}
setTimeout( () => { this.setState({...this.state, notifications: []}); }, 4000 );
setTimeout(() => { this.setState({ ...this.state, notifications: [] }); }, 4000);
return <div>
{
this.state.notifications.map( (item, idx) =>
<MessageBar messageBarType={ MessageBarType.success }>{item}</MessageBar>
this.state.notifications.map((item, idx) =>
<MessageBar messageBarType={MessageBarType.success}>{item}</MessageBar>
)
}
</div>;
}
private renderErrors() {
return this.state.errors.length > 0
?
<div>
{
this.state.errors.map( (item, idx) =>
<MessageBar
messageBarType={ MessageBarType.error }
isMultiline={ true }
onDismiss={ (ev) => this.clearError(idx) }
>
{item}
</MessageBar>
)
}
</div>
: null;
return this.state.errors.length > 0
?
<div>
{
this.state.errors.map((item, idx) =>
<MessageBar
messageBarType={MessageBarType.error}
isMultiline={true}
onDismiss={(ev) => this.clearError(idx)}
>
{item}
</MessageBar>
)
}
</div>
: null;
}
private renderFields() {
const { fieldsSchema, data, fieldErrors } = this.state;
const fields = this.getFields();
return (fields && (fields.length > 0))
?
<div className='ard-formFieldsContainer' >
?
<div className='ard-formFieldsContainer' >
{
fields.map((field, idx) => {
const fieldSchemas = fieldsSchema.filter((f) => f.InternalName === field.fieldName);
if (fieldSchemas.length > 0) {
const fieldSchema = fieldSchemas[0];
const value = data[field.fieldName];
let extraData;
if (data.hasOwnProperty(field.fieldName + '.')) {
extraData = data[field.fieldName + '.'];
} else {
extraData = Object.keys(data)
.filter( (propName) => propName.indexOf(field.fieldName + '.') === 0 )
.reduce( (newData, pn) => { newData[pn.substring(field.fieldName.length + 1)] = data[pn]; return newData; }, {} );
}
const errorMessage = fieldErrors[field.fieldName];
const fieldComponent = SPFormField({
fieldSchema: fieldSchema,
controlMode: this.props.formType,
value: value,
extraData: extraData,
errorMessage: errorMessage,
hideIfFieldUnsupported: !this.props.showUnsupportedFields,
valueChanged: (val) => this.valueChanged(field.fieldName, val) });
if (fieldComponent && this.props.inDesignMode) {
return (
<DraggableComponent
key={field.key}
index={idx}
itemKey={field.key}
moveField={(dragIdx, hoverIdx) => this.moveField(dragIdx, hoverIdx)}
removeField={(index) => this.removeField(index)} >
{fieldComponent}
</DraggableComponent>);
} else {
return fieldComponent;
}
}
})
fields.map((field, idx) => {
const fieldSchemas = fieldsSchema.filter((f) => f.InternalName === field.fieldName);
if (fieldSchemas.length > 0) {
const fieldSchema = fieldSchemas[0];
const value = data[field.fieldName];
let extraData;
if (data.hasOwnProperty(field.fieldName + '.')) {
extraData = data[field.fieldName + '.'];
} else {
extraData = Object.keys(data)
.filter((propName) => propName.indexOf(field.fieldName + '.') === 0)
.reduce((newData, pn) => { newData[pn.substring(field.fieldName.length + 1)] = data[pn]; return newData; }, {});
}
const errorMessage = fieldErrors[field.fieldName];
const fieldComponent = SPFormField({
fieldSchema: fieldSchema,
controlMode: this.props.formType,
value: value,
extraData: extraData,
errorMessage: errorMessage,
hideIfFieldUnsupported: !this.props.showUnsupportedFields,
valueChanged: (val) => this.valueChanged(field.fieldName, val)
});
if (fieldComponent && this.props.inDesignMode) {
return (
<DraggableComponent
key={field.key}
index={idx}
itemKey={field.key}
moveField={(dragIdx, hoverIdx) => this.moveField(dragIdx, hoverIdx)}
removeField={(index) => this.removeField(index)} >
{fieldComponent}
</DraggableComponent>);
} else {
return fieldComponent;
}
}
})
}
</div>
: <MessageBar messageBarType={MessageBarType.warning}>No fields available!</MessageBar>;
</div>
: <MessageBar messageBarType={MessageBarType.warning}>No fields available!</MessageBar>;
}
@ -215,75 +208,71 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
}
}
@autobind
private async readSchema(listUrl: string, formType: ControlMode): Promise<void> {
try {
if (!listUrl) {
this.setState({...this.state, isLoadingSchema: false, fieldsSchema: null, errors: [strings.ConfigureListMessage]});
return;
}
this.setState({ ...this.state, isLoadingSchema: true });
const fieldsSchema = await this.listFormService.getFieldSchemasForForm(
this.props.webUrl,
listUrl,
formType,
);
this.setState({ ...this.state, isLoadingSchema: false, fieldsSchema });
} catch (error) {
const errorText = `${strings.ErrorLoadingSchema}${listUrl}: ${error}`;
this.setState({
...this.state,
isLoadingSchema: false,
fieldsSchema: null,
errors: [...this.state.errors, errorText],
});
}
try {
if (!listUrl) {
this.setState({ ...this.state, isLoadingSchema: false, fieldsSchema: null, errors: [strings.ConfigureListMessage] });
return;
}
this.setState({ ...this.state, isLoadingSchema: true });
const fieldsSchema = await this.listFormService.getFieldSchemasForForm(
this.props.webUrl,
listUrl,
formType,
);
this.setState({ ...this.state, isLoadingSchema: false, fieldsSchema });
} catch (error) {
const errorText = `${strings.ErrorLoadingSchema}${listUrl}: ${error}`;
this.setState({
...this.state,
isLoadingSchema: false,
fieldsSchema: null,
errors: [...this.state.errors, errorText],
});
}
}
@autobind
private async readData(listUrl: string, formType: ControlMode, id?: number): Promise<void> {
try {
if ((formType === ControlMode.New) || !id) {
const data = this.state.fieldsSchema
.reduce( (newData, fld) => { newData[fld.InternalName] = fld.DefaultValue; return newData; }, {} );
this.setState({ ...this.state, data: data, originalData: {...data}, fieldErrors: {}, isLoadingData: false});
.reduce((newData, fld) => { newData[fld.InternalName] = fld.DefaultValue; return newData; }, {});
this.setState({ ...this.state, data: data, originalData: { ...data }, fieldErrors: {}, isLoadingData: false });
return;
}
this.setState({ ...this.state, data: {}, originalData: {}, fieldErrors: {}, isLoadingData: true});
const dataObj = await this.listFormService.getDataForForm( this.props.webUrl, listUrl, id, formType );
this.setState({ ...this.state, data: {}, originalData: {}, fieldErrors: {}, isLoadingData: true });
const dataObj = await this.listFormService.getDataForForm(this.props.webUrl, listUrl, id, formType);
// We shallow clone here, so that changing values on dataObj object fields won't be changing in originalData too
const dataObjOriginal = { ...dataObj };
this.setState({...this.state, data: dataObj, originalData: dataObjOriginal, isLoadingData: false});
this.setState({ ...this.state, data: dataObj, originalData: dataObjOriginal, isLoadingData: false });
} catch (error) {
const errorText = `${strings.ErrorLoadingData}${id}: ${error}`;
this.setState({ ...this.state, data: {}, isLoadingData: false, errors: [...this.state.errors, errorText] });
}
}
@autobind
private valueChanged(fieldName: string, newValue: any) {
this.setState((prevState, props) => {
return {
...prevState,
data: {...prevState.data, [fieldName]: newValue},
fieldErrors: {
...prevState.fieldErrors,
[fieldName]:
(prevState.fieldsSchema.filter((item) => item.InternalName === fieldName)[0].Required) && !newValue
return {
...prevState,
data: { ...prevState.data, [fieldName]: newValue },
fieldErrors: {
...prevState.fieldErrors,
[fieldName]:
(prevState.fieldsSchema.filter((item) => item.InternalName === fieldName)[0].Required) && !newValue
? strings.RequiredValueMessage
: ''
}
};
},
}
};
},
);
}
private async saveItem(): Promise<void> {
this.setState({ ...this.state, isSaving: true, errors: []});
this.setState({ ...this.state, isSaving: true, errors: [] });
try {
let updatedValues;
if (this.props.id) {
@ -302,9 +291,9 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
this.state.data);
}
let dataReloadNeeded = false;
const newState: IListFormState = {...this.state, fieldErrors: {}};
const newState: IListFormState = { ...this.state, fieldErrors: {} };
let hadErrors = false;
updatedValues.filter( (fieldVal) => fieldVal.HasException ).forEach( (element) => {
updatedValues.filter((fieldVal) => fieldVal.HasException).forEach((element) => {
newState.fieldErrors[element.FieldName] = element.ErrorMessage;
hadErrors = true;
});
@ -325,9 +314,9 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
newState.originalData = { ...newState.data };
let id = (this.props.id) ? this.props.id : 0;
if (id === 0) {
id = updatedValues.filter( (val) => val.FieldName === 'Id' )[0].FieldValue;
id = updatedValues.filter((val) => val.FieldName === 'Id')[0].FieldValue;
}
if (this.props.onSubmitSucceeded) { this.props.onSubmitSucceeded( id ); }
if (this.props.onSubmitSucceeded) { this.props.onSubmitSucceeded(id); }
newState.notifications = [...newState.notifications, strings.ItemSavedSuccessfully];
dataReloadNeeded = true;
}
@ -341,51 +330,46 @@ class ListForm extends React.Component<IListFormProps, IListFormState> {
}
}
private clearError(idx: number) {
this.setState( (prevState, props) => {
return {...prevState, errors: prevState.errors.splice( idx, 1 )};
} );
this.setState((prevState, props) => {
return { ...prevState, errors: prevState.errors.splice(idx, 1) };
});
}
private getFields(): IFieldConfiguration[] {
let fields = this.props.fields;
if ((!fields) && this.state.fieldsSchema) {
fields = this.state.fieldsSchema.map( (field) => ({key: field.InternalName, fieldName: field.InternalName}) );
fields = this.state.fieldsSchema.map((field) => ({ key: field.InternalName, fieldName: field.InternalName }));
}
return fields;
}
private appendField(fieldName: string) {
const newFields = this.getFields();
let fieldKey = fieldName;
let indexer = 0;
while (newFields.some( (fld) => fld.key === fieldKey )) {
while (newFields.some((fld) => fld.key === fieldKey)) {
indexer++;
fieldKey = fieldName + '_' + indexer;
}
newFields.push({key: fieldKey, fieldName: fieldName});
newFields.push({ key: fieldKey, fieldName: fieldName });
this.props.onUpdateFields(newFields);
}
private moveField(fieldKey, toIndex) {
const fields = this.getFields();
const dragField = fields.filter( (fld) => fld.key === fieldKey )[0];
const dragIndex = fields.indexOf(dragField);
const newFields = fields.splice(0); // clone
newFields.splice(dragIndex, 1);
newFields.splice(toIndex, 0, dragField);
this.props.onUpdateFields(newFields);
const fields = this.getFields();
const dragField = fields.filter((fld) => fld.key === fieldKey)[0];
const dragIndex = fields.indexOf(dragField);
const newFields = fields.splice(0); // clone
newFields.splice(dragIndex, 1);
newFields.splice(toIndex, 0, dragField);
this.props.onUpdateFields(newFields);
}
private removeField(index: number) {
const newFields = this.getFields().splice(0); // clone
newFields.splice(index, 1);
this.props.onUpdateFields(newFields);
const newFields = this.getFields().splice(0); // clone
newFields.splice(index, 1);
this.props.onUpdateFields(newFields);
}
}

View File

@ -1,15 +1,13 @@
import * as React from 'react';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { DatePicker, DayOfWeek, IDatePickerProps, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
import { DatePicker, DayOfWeek, IDatePickerProps, IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
import * as strings from 'FormFieldStrings';
export interface IDateFormFieldProps extends IDatePickerProps {
locale: string;
}
export default class DateFormField extends React.Component<IDateFormFieldProps> {
constructor(props) {
super(props);
@ -19,8 +17,8 @@ export default class DateFormField extends React.Component<IDateFormFieldProps>
return (
<DatePicker
{...this.props}
parseDateFromString={ (dateStr: string) => new Date( Date.parse(dateStr) )}
formatDate={ (date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(this.props.locale) : '' }
parseDateFromString={(dateStr: string) => new Date(Date.parse(dateStr))}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(this.props.locale) : ''}
strings={strings}
/>
);

View File

@ -28,7 +28,6 @@ export interface IFormFieldProps {
valueChanged(newValue: any): void;
}
const FormField: React.SFC<IFormFieldProps> = (props) => {
const {
@ -49,31 +48,31 @@ const FormField: React.SFC<IFormFieldProps> = (props) => {
const isDescriptionAvailable = Boolean(props.description || props.errorMessage);
return (
<div className={ css(formFieldClassName, 'od-ClientFormFields-field') }>
<div className={ css('ard-FormField-wrapper', styles.wrapper) }>
{ label && <Label className={ css(ardStyles.label, {['is-required']: required}) } htmlFor={ this._id }>{ label }</Label> }
<div className={ css('ard-FormField-fieldGroup', ardStyles.controlContainerDisplay, active
&& styles.fieldGroupIsFocused, errorMessage && styles.invalid) }>
{children}
</div>
<div className={css(formFieldClassName, 'od-ClientFormFields-field')}>
<div className={css('ard-FormField-wrapper', styles.wrapper)}>
{label && <Label className={css(ardStyles.label, { ['is-required']: required })} htmlFor={this._id}>{label}</Label>}
<div className={css('ard-FormField-fieldGroup', ardStyles.controlContainerDisplay, active
&& styles.fieldGroupIsFocused, errorMessage && styles.invalid)}>
{children}
</div>
{ isDescriptionAvailable &&
<span>
{ description && <span className={ css('ard-FormField-description', styles.description) }>{ description }</span> }
{ errorMessage &&
<div aria-live='assertive'>
<DelayedRender>
<p className={ css('ard-FormField-errorMessage', AnimationClassNames.slideDownIn20, styles.errorMessage) }>
{ Icon({ iconName: 'Error', className: styles.errorIcon }) }
<span className={ styles.errorText } data-automation-id='error-message'>{ errorMessage }</span>
</p>
</DelayedRender>
</div>
}
</span>
}
</div>
);
{isDescriptionAvailable &&
<span>
{description && <span className={css('ard-FormField-description', styles.description)}>{description}</span>}
{errorMessage &&
<div aria-live='assertive'>
<DelayedRender>
<p className={css('ard-FormField-errorMessage', AnimationClassNames.slideDownIn20, styles.errorMessage)}>
{Icon({ iconName: 'Error', className: styles.errorIcon })}
<span className={styles.errorText} data-automation-id='error-message'>{errorMessage}</span>
</p>
</DelayedRender>
</div>
}
</span>
}
</div>
);
};
export default FormField;

View File

@ -25,10 +25,10 @@ export default class NumberFormField extends React.Component<INumberFormFieldPro
<TextField
{...this.props}
className='NumberFormField'
label={ this.props.label }
label={this.props.label}
value={value}
onChanged={ this.props.valueChanged }
onGetErrorMessage={ this._validateNumber }
onChanged={this.props.valueChanged}
onGetErrorMessage={this._validateNumber}
/>
);
}
@ -40,13 +40,12 @@ export default class NumberFormField extends React.Component<INumberFormFieldPro
}
private parseNumber(value, locale = navigator.language) {
const decimalSperator = Intl.NumberFormat(locale).format(1.1).charAt( 1 );
const decimalSperator = Intl.NumberFormat(locale).format(1.1).charAt(1);
// const cleanPattern = new RegExp(`[^-+0-9${ example.charAt( 1 ) }]`, 'g');
const cleanPattern = new RegExp(`[${ '\' ,.'.replace(decimalSperator, '') }]`, 'g');
const cleanPattern = new RegExp(`[${'\' ,.'.replace(decimalSperator, '')}]`, 'g');
const cleaned = value.replace(cleanPattern, '');
const normalized = cleaned.replace(decimalSperator, '.');
return Number(normalized);
}
}

View File

@ -5,14 +5,14 @@ import * as strings from 'FormFieldStrings';
const SPFieldBooleanEdit: React.SFC<ISPFormFieldProps> = (props) => {
return <Toggle
className='ard-booleanFormField'
checked={props.value === '1' || props.value === 'true' || props.value === 'Yes'}
onAriaLabel={strings.ToggleOnAriaLabel}
offAriaLabel={strings.ToggleOffAriaLabel}
onText={strings.ToggleOnText}
offText={strings.ToggleOffText}
onChanged={ (checked: boolean) => props.valueChanged(checked.toString())}
/>;
className='ard-booleanFormField'
checked={props.value === '1' || props.value === 'true' || props.value === 'Yes'}
onAriaLabel={strings.ToggleOnAriaLabel}
offAriaLabel={strings.ToggleOffAriaLabel}
onText={strings.ToggleOnText}
offText={strings.ToggleOffText}
onChanged={(checked: boolean) => props.valueChanged(checked.toString())}
/>;
};
export default SPFieldBooleanEdit;

View File

@ -9,26 +9,25 @@ const SPFieldChoiceEdit: React.SFC<ISPFormFieldProps> = (props) => {
if (props.fieldSchema.FieldType !== 'MultiChoice') {
const options = (props.fieldSchema.Required) ? props.fieldSchema.Choices : [''].concat(props.fieldSchema.Choices);
return <Dropdown
className={css(styles.dropDownFormField, 'ard-choiceFormField')}
options = {options.map( (option: string) => ({key: option, text: option}) )}
selectedKey = {props.value}
onChanged={ (item) => props.valueChanged( item.key.toString() ) }
/>;
className={css(styles.dropDownFormField, 'ard-choiceFormField')}
options={options.map((option: string) => ({ key: option, text: option }))}
selectedKey={props.value}
onChanged={(item) => props.valueChanged(item.key.toString())}
/>;
} else {
const options = props.fieldSchema.MultiChoices;
const values = props.value ? props.value.split(';#').filter((s) => s) : [];
return <Dropdown
title = {JSON.stringify(props.fieldSchema) + props.value}
className={css(styles.dropDownFormField, 'ard-multiChoiceFormField')}
options = {options.map( (option: string) => ({key: option, text: option}) )}
selectedKeys = {values}
multiSelect
onChanged={ (item) => props.valueChanged( getUpdatedValue(values, item) ) }
/>;
title={JSON.stringify(props.fieldSchema) + props.value}
className={css(styles.dropDownFormField, 'ard-multiChoiceFormField')}
options={options.map((option: string) => ({ key: option, text: option }))}
selectedKeys={values}
multiSelect
onChanged={(item) => props.valueChanged(getUpdatedValue(values, item))}
/>;
}
};
function getUpdatedValue(oldValues: string[], changedItem: IDropdownOption): string {
const changedKey = changedItem.key.toString();
const newValues = [...oldValues];
@ -43,5 +42,4 @@ function getUpdatedValue(oldValues: string[], changedItem: IDropdownOption): str
return newValues.join(';#');
}
export default SPFieldChoiceEdit;

View File

@ -8,20 +8,19 @@ import DateFormField from './DateFormField';
import * as strings from 'FormFieldStrings';
import styles from './SPFormField.module.scss';
const SPFieldDateEdit: React.SFC<ISPFormFieldProps> = (props) => {
const locale = Locales[props.fieldSchema.LocaleId];
return <DateFormField
{...props.value && moment(props.value).isValid() ? {value: moment(props.value).toDate()} : {}}
className={css(styles.dateFormField, 'ard-dateFormField')}
placeholder={strings.DateFormFieldPlaceholder}
isRequired={props.fieldSchema.Required}
ariaLabel={props.fieldSchema.Title}
locale={Locales[locale]}
firstDayOfWeek={props.fieldSchema.FirstDayOfWeek}
allowTextInput
onSelectDate={(date) => props.valueChanged(date.toLocaleDateString(locale))}
/>;
const locale = Locales[props.fieldSchema.LocaleId];
return <DateFormField
{...props.value && moment(props.value).isValid() ? { value: moment(props.value).toDate() } : {}}
className={css(styles.dateFormField, 'ard-dateFormField')}
placeholder={strings.DateFormFieldPlaceholder}
isRequired={props.fieldSchema.Required}
ariaLabel={props.fieldSchema.Title}
locale={Locales[locale]}
firstDayOfWeek={props.fieldSchema.FirstDayOfWeek}
allowTextInput
onSelectDate={(date) => props.valueChanged(date.toLocaleDateString(locale))}
/>;
};
export default SPFieldDateEdit;

View File

@ -3,11 +3,11 @@ import { ISPFormFieldProps } from './SPFormField';
import { Link } from 'office-ui-fabric-react/lib/Link';
const SPFieldLookupDisplay: React.SFC<ISPFormFieldProps> = (props) => {
if ((props.value) && (props.value.length > 0)) {
if ((props.value) && (props.value.length > 0)) {
const baseUrl = `${props.fieldSchema.BaseDisplayFormUrl}&ListId={${props.fieldSchema.LookupListId}}`;
return <div>
{props.value.map( (val) => <div><Link href={`{baseUrl}&ID=${val.lookupId}`}>{val.lookupValue}</Link></div> )}
</div>;
return <div>
{props.value.map((val) => <div><Link href={`{baseUrl}&ID=${val.lookupId}`}>{val.lookupValue}</Link></div>)}
</div>;
} else {
return <div></div>;
}

View File

@ -7,42 +7,42 @@ import * as strings from 'FormFieldStrings';
import styles from './SPFormField.module.scss';
const SPFieldLookupEdit: React.SFC<ISPFormFieldProps> = (props) => {
let options = props.fieldSchema.Choices.map( (option) => ({ key: option.LookupId, text: option.LookupValue }) );
let options = props.fieldSchema.Choices.map((option) => ({ key: option.LookupId, text: option.LookupValue }));
if (props.fieldSchema.FieldType !== 'LookupMulti') {
if (!props.required) { options = [{key: 0, text: strings.LookupEmptyOptionText}].concat(options); }
if (!props.required) { options = [{ key: 0, text: strings.LookupEmptyOptionText }].concat(options); }
const value = props.value ? Number(props.value.split(';#')[0]) : 0;
return <Dropdown
className={css(styles.dropDownFormField, 'ard-lookupFormField')}
options={options}
selectedKey={value}
onChanged={ (item) => props.valueChanged( `${item.key};#${item.text}` ) }
/>;
className={css(styles.dropDownFormField, 'ard-lookupFormField')}
options={options}
selectedKey={value}
onChanged={(item) => props.valueChanged(`${item.key};#${item.text}`)}
/>;
} else {
let values = [];
if (props.value) {
const splitArray = props.value.split(';#');
values = splitArray.filter( (item, idx) => (idx % 2 === 0) )
.map( (comp, idx) => ({key: Number(comp), text: (splitArray.length > idx + 1) ? splitArray[idx + 1] : '' }) );
values = splitArray.filter((item, idx) => (idx % 2 === 0))
.map((comp, idx) => ({ key: Number(comp), text: (splitArray.length > idx + 1) ? splitArray[idx + 1] : '' }));
}
return <Dropdown
className={css(styles.dropDownFormField, 'ard-lookupMultiFormField')}
options={options}
selectedKeys={values.map((val) => val.key)}
multiSelect
onChanged={ (item) => props.valueChanged( getUpdatedValue(values, item) ) }
/>;
className={css(styles.dropDownFormField, 'ard-lookupMultiFormField')}
options={options}
selectedKeys={values.map((val) => val.key)}
multiSelect
onChanged={(item) => props.valueChanged(getUpdatedValue(values, item))}
/>;
}
};
function getUpdatedValue(oldValues: Array<{key: number, text: string}>, changedItem: IDropdownOption): string {
let newValues: Array<{key: number, text: string}>;
function getUpdatedValue(oldValues: Array<{ key: number, text: string }>, changedItem: IDropdownOption): string {
let newValues: Array<{ key: number, text: string }>;
if (changedItem.selected) {
newValues = [...oldValues, {key: Number(changedItem.key), text: changedItem.text}];
newValues = [...oldValues, { key: Number(changedItem.key), text: changedItem.text }];
} else {
newValues = oldValues.filter( (item) => item.key !== changedItem.key );
newValues = oldValues.filter((item) => item.key !== changedItem.key);
}
return newValues.reduce( (valStr, item) => valStr + `${item.key};#${item.text}`, '' );
return newValues.reduce((valStr, item) => valStr + `${item.key};#${item.text}`, '');
}
export default SPFieldLookupEdit;

View File

@ -0,0 +1,9 @@
import * as React from 'react';
import { ISPFormFieldProps } from './SPFormField';
import ReactHtmlParser from 'react-html-parser';
const SPFieldRichTextDisplay: React.SFC<ISPFormFieldProps> = (props) => {
return <div className='ard-textfield-display'>{ReactHtmlParser(props.value)}</div>;
};
export default SPFieldRichTextDisplay;

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import { ISPFormFieldProps } from './SPFormField';
import * as tinymce from 'tinymce';
import 'tinymce/themes/silver';
import 'tinymce/plugins/paste';
import 'tinymce/plugins/link';
import 'tinymce/plugins/image';
import 'tinymce/plugins/imagetools';
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/print';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/table';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/media';
import 'tinymce/plugins/imagetools';
import { Editor } from "@tinymce/tinymce-react";
const SPFieldRichTextEdit: React.SFC<ISPFormFieldProps> = (props) => {
tinymce.init({});
const { Name, RichTextMode } = props.fieldSchema;
const value = props.value ? props.value : '';
if (tinymce.editors[`Editor-${Name}`] !== undefined) {
tinymce.editors[`Editor-${Name}`].setContent(value);
}
const editorConfig = {
"relative_urls": false, "convert_urls": false, "remove_script_host": false,
height: 300,
plugins: [
"paste advlist autolink lists link print preview anchor",
RichTextMode === 1 ? 'image media table paste imagetools' : 'fullscreen'
],
skin_url: "https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.0.1/skins/ui/oxide"
};
return <Editor
id={`Editor-${Name}`}
init={editorConfig}
initialValue={props.value}
onChange={(event) => { props.valueChanged(event.target.getContent()); }}
/>;
};
export default SPFieldRichTextEdit;

View File

@ -7,15 +7,15 @@ const SPFieldTextEdit: React.SFC<ISPFormFieldProps> = (props) => {
// We need to set value to empty string when null or undefined to force TextField still be used like a controlled component
const value = props.value ? props.value : '';
return <TextField
className='ard-TextFormField'
name={props.fieldSchema.InternalName}
value={value}
onChanged={props.valueChanged}
placeholder={strings.TextFormFieldPlaceholder}
multiline={props.fieldSchema.FieldType === 'Note'}
underlined
noValidate
/>;
className='ard-TextFormField'
name={props.fieldSchema.InternalName}
value={value}
onChanged={props.valueChanged}
placeholder={strings.TextFormFieldPlaceholder}
multiline={props.fieldSchema.FieldType === 'Note'}
underlined
noValidate
/>;
};
export default SPFieldTextEdit;

View File

@ -3,7 +3,7 @@ import { ISPFormFieldProps } from './SPFormField';
import { Link } from 'office-ui-fabric-react/lib/Link';
const SPFieldUrlDisplay: React.SFC<ISPFormFieldProps> = (props) => {
if (props.value) {
if (props.value) {
if (props.fieldSchema.DisplayFormat === 1) { // picture field
return <div><img src={props.value} title={(props.extraData) ? props.extraData.desc : ''}></img></div>;
} else {

View File

@ -3,9 +3,9 @@ import { ISPFormFieldProps } from './SPFormField';
import { Link } from 'office-ui-fabric-react/lib/Link';
const SPFieldUserDisplay: React.SFC<ISPFormFieldProps> = (props) => {
if ((props.value) && (props.value.length > 0)) {
if ((props.value) && (props.value.length > 0)) {
const baseUrl = `${props.fieldSchema.ListFormUrl}?PageType=4&ListId=${props.fieldSchema.UserInfoListId}`;
return <div>{props.value.map( (val) => <div><Link href={`{baseUrl}&ID=${val.id}`}>{val.title}</Link></div> )}</div>;
return <div>{props.value.map((val) => <div><Link href={`{baseUrl}&ID=${val.id}`}>{val.title}</Link></div>)}</div>;
} else {
return <div></div>;
}

View File

@ -9,12 +9,14 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
import SPFieldTextEdit from './SPFieldTextEdit';
import SPFieldRichTextEdit from './SPFieldRichTextEdit';
import SPFieldLookupEdit from './SPFieldLookupEdit';
import SPFieldChoiceEdit from './SPFieldChoiceEdit';
import SPFieldNumberEdit from './SPFieldNumberEdit';
import SPFieldDateEdit from './SPFieldDateEdit';
import SPFieldBooleanEdit from './SPFieldBooleanEdit';
import SPFieldTextDisplay from './SPFieldTextDisplay';
import SPFieldRichTextDisplay from './SPFieldRichTextDisplay';
import SPFieldLookupDisplay from './SPFieldLookupDisplay';
import SPFieldUserDisplay from './SPFieldUserDisplay';
import SPFieldUrlDisplay from './SPFieldUrlDisplay';
@ -22,9 +24,9 @@ import SPFieldUrlDisplay from './SPFieldUrlDisplay';
import * as strings from 'FormFieldStrings';
import styles from './SPFormField.module.scss';
const EditFieldTypeMappings: {[fieldType: string]: React.StatelessComponent<ISPFormFieldProps>} = {
const EditFieldTypeMappings: { [fieldType: string]: React.StatelessComponent<ISPFormFieldProps> } = {
Text: SPFieldTextEdit,
RichText: SPFieldRichTextEdit,
Note: SPFieldTextEdit,
Lookup: SPFieldLookupEdit,
LookupMulti: SPFieldLookupEdit,
@ -45,15 +47,19 @@ const EditFieldTypeMappings: {[fieldType: string]: React.StatelessComponent<ISPF
*/
};
const DisplayFieldTypeMappings: {[fieldType: string]: {component: React.StatelessComponent<ISPFormFieldProps>,
valuePreProcess?: (value: any) => any}} = {
const DisplayFieldTypeMappings: {
[fieldType: string]: {
component: React.StatelessComponent<ISPFormFieldProps>,
valuePreProcess?: (value: any) => any
},
} = {
Text: { component: SPFieldTextDisplay },
RichText: { component: SPFieldRichTextDisplay },
Note: { component: SPFieldTextDisplay },
Lookup: { component: SPFieldLookupDisplay },
LookupMulti: { component: SPFieldLookupDisplay },
Choice: { component: SPFieldTextDisplay },
MultiChoice: {component: SPFieldTextDisplay, valuePreProcess: (val) => val ? val.join(', ') : '' },
MultiChoice: { component: SPFieldTextDisplay, valuePreProcess: (val) => val ? val.join(', ') : '' },
Number: { component: SPFieldTextDisplay },
Currency: { component: SPFieldTextDisplay },
DateTime: { component: SPFieldTextDisplay },
@ -61,64 +67,63 @@ const DisplayFieldTypeMappings: {[fieldType: string]: {component: React.Stateles
User: { component: SPFieldUserDisplay },
UserMulti: { component: SPFieldUserDisplay },
URL: { component: SPFieldUrlDisplay },
File: { component: SPFieldTextDisplay},
File: { component: SPFieldTextDisplay },
TaxonomyFieldType: { component: SPFieldTextDisplay, valuePreProcess: (val) => val ? val.Label : '' },
TaxonomyFieldTypeMulti: { component: SPFieldTextDisplay, valuePreProcess: (val) => val ? val.map( (v) => v.Label ).join(', ') : '' },
TaxonomyFieldTypeMulti: { component: SPFieldTextDisplay, valuePreProcess: (val) => val ? val.map((v) => v.Label).join(', ') : '' },
/* The following are known but unsupported types as of now:
Attachments: null,
*/
};
export interface ISPFormFieldProps extends IFormFieldProps {
extraData?: any;
fieldSchema: IFieldSchema;
hideIfFieldUnsupported?: boolean;
extraData?: any;
fieldSchema: IFieldSchema;
hideIfFieldUnsupported?: boolean;
}
const SPFormField: React.SFC<ISPFormFieldProps> = (props) => {
let fieldControl = null;
const fieldType = props.fieldSchema.FieldType;
const richText = props.fieldSchema.RichText;
if (props.controlMode === ControlMode.Display) {
if (DisplayFieldTypeMappings.hasOwnProperty(fieldType)) {
const fieldMapping = DisplayFieldTypeMappings[fieldType];
const childProps = fieldMapping.valuePreProcess ? {...props, value: fieldMapping.valuePreProcess(props.value)} : props;
fieldControl = React.createElement( fieldMapping.component, childProps );
const fieldMapping = richText ? DisplayFieldTypeMappings['RichText'] : DisplayFieldTypeMappings[fieldType];
const childProps = fieldMapping.valuePreProcess ? { ...props, value: fieldMapping.valuePreProcess(props.value) } : props;
fieldControl = React.createElement(fieldMapping.component, childProps);
} else if (!props.hideIfFieldUnsupported) {
const value = (props.value) ? ((typeof props.value === 'string') ? props.value : JSON.stringify(props.value)) : '';
fieldControl = <div className={`ard-${fieldType}field-display`}>
<span>{value}</span>
<div className={styles.unsupportedFieldMessage}><Icon iconName='Error' />{`${strings.UnsupportedFieldType} "${fieldType}"`}</div>
</div>;
<span>{value}</span>
<div className={styles.unsupportedFieldMessage}><Icon iconName='Error' />{`${strings.UnsupportedFieldType} "${fieldType}"`}</div>
</div>;
}
} else {
if (EditFieldTypeMappings.hasOwnProperty(fieldType)) {
fieldControl = React.createElement( EditFieldTypeMappings[fieldType], props );
fieldControl = richText ? React.createElement(EditFieldTypeMappings['RichText'], props) : React.createElement(EditFieldTypeMappings[fieldType], props);
} else if (!props.hideIfFieldUnsupported) {
const isObjValue = (props.value) && (typeof props.value !== 'string');
const value = (props.value) ? ((typeof props.value === 'string') ? props.value : JSON.stringify(props.value)) : '';
fieldControl = <TextField
readOnly
multiline={isObjValue}
value={value}
errorMessage={`${strings.UnsupportedFieldType} "${fieldType}"`}
underlined
/>;
readOnly
multiline={isObjValue}
value={value}
errorMessage={`${strings.UnsupportedFieldType} "${fieldType}"`}
underlined
/>;
}
}
return (fieldControl)
? <FormField
{...props}
label={props.label || props.fieldSchema.Title}
description={props.description || props.fieldSchema.Description}
required={props.fieldSchema.Required}
errorMessage={props.errorMessage}
>
{fieldControl}
</FormField>
{...props}
label={props.label || props.fieldSchema.Title}
description={props.description || props.fieldSchema.Description}
required={props.fieldSchema.Required}
errorMessage={props.errorMessage}
>
{fieldControl}
</FormField>
: null;
};
export default SPFormField;

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
UnsupportedFieldType: 'Unsupported field type',
InvalidNumberValue: 'The value should be a number, actual is',
@ -12,10 +12,10 @@ define([], function() {
LookupEmptyOptionText: '(None)',
// IDatePickerStrings
months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ],
shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ],
days: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
shortDays: [ 'S', 'M', 'T', 'W', 'T', 'F', 'S' ],
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
goToToday: 'Go to today',
prevMonthAriaLabel: 'Go to previous month',
nextMonthAriaLabel: 'Go to next month',

View File

@ -1,31 +1,30 @@
declare interface IFormFieldStrings {
UnsupportedFieldType: string;
InvalidNumberValue: string;
ToggleOnAriaLabel: string;
ToggleOffAriaLabel: string;
ToggleOnText: string;
ToggleOffText: string;
TextFormFieldPlaceholder: string;
DateFormFieldPlaceholder: string;
NumberFormFieldPlaceholder: string;
LookupEmptyOptionText: string;
UnsupportedFieldType: string;
InvalidNumberValue: string;
ToggleOnAriaLabel: string;
ToggleOffAriaLabel: string;
ToggleOnText: string;
ToggleOffText: string;
TextFormFieldPlaceholder: string;
DateFormFieldPlaceholder: string;
NumberFormFieldPlaceholder: string;
LookupEmptyOptionText: string;
// IDatePickerStrings
months: string[];
shortMonths: string[];
days: string[];
shortDays: string[];
goToToday: string;
isRequiredErrorMessage?: string;
invalidInputErrorMessage?: string;
prevMonthAriaLabel?: string;
nextMonthAriaLabel?: string;
prevYearAriaLabel?: string;
nextYearAriaLabel?: string;
}
declare module 'FormFieldStrings' {
const strings: IFormFieldStrings;
export = strings;
}
// IDatePickerStrings
months: string[];
shortMonths: string[];
days: string[];
shortDays: string[];
goToToday: string;
isRequiredErrorMessage?: string;
invalidInputErrorMessage?: string;
prevMonthAriaLabel?: string;
nextMonthAriaLabel?: string;
prevYearAriaLabel?: string;
nextYearAriaLabel?: string;
}
declare module 'FormFieldStrings' {
const strings: IFormFieldStrings;
export = strings;
}

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
UnsupportedFieldType: 'Type de champ non pris en charge',
InvalidNumberValue: 'La valeur doit être un nombre, la valeur actuel est',
@ -12,10 +12,10 @@ define([], function() {
LookupEmptyOptionText: '(Aucun)',
// IDatePickerStrings
months: [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ],
shortMonths: [ 'Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jui', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc' ],
days: [ 'Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi' ],
shortDays: [ 'D', 'L', 'M', 'M', 'J', 'V', 'S' ],
months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
shortMonths: ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jui', 'Jul', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'],
days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
shortDays: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
goToToday: 'Aller à aujourd\'hui',
prevMonthAriaLabel: 'Aller au mois précédent',
nextMonthAriaLabel: 'Aller au mois suivant',

View File

@ -0,0 +1,28 @@
define([], function () {
return {
UnsupportedFieldType: 'Desteklenmeyen alan türü',
InvalidNumberValue: 'Değer bir sayı olmalıdır',
ToggleOnAriaLabel: 'Seçili. Seçimi kaldırmak için düğmeye basın.',
ToggleOffAriaLabel: 'Seçili değil. Seçimi kaldırmak için düğmeye basın.',
ToggleOnText: 'Evet',
ToggleOffText: 'Hayıt',
TextFormFieldPlaceholder: 'Yazıyı buraya girin',
DateFormFieldPlaceholder: 'Bir tarih girin',
NumberFormFieldPlaceholder: 'Buraya değeri girin',
LookupEmptyOptionText: '(Yok)',
// IDatePickerStrings
months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
shortMonths: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi'],
shortDays: ['Pz', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'],
goToToday: 'Bugüne git',
prevMonthAriaLabel: 'Önceki aya git',
nextMonthAriaLabel: 'Gelecek aya git',
prevYearAriaLabel: 'Önceki yıla git',
nextYearAriaLabel: 'Gelecek yıla git',
isRequiredErrorMessage: 'Bu tarih zorunlu.',
invalidInputErrorMessage: 'Geçersiz tarih biçimi.'
}
});

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
'SaveButtonText': 'Save',
'CancelButtonText': 'Cancel',
@ -11,7 +11,7 @@ define([], function() {
'ItemSavedSuccessfully': 'Item saved successfully.',
'FieldsErrorOnSaving': 'The item could not be saved. Please check detailed error messages on the fields below.',
'ErrorOnSavingListItem': 'Error on loading lists: ',
'MoveField' : "Move field",
'RemoveField' : "Remove field"
'MoveField': "Move field",
'RemoveField': "Remove field"
}
});

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
'SaveButtonText': 'Sauvegarder',
'CancelButtonText': 'Annuler',
@ -11,7 +11,7 @@ define([], function() {
'ItemSavedSuccessfully': 'Item enregistré avec succès.',
'FieldsErrorOnSaving': 'L\'item n\'a pas pu être enregistré. Veuillez vérifier les messages d\'erreur détaillés dans les champs ci-dessous.',
'ErrorOnSavingListItem': 'Erreur de chargement des listes : ',
'MoveField' : "Déplacer le champ",
'RemoveField' : "Supprimer le champ"
'MoveField': "Déplacer le champ",
'RemoveField': "Supprimer le champ"
}
});

View File

@ -1,20 +1,20 @@
declare interface IListFormStrings {
SaveButtonText: string;
CancelButtonText: string;
AddNewFieldAction: string;
LoadingFormIndicator: string;
ErrorLoadingSchema: string;
ConfigureListMessage: string;
RequiredValueMessage: string;
ErrorLoadingData: string;
ItemSavedSuccessfully: string;
FieldsErrorOnSaving: string;
ErrorOnSavingListItem: string;
MoveField :string;
RemoveField : string;
}
SaveButtonText: string;
CancelButtonText: string;
AddNewFieldAction: string;
LoadingFormIndicator: string;
ErrorLoadingSchema: string;
ConfigureListMessage: string;
RequiredValueMessage: string;
ErrorLoadingData: string;
ItemSavedSuccessfully: string;
FieldsErrorOnSaving: string;
ErrorOnSavingListItem: string;
MoveField: string;
RemoveField: string;
}
declare module 'ListFormStrings' {
const strings: IListFormStrings;
export = strings;
}
declare module 'ListFormStrings' {
const strings: IListFormStrings;
export = strings;
}

View File

@ -0,0 +1,17 @@
define([], function () {
return {
'SaveButtonText': 'Kaydet',
'CancelButtonText': 'İptal',
'AddNewFieldAction': 'Forma yeni bir alan ekle',
'LoadingFormIndicator': 'Form yükleniyor...',
'ErrorLoadingSchema': 'Liste için şema yüklenirken hata oluştu',
'ConfigureListMessage': 'Lütfen önce web bölümünün editöründe bir liste yapılandırın.',
'RequiredValueMessage': 'Lütfen değer girin!',
'ErrorLoadingData': 'Kimliği olan öğe için veri yüklenirken hata oluştu ',
'ItemSavedSuccessfully': 'Öğe başarıyla kaydedildi.',
'FieldsErrorOnSaving': 'Öğe kaydedilemedi. Lütfen aşağıdaki alanlardaki ayrıntılı hata mesajlarını kontrol edin.',
'ErrorOnSavingListItem': 'Listeler yüklenirken hata oluştu: ',
'MoveField': "Alan taşı",
'RemoveField': "Alan sil"
}
});

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
'PropertyPaneDescription': 'Configure the list form here. Once the list is configured fields can be moved, inserted and removed in the webpart\'s content itself.',
'BasicGroupName': 'Settings',
@ -7,7 +7,7 @@ define([], function() {
'ListFieldLabel': 'List',
'FormTypeFieldLabel': 'Form Type',
'ItemIdFieldLabel': 'Item ID',
'ItemIdFieldDescription' : 'Enter either a number for the ID or the query string parameter name to use for the ID.',
'ItemIdFieldDescription': 'Enter either a number for the ID or the query string parameter name to use for the ID.',
'ShowUnsupportedFieldsLabel': 'Show unsupported fields',
'RedirectUrlFieldLabel': 'URL to redirect after saving (optional)',
'RedirectUrlFieldDescription': 'Can contain [ID] as a placeholder to be replaced by ID of updated or created item. Example: /list/Test/DispForm.aspx?ID=[ID]',
@ -15,5 +15,5 @@ define([], function() {
'MissingListConfiguration': 'Please configure a SharePoint list in the web part\'s properties.',
'ConfigureWebpartButtonText': 'Configure Web Part',
'ErrorOnLoadingLists': 'Error on loading lists: ',
}
}
});

View File

@ -1,4 +1,4 @@
define([], function() {
define([], function () {
return {
'PropertyPaneDescription': 'Configurez le formulaire de la liste ici. Une fois la liste configurée, les champs peuvent être déplacés, insérés et supprimés dans le contenu du webpart.',
'BasicGroupName': 'Paramètres',
@ -7,7 +7,7 @@ define([], function() {
'ListFieldLabel': 'Liste',
'FormTypeFieldLabel': 'Type de formulaire',
'ItemIdFieldLabel': 'ID de l\'item',
'ItemIdFieldDescription' : 'Entrez un nombre pour l\'ID ou le nom du paramètre de chaîne de requête à utiliser pour l\'ID.',
'ItemIdFieldDescription': 'Entrez un nombre pour l\'ID ou le nom du paramètre de chaîne de requête à utiliser pour l\'ID.',
'ShowUnsupportedFieldsLabel': 'Afficher les champs non pris en charge',
'RedirectUrlFieldLabel': 'URL de redirection après l\'enregistrement (facultatif)',
'RedirectUrlFieldDescription': 'Peut contenir [ID] comme placeholder remplacable par l\'ID de l\'élément mis à jour ou créé. Exemple: /list/Test/DispForm.aspx?ID=[ID]',
@ -15,5 +15,5 @@ define([], function() {
'MissingListConfiguration': 'Veuillez configurer une liste SharePoint dans les propriétés du WebPart.',
'ConfigureWebpartButtonText': 'Configurer le WebPart',
'ErrorOnLoadingLists': 'Erreur de chargement des listes : ',
}
}
});

View File

@ -0,0 +1,19 @@
define([], function () {
return {
'PropertyPaneDescription': 'Liste formunu burada yapılandırın. Liste yapılandırıldıktan sonra alanlar taşınabilir, web bölümünün içeriğine eklenebilir ve kaldırılabilir.',
'BasicGroupName': 'Ayarlar',
'TitleFieldLabel': 'Başlık',
'DescriptionFieldLabel': 'Açıklama',
'ListFieldLabel': 'Liste',
'FormTypeFieldLabel': 'Form Tipi',
'ItemIdFieldLabel': 'Öğe Kimliği',
'ItemIdFieldDescription': 'Kimlik için bir sayı veya kimlik için kullanılacak sorgu dizesi parametre adını girin.',
'ShowUnsupportedFieldsLabel': 'Desteklenmeyen alanları göster',
'RedirectUrlFieldLabel': 'Kaydettikten sonra yönlendirilecek URL (isteğe bağlı)',
'RedirectUrlFieldDescription': 'Güncellenmiş veya oluşturulan öğenin kimliği ile değiştirilecek bir yer tutucu olarak [ID] içerebilir. Örnek: /list/Test/DispForm.aspx?ID[ID]',
'LocalWorkbenchUnsupported': 'Bu web bölümünü local workbench üzerinden çalıştırılmayı desteklenmiyor. Lütfen SharePoint sitenizde çalıştırın.',
'MissingListConfiguration': 'Lütfen web bölümü özelliklerinde bir SharePoint listesi yapılandırın.',
'ConfigureWebpartButtonText': 'Web bölümünü ayarlar',
'ErrorOnLoadingLists': 'Listeler yüklenirken hata oluştu: ',
}
});

8
samples/react-list-form/tsconfig.json Normal file → Executable file
View File

@ -1,15 +1,15 @@
{
"compilerOptions": {
"target": "es5",
"outDir": "lib",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
@ -31,4 +31,4 @@
"node_modules",
"lib"
]
}
}

41
samples/react-list-form/tslint.json Normal file → Executable file
View File

@ -1,15 +1,30 @@
{
"enable": false,
"extends": "tslint:recommended",
"rules": {
"quotemark": [true, "single"],
"object-literal-sort-keys": [false],
"max-line-length": [true, 140],
"trailing-comma": [false],
"no-consecutive-blank-lines": [false],
"ordered-imports": [false],
"object-literal-shorthand": [false],
"member-ordering": [false]
},
"defaultSeverity": "warning"
"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
}
}