Attachment for react list form (#1167)

* #1 Add CSS classes for correct layout from Office UI Fabric

* #1 - Removed intermediary component, which was only sending data

* #1 - Add all the component methods, carried out some simple tests

* #01 - Remove CSS styles from the FormField. Only date controls have those

* #01 - Fix some minor issues

* Add a lot about attachments

* Fixed tha files with the same name cannot be added

* Added attachments functionality

* Fixed the bug with multiple attachments delete

* Removed console.log lines

Co-authored-by: SharePoint Student <student@CONTOSOLTD.LOCAL>
This commit is contained in:
Konstantin 2020-03-09 19:29:39 +01:00 committed by GitHub
parent d159945fd5
commit c837006bf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 19621 additions and 19158 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,43 +1,44 @@
{ {
"name": "react-form-webpart", "name": "react-form-webpart",
"version": "1.0.2", "version": "1.0.2",
"private": true, "private": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
}, },
"main": "lib/index.js", "main": "lib/index.js",
"scripts": { "scripts": {
"serve": "gulp serve", "serve": "gulp serve",
"build": "gulp bundle", "build": "gulp bundle",
"clean": "gulp clean", "clean": "gulp clean",
"test": "gulp test" "test": "gulp test"
}, },
"dependencies": { "dependencies": {
"@microsoft/sp-core-library": "1.9.1", "@microsoft/sp-core-library": "1.9.1",
"@microsoft/sp-lodash-subset": "1.9.1", "@microsoft/sp-lodash-subset": "1.9.1",
"@microsoft/sp-office-ui-fabric-core": "1.9.1", "@microsoft/sp-office-ui-fabric-core": "1.9.1",
"@microsoft/sp-webpart-base": "1.9.1", "@microsoft/sp-webpart-base": "1.9.1",
"@tinymce/tinymce-react": "^3.0.1", "@tinymce/tinymce-react": "^3.0.1",
"@types/es6-promise": "0.0.33", "@types/es6-promise": "0.0.33",
"@types/react-dnd": "~2.0.34", "@types/react-dnd": "~2.0.34",
"@types/webpack-env": "1.13.1", "@types/webpack-env": "1.13.1",
"moment": "^2.24.0", "@uifabric/icons": "^7.3.14",
"react-dnd": "~2.5.4", "moment": "^2.24.0",
"react-dnd-html5-backend": "~2.5.4", "react-dnd": "~2.5.4",
"react-html-parser": "^2.0.2", "react-dnd-html5-backend": "~2.5.4",
"spfx-uifabric-themes": "~0.1.3", "react-html-parser": "^2.0.2",
"tinymce": "^5.0.1" "spfx-uifabric-themes": "~0.1.3",
}, "tinymce": "^5.0.1"
"devDependencies": { },
"@microsoft/rush-stack-compiler-2.9": "^0.8.5", "devDependencies": {
"@microsoft/sp-build-web": "1.9.1", "@microsoft/rush-stack-compiler-2.9": "^0.8.5",
"@microsoft/sp-module-interfaces": "1.9.1", "@microsoft/sp-build-web": "1.9.1",
"@microsoft/sp-tslint-rules": "1.9.1", "@microsoft/sp-module-interfaces": "1.9.1",
"@microsoft/sp-webpart-workbench": "1.9.1", "@microsoft/sp-tslint-rules": "1.9.1",
"@types/chai": "3.4.34", "@microsoft/sp-webpart-workbench": "1.9.1",
"@types/mocha": "2.2.38", "@types/chai": "3.4.34",
"ajv": "~5.2.2", "@types/mocha": "2.2.38",
"gulp": "~3.9.1", "ajv": "~5.2.2",
"tslint-microsoft-contrib": "5.0.0" "gulp": "~3.9.1",
} "tslint-microsoft-contrib": "5.0.0"
} }
}

View File

@ -4,6 +4,8 @@ import * as strings from 'servicesStrings';
import { ControlMode } from '../datatypes/ControlMode'; import { ControlMode } from '../datatypes/ControlMode';
import { IFieldSchema, RenderListDataOptions } from './datatypes/RenderListData'; import { IFieldSchema, RenderListDataOptions } from './datatypes/RenderListData';
import { IListFormService } from './IListFormService'; import { IListFormService } from './IListFormService';
import { IAttachment } from '../../types/IAttachment';
export class ListFormService implements IListFormService { export class ListFormService implements IListFormService {
@ -118,41 +120,45 @@ export class ListFormService implements IListFormService {
* @param originalData An object containing all the field values retrieved on loading from list item. * @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. * @returns Promise object represents the updated or erroneous form field values.
*/ */
public updateItem(webUrl: string, listUrl: string, itemId: number, public updateItem = async (webUrl: string, listUrl: string, itemId: number, fieldsSchema: IFieldSchema[], data: any, originalData: any) => {
fieldsSchema: IFieldSchema[], data: any, originalData: any): Promise<any> { const httpClientOptions: ISPHttpClientOptions = {
return new Promise<any>((resolve, reject) => { headers: {
const httpClientOptions: ISPHttpClientOptions = { 'Accept': 'application/json;odata=verbose',
headers: { 'Content-type': 'application/json;odata=verbose',
'Accept': 'application/json;odata=verbose', 'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
'Content-type': 'application/json;odata=verbose', 'odata-version': '',
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl), },
'odata-version': '', };
}, const formValues = this.GetFormValues(fieldsSchema, data, originalData);
}; let createAttachmetns = this.GetAttachmentsCreate(data);
const formValues = this.GetFormValues(fieldsSchema, data, originalData); let deleteAttachmetns = this.GetAttachmentsDelete(data, originalData);
httpClientOptions.body = JSON.stringify({
httpClientOptions.body = JSON.stringify({ bNewDocumentUpdate: false,
bNewDocumentUpdate: false, checkInComment: null,
checkInComment: null, formValues,
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)
.then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
reject(this.getErrorMessage(webUrl, response));
}
})
.then((respData) => {
resolve(respData.d.ValidateUpdateListItem.results);
})
.catch((error) => {
reject(this.getErrorMessage(webUrl, error));
});
}); });
const endpoint = `${webUrl}/_api/web/GetList(@listUrl)/items(@itemId)/ValidateUpdateListItem()`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}&@itemId=%27${itemId}%27`;
try {
let response = await this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions);
if (!response.ok) {
return this.getErrorMessage(webUrl, response);
}
let responseData = await response.json();
responseData.AttachmentResponse = [];
if (deleteAttachmetns.length > 0) {
let deleteResponse = await this.deleteAttachments(webUrl, listUrl, itemId, deleteAttachmetns);
responseData.AttachmentResponse.push(deleteResponse);
}
if (createAttachmetns.length > 0) {
let createResponse = await this.uploadAttachments(webUrl, listUrl, itemId, createAttachmetns);
responseData.AttachmentResponse.push(createResponse);
}
return responseData.d.ValidateUpdateListItem.results;
} catch (error) {
return this.getErrorMessage(webUrl, error);
}
} }
/** /**
@ -167,6 +173,7 @@ export class ListFormService implements IListFormService {
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) => { return new Promise<any>((resolve, reject) => {
const formValues = this.GetFormValues(fieldsSchema, data, {}); const formValues = this.GetFormValues(fieldsSchema, data, {});
const formAttachmetns = this.GetAttachmentsCreate(data);
const httpClientOptions: ISPHttpClientOptions = { const httpClientOptions: ISPHttpClientOptions = {
headers: { headers: {
'Accept': 'application/json;odata=verbose', 'Accept': 'application/json;odata=verbose',
@ -198,6 +205,20 @@ export class ListFormService implements IListFormService {
} }
}) })
.then((respData) => { .then((respData) => {
let itemId = respData.d.AddValidateUpdateItemUsingPath.results.find(item => {
return item.FieldName == "Id";
}).FieldValue;
//if there are attachments, we upload all of them.
if (formAttachmetns.length > 0) {
this.uploadAttachments(webUrl, listUrl, itemId, formAttachmetns)
.then((attachmentResponse) => {
respData.AttachmentResponse = attachmentResponse;
resolve(respData.d.AddValidateUpdateItemUsingPath.results);
})
.catch((error) => {
reject(this.getErrorMessage(webUrl, error));
});
}
resolve(respData.d.AddValidateUpdateItemUsingPath.results); resolve(respData.d.AddValidateUpdateItemUsingPath.results);
}) })
.catch((error) => { .catch((error) => {
@ -206,6 +227,59 @@ export class ListFormService implements IListFormService {
}); });
} }
private uploadAttachments = async (webUrl: string, listUrl: string, itemId: number, attachments: any) => {
let responses = [];
for (var i = 0; i < attachments.length; i++) {
let attachment = attachments[i];
let httpClientOptions: ISPHttpClientOptions = {
headers: {
"Accept": "application/json; odata=verbose",
"content-type": "application/json; odata=verbose",
"content-length": attachment.buffer.byteLength,
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
'odata-version': '',
},
body: attachment.buffer,
};
let endpoint = `${webUrl}/_api/web/GetList(@listUrl)/items(@itemId)/AttachmentFiles/add(FileName='${attachment.fileName}')`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}&@itemId=%27${itemId}%27`;
try {
let response = await this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions);
if (response.ok) {
let respJson = await response.json();
responses.push(respJson);
} else {
return this.getErrorMessage(webUrl, response);
}
} catch (error) {
return error;
}
}
return responses;
}
private deleteAttachments = async (webUrl: string, listUrl: string, itemId: number, attachments: string[]) => {
let responses = [];
for (const attachment of attachments) {
let httpClientOptions: ISPHttpClientOptions = {
headers: {
"Accept": "application/json; odata=verbose",
"content-type": "application/json; odata=verbose",
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
"X-HTTP-Method": "DELETE",
'odata-version': '',
}
};
let endpoint = `${webUrl}/_api/web/GetList(@listUrl)/items(@itemId)/AttachmentFiles/getByFileName('${attachment}')`
+ `?@listUrl=${encodeURIComponent('\'' + listUrl + '\'')}&@itemId=%27${itemId}%27`;
try {
await this.spHttpClient.post(endpoint, SPHttpClient.configurations.v1, httpClientOptions);
} catch (error) {
return error;
}
}
return responses;
}
private GetFormValues(fieldsSchema: IFieldSchema[], data: any, originalData: any) private GetFormValues(fieldsSchema: IFieldSchema[], data: any, originalData: any)
: Array<{ FieldName: string, FieldValue: any, HasException: boolean, ErrorMessage: string }> { : Array<{ FieldName: string, FieldValue: any, HasException: boolean, ErrorMessage: string }> {
return fieldsSchema.filter( return fieldsSchema.filter(
@ -214,8 +288,8 @@ export class ListFormService implements IListFormService {
&& (field.InternalName in data) && (field.InternalName in data)
&& (data[field.InternalName] !== null) && (data[field.InternalName] !== null)
&& (data[field.InternalName] !== originalData[field.InternalName]) && (data[field.InternalName] !== originalData[field.InternalName])
), && (field.InternalName != "Attachments")
) ))
.map((field) => { .map((field) => {
return { return {
ErrorMessage: null, ErrorMessage: null,
@ -223,8 +297,37 @@ export class ListFormService implements IListFormService {
FieldValue: data[field.InternalName], FieldValue: data[field.InternalName],
HasException: false, HasException: false,
}; };
}, });
); }
private GetAttachmentsCreate(data: any)
: Array<{ buffer: any, bufferLength: number, fileName: string }> {
var results = new Array<{ buffer: any, bufferLength: number, fileName: string }>();
if (data.Attachments && data.Attachments.length > 0) {
results = data.Attachments
.filter((attachment: IAttachment) => typeof attachment.AttachmentId == "undefined")
.map((attachment: IAttachment) => {
return {
buffer: attachment.FileBuffer,
bufferLength: attachment.FileBuffer.byteLength,
fileName: attachment.FileName
} as { buffer: any, bufferLength: number, fileName: string };
});
}
return results;
}
private GetAttachmentsDelete(data: any, originalData: any)
: Array<string> {
let results = new Array<string>();
let newAttachments = (typeof data.Attachments.Attachments == "undefined") ? data.Attachments : data.Attachments.Attachments;
if (originalData && originalData.Attachments && originalData.Attachments.Attachments) {
let filtered = originalData.Attachments.Attachments
.filter((originalAttachment: IAttachment) =>
!newAttachments.some((attachment: IAttachment) => attachment.FileName == originalAttachment.FileName)
);
results = filtered.map((originalAttachment: IAttachment) => originalAttachment.FileName);
}
return results;
} }
/** /**

View File

@ -99,6 +99,7 @@ export interface IFieldSchema {
Disable?: boolean; Disable?: boolean;
WebServiceUrl: string; WebServiceUrl: string;
HiddenListInternalName: string; HiddenListInternalName: string;
HoursOptions?: string[];
} }
export interface IFormSchema { export interface IFormSchema {

View File

@ -0,0 +1,6 @@
export interface IAttachment {
AttachmentId?: string;
FileName: string;
FileBuffer?: ArrayBuffer;
RedirectUrl?: string;
}

View File

@ -7,6 +7,7 @@ import {
IPropertyPaneConfiguration, PropertyPaneDropdown, IPropertyPaneConfiguration, PropertyPaneDropdown,
PropertyPaneTextField, PropertyPaneToggle, IPropertyPaneField PropertyPaneTextField, PropertyPaneToggle, IPropertyPaneField
} from "@microsoft/sp-property-pane"; } from "@microsoft/sp-property-pane";
import { initializeIcons } from '@uifabric/icons';
import * as strings from 'ListFormWebPartStrings'; import * as strings from 'ListFormWebPartStrings';
import ListForm from './components/ListForm'; import ListForm from './components/ListForm';
@ -23,6 +24,7 @@ import { update, get } from '@microsoft/sp-lodash-subset';
import { ListService } from '../../common/services/ListService'; import { ListService } from '../../common/services/ListService';
import { ControlMode } from '../../common/datatypes/ControlMode'; import { ControlMode } from '../../common/datatypes/ControlMode';
initializeIcons();
export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebPartProps> { export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebPartProps> {

View File

@ -23,6 +23,7 @@ import HTML5Backend from 'react-dnd-html5-backend';
import * as strings from 'ListFormStrings'; import * as strings from 'ListFormStrings';
import styles from './ListForm.module.scss'; import styles from './ListForm.module.scss';
import { Validate } from '@microsoft/sp-core-library';
/************************************************************************************* /*************************************************************************************
* React Component to render a SharePoint list form on any page. * React Component to render a SharePoint list form on any page.

View File

@ -0,0 +1,131 @@
import * as React from 'react';
import { Link, Label } from 'office-ui-fabric-react';
import { Separator } from 'office-ui-fabric-react/lib/Separator';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
import { IconNames } from '@uifabric/icons';
import { IAttachment } from "../../../../types/IAttachment";
import { ControlMode } from '../../../../common/datatypes/ControlMode';
interface IAttachmentProps {
controlMode: ControlMode;
value: any;
valueChanged(newValue: any): void;
}
interface IAttachmentState {
waitingImageUpload: boolean;
fileBuffer: any;
hideDialog: boolean;
attachments: IAttachment[];
}
export class AttachmentRender extends React.Component<IAttachmentProps, IAttachmentState> {
private inputRef;
constructor(props: IAttachmentProps) {
super(props);
this.state = {
waitingImageUpload: null,
fileBuffer: null,
hideDialog: true,
attachments: new Array<IAttachment>()
};
this._handleFileSelect = this._handleFileSelect.bind(this);
this._handleFileInputChange = this._handleFileInputChange.bind(this);
this._deleteFileItem = this._deleteFileItem.bind(this);
this.inputRef = React.createRef();
}
public componentDidUpdate(prevProps: IAttachmentProps, prevState: IAttachmentState) {
//Component Value property got updated from List State
if (this.props.value
&& this.props.value.Attachments
&& this.props.value.Attachments.length > 0
&& this.state.attachments.length == 0
&& prevState.attachments.length == 0) {
this.setState({ attachments: this.props.value.Attachments });
}
if (this.state.attachments.length < prevState.attachments.length) {
this.props.valueChanged(this.state.attachments);
}
}
public render() {
return (
<div>
{this._renderFilesList()}
{this.props.controlMode != ControlMode.Display &&
<React.Fragment>
<Separator />
<Link onClick={this._handleFileSelect}>Add Attachment</Link>
<input id="inputFile1" ref={this.inputRef} type='file' onChange={this._handleFileInputChange} hidden />
</React.Fragment>
}
</div>
);
}
private _handleFileSelect = (e) => {
e.preventDefault();
this.inputRef.current.click();
}
private _handleFileInputChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
event.persist();
if (!event.target || !event.target.files) {
return;
}
let attachments = new Array<IAttachment>();
for (var i = 0; i < event.target.files.length; i++) {
let file = event.target.files.item(i);
if (!this.state.attachments.some(attachment => attachment.FileName == file.name)) {
let contentsAsBuffer: any = await this._getFileAsBuffer(file);
let attachment: IAttachment = {
FileBuffer: contentsAsBuffer,
FileName: file.name,
RedirectUrl: URL.createObjectURL(file)
};
attachments.push(attachment);
}
}
let allAttachments = this.state.attachments.concat(attachments);
this.setState({
attachments: allAttachments
});
this.props.valueChanged(allAttachments);
//Clear Input field
this.inputRef.current.value = '';
}
private _renderFilesList() {
return this.state.attachments.map((attachment: IAttachment, index: number) => {
let itemclass = mergeStyles({
lineHeight: "32px"
});
let link = attachment.RedirectUrl ?
<Link className={itemclass} target="_blank" href={attachment.RedirectUrl.replace("1https://", "https://")}>{attachment.FileName} </Link> :
<Label className={itemclass}>{attachment.FileName}</Label>;
return (
<div key={index}>
{link}
{this.props.controlMode != ControlMode.Display &&
<IconButton iconProps={{ iconName: IconNames.Delete }} onClick={() => this._deleteFileItem(index)} />}
</div>
);
});
}
private _deleteFileItem(index: number): void {
let attachments = new Array().concat(this.state.attachments);
attachments.splice(index, 1);
this.setState({ attachments });
}
private _getFileAsBuffer = inputFile => {
const temporaryFileReader = new FileReader();
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
reject(new DOMException("Problem parsing input file."));
};
temporaryFileReader.onload = () => {
resolve(temporaryFileReader.result);
};
temporaryFileReader.readAsArrayBuffer(inputFile);
});
}
}

View File

@ -1,26 +1,144 @@
import * as React from 'react'; import * as React from 'react';
import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; import * as moment from 'moment';
import { css } from 'office-ui-fabric-react/lib/Utilities';
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 { ComboBox, IComboBoxOption, IComboBox } from 'office-ui-fabric-react/lib/ComboBox';
import * as strings from 'FormFieldStrings'; import * as strings from 'FormFieldStrings';
import { IFieldSchema } from '../../../../common/services/datatypes/RenderListData';
export interface IDateFormFieldProps extends IDatePickerProps { export interface IDateFormFieldProps extends IDatePickerProps {
locale: string; locale: string;
fieldSchema: IFieldSchema;
valueChanged(newValue: any): void;
value: any;
}
export interface IDateFormFieldState {
date?: Date;
hours: number;
minutes: number;
} }
export default class DateFormField extends React.Component<IDateFormFieldProps> { export default class DateFormField extends React.Component<IDateFormFieldProps, IDateFormFieldState> {
constructor(props) { constructor(props) {
super(props); super(props);
this._createComboBoxHours = this._createComboBoxHours.bind(this);
this._createComboBoxMinutes = this._createComboBoxMinutes.bind(this);
this.state = {
hours: 0,
minutes: 0
};
}
public componentDidUpdate(prevProps: IDateFormFieldProps, prevState: IDateFormFieldState) {
//Component Value property got updated from List State
if (this.props.value && prevProps.value != this.props.value) {
console.log("Component Value property got updated from List State");
let momentDate = moment(this.props.value);
this.setState({
hours: momentDate.hour(),
minutes: momentDate.minute(),
date: momentDate.toDate()
});
}
//Component value updated
if (this.state.date && this.state.date != prevState.date) {
let result = this.props.fieldSchema.DisplayFormat == 1 ?
this.state.date.toLocaleDateString(this.props.locale) + " " + this.state.date.toLocaleTimeString(this.props.locale, { hour: "2-digit", minute: "2-digit" }) : //Date + Time
this.state.date.toLocaleDateString(this.props.locale); //Only date
this.props.valueChanged(result);
}
} }
public render() { public render() {
return ( return (
<DatePicker <React.Fragment>
{...this.props} <DatePicker
parseDateFromString={(dateStr: string) => new Date(Date.parse(dateStr))} allowTextInput={this.props.allowTextInput}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(this.props.locale) : ''} ariaLabel={this.props.ariaLabel}
strings={strings} className={css(this.props.className, this.props.fieldSchema.DisplayFormat == 1 ? "ms-sm12 ms-md12 ms-lg6 ms-xl8" : "ms-sm12")}
/> firstDayOfWeek={this.props.firstDayOfWeek}
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(this.props.locale) : ''}
isRequired={this.props.isRequired}
onSelectDate={this._onSelectDate}
parseDateFromString={(dateStr: string) => new Date(Date.parse(dateStr))}
placeholder={this.props.placeholder}
strings={strings}
value={this.state.date}
/>
{this.props.fieldSchema.DisplayFormat == 1 &&
<React.Fragment>
<ComboBox
onChange={this._onHoursChanged}
selectedKey={this.state.hours}
allowFreeform
autoComplete="on"
persistMenu={true}
options={this._createComboBoxHours()}
className={css(this.props.className, "ms-sm6", "ms-md6", "ms-lg3", "ms-xl2")}
/>
<ComboBox
selectedKey={this.state.minutes}
onChange={this._onMinutesChanged}
allowFreeform
autoComplete="on"
persistMenu={true}
options={this._createComboBoxMinutes()}
className={css(this.props.className, "ms-sm6", "ms-md6", "ms-lg3", "ms-xl2")}
/>
</React.Fragment>
}
</React.Fragment>
); );
} }
private _onSelectDate = (inputDate: Date | null | undefined): void => {
let date = inputDate ? moment(inputDate) : moment();
date.hour(this.state.hours);
date.minute(this.state.minutes);
this.setState({ date: date.toDate() });
}
private _onHoursChanged = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption): void => {
if (option) {
let date = this.state.date ? moment(this.state.date) : moment();
let hours = parseInt(option.key.toString());
date.hour(hours);
this.setState({ date: date.toDate(), hours });
}
}
private _onMinutesChanged = (event: React.FormEvent<IComboBox>, option?: IComboBoxOption): void => {
if (option) {
let date = this.state.date ? moment(this.state.date) : moment();
let minutes = parseInt(option.key.toString());
date.minute(minutes);
this.setState({ date: date.toDate(), minutes });
}
}
private _createComboBoxHours(): IComboBoxOption[] {
let results = new Array<IComboBoxOption>();
if (this.props.fieldSchema.HoursOptions) {
results = this.props.fieldSchema.HoursOptions.map((item, index) => {
return {
key: index,
text: item
} as IComboBoxOption;
});
}
return results;
}
private _createComboBoxMinutes(): IComboBoxOption[] {
let results = new Array<IComboBoxOption>();
for (var i = 0; i < 60; i++) {
results.push({
key: i,
text: ("00" + i).slice(-2)
});
}
return results;
}
} }

View File

@ -0,0 +1,15 @@
import * as React from 'react';
import { ISPFormFieldProps } from './SPFormField';
import { AttachmentRender } from './AttachmentRender';
const SPAttachmentFormFieldEdit: React.SFC<ISPFormFieldProps> = (props, state) => {
if (props.fieldSchema.FieldType !== 'Attachment') {
return <AttachmentRender
controlMode={props.controlMode}
valueChanged={props.valueChanged}
value={props.value}
> </AttachmentRender>;
}
};
export default SPAttachmentFormFieldEdit;

View File

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

View File

@ -21,6 +21,8 @@ import SPFieldLookupDisplay from './SPFieldLookupDisplay';
import SPFieldUserDisplay from './SPFieldUserDisplay'; import SPFieldUserDisplay from './SPFieldUserDisplay';
import SPFieldUrlDisplay from './SPFieldUrlDisplay'; import SPFieldUrlDisplay from './SPFieldUrlDisplay';
import SPAttachmentFormFieldEdit from './SPAttachmentFormFieldEdit';
import * as strings from 'FormFieldStrings'; import * as strings from 'FormFieldStrings';
import styles from './SPFormField.module.scss'; import styles from './SPFormField.module.scss';
@ -37,6 +39,7 @@ const EditFieldTypeMappings: { [fieldType: string]: React.StatelessComponent<ISP
DateTime: SPFieldDateEdit, DateTime: SPFieldDateEdit,
Boolean: SPFieldBooleanEdit, Boolean: SPFieldBooleanEdit,
File: SPFieldTextEdit, File: SPFieldTextEdit,
Attachments: SPAttachmentFormFieldEdit
/* The following are known but unsupported types as of now: /* The following are known but unsupported types as of now:
User: null, User: null,
UserMulti: null, UserMulti: null,
@ -91,7 +94,8 @@ const SPFormField: React.SFC<ISPFormFieldProps> = (props) => {
const fieldMapping = richText ? DisplayFieldTypeMappings['RichText'] : DisplayFieldTypeMappings[fieldType]; const fieldMapping = richText ? DisplayFieldTypeMappings['RichText'] : DisplayFieldTypeMappings[fieldType];
const childProps = fieldMapping.valuePreProcess ? { ...props, value: fieldMapping.valuePreProcess(props.value) } : props; const childProps = fieldMapping.valuePreProcess ? { ...props, value: fieldMapping.valuePreProcess(props.value) } : props;
fieldControl = React.createElement(fieldMapping.component, childProps); fieldControl = React.createElement(fieldMapping.component, childProps);
} else if (!props.hideIfFieldUnsupported) { }
else if (!props.hideIfFieldUnsupported) {
const value = (props.value) ? ((typeof props.value === 'string') ? props.value : JSON.stringify(props.value)) : ''; const value = (props.value) ? ((typeof props.value === 'string') ? props.value : JSON.stringify(props.value)) : '';
fieldControl = <div className={`ard-${fieldType}field-display`}> fieldControl = <div className={`ard-${fieldType}field-display`}>
<span>{value}</span> <span>{value}</span>

View File

@ -22,7 +22,25 @@ define([], function () {
prevYearAriaLabel: 'Go to previous year', prevYearAriaLabel: 'Go to previous year',
nextYearAriaLabel: 'Go to next year', nextYearAriaLabel: 'Go to next year',
isRequiredErrorMessage: 'This date is required.', isRequiredErrorMessage: 'This date is required.',
invalidInputErrorMessage: 'Invalid date format.' invalidInputErrorMessage: 'Invalid date format.',
//Attachment Data
SaveButtonText: 'Save',
'CancelButtonText': 'Cancel',
'AddNewFieldAction': 'Add a new field to form',
'LoadingFormIndicator': 'Loading the form...',
'ErrorLoadingSchema': 'Error loading schema for list ',
'ConfigureListMessage': 'Please configure a list in the web part\'s editor first.',
'RequiredValueMessage': 'Please enter a value!',
'ErrorLoadingData': 'Error loading data for item with ID ',
'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",
'RemoveButtonLabel': "Remove",
'ReplaceButtonLabel': "Replace",
} }
}); });

View File

@ -22,6 +22,12 @@ declare interface IFormFieldStrings {
nextMonthAriaLabel?: string; nextMonthAriaLabel?: string;
prevYearAriaLabel?: string; prevYearAriaLabel?: string;
nextYearAriaLabel?: string; nextYearAriaLabel?: string;
//Attachment
RemoveButtonLabel: string;
ReplaceButtonLabel: string;
AttachmentTermsConditionTitleText: string;
AttachmentTermsConditionText: string;
} }
declare module 'FormFieldStrings' { declare module 'FormFieldStrings' {