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:
parent
060dd3438a
commit
97efa6e8cf
|
@ -71,5 +71,6 @@
|
|||
],
|
||||
"url": "./node_modules/@microsoft/sp-build-core-tasks/lib/copyStaticAssets/copy-static-assets.schema.json"
|
||||
}
|
||||
]
|
||||
],
|
||||
"editor.formatOnSave": true
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.**
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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",
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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, '');
|
||||
|
|
|
@ -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); });
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.",
|
||||
}
|
||||
});
|
|
@ -7,4 +7,4 @@ declare interface IServicesStrings {
|
|||
declare module 'servicesStrings' {
|
||||
const strings: IServicesStrings;
|
||||
export = strings;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
}
|
||||
});
|
|
@ -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 { }
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
|||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
75
samples/react-list-form/src/webparts/listForm/components/formFields/SPFormField.tsx
Normal file → Executable file
75
samples/react-list-form/src/webparts/listForm/components/formFields/SPFormField.tsx
Normal file → Executable 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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
28
samples/react-list-form/src/webparts/listForm/components/formFields/loc/tr-tr.js
vendored
Executable file
28
samples/react-list-form/src/webparts/listForm/components/formFields/loc/tr-tr.js
vendored
Executable 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.'
|
||||
|
||||
}
|
||||
});
|
|
@ -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"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
});
|
|
@ -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: ',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 : ',
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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: ',
|
||||
}
|
||||
});
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue