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:
parent
d159945fd5
commit
c837006bf6
File diff suppressed because it is too large
Load Diff
|
@ -1,43 +1,44 @@
|
|||
{
|
||||
"name": "react-form-webpart",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"serve": "gulp serve",
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@tinymce/tinymce-react": "^3.0.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react-dnd": "~2.0.34",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"moment": "^2.24.0",
|
||||
"react-dnd": "~2.5.4",
|
||||
"react-dnd-html5-backend": "~2.5.4",
|
||||
"react-html-parser": "^2.0.2",
|
||||
"spfx-uifabric-themes": "~0.1.3",
|
||||
"tinymce": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-2.9": "^0.8.5",
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"tslint-microsoft-contrib": "5.0.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "react-form-webpart",
|
||||
"version": "1.0.2",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"serve": "gulp serve",
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.9.1",
|
||||
"@microsoft/sp-lodash-subset": "1.9.1",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.9.1",
|
||||
"@microsoft/sp-webpart-base": "1.9.1",
|
||||
"@tinymce/tinymce-react": "^3.0.1",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react-dnd": "~2.0.34",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"@uifabric/icons": "^7.3.14",
|
||||
"moment": "^2.24.0",
|
||||
"react-dnd": "~2.5.4",
|
||||
"react-dnd-html5-backend": "~2.5.4",
|
||||
"react-html-parser": "^2.0.2",
|
||||
"spfx-uifabric-themes": "~0.1.3",
|
||||
"tinymce": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-2.9": "^0.8.5",
|
||||
"@microsoft/sp-build-web": "1.9.1",
|
||||
"@microsoft/sp-module-interfaces": "1.9.1",
|
||||
"@microsoft/sp-tslint-rules": "1.9.1",
|
||||
"@microsoft/sp-webpart-workbench": "1.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"tslint-microsoft-contrib": "5.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import * as strings from 'servicesStrings';
|
|||
import { ControlMode } from '../datatypes/ControlMode';
|
||||
import { IFieldSchema, RenderListDataOptions } from './datatypes/RenderListData';
|
||||
import { IListFormService } from './IListFormService';
|
||||
import { IAttachment } from '../../types/IAttachment';
|
||||
|
||||
|
||||
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.
|
||||
* @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> {
|
||||
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': '',
|
||||
},
|
||||
};
|
||||
const formValues = this.GetFormValues(fieldsSchema, data, originalData);
|
||||
|
||||
httpClientOptions.body = JSON.stringify({
|
||||
bNewDocumentUpdate: false,
|
||||
checkInComment: null,
|
||||
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));
|
||||
});
|
||||
public updateItem = async (webUrl: string, listUrl: string, itemId: number, fieldsSchema: IFieldSchema[], data: any, originalData: any) => {
|
||||
const httpClientOptions: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
'Accept': 'application/json;odata=verbose',
|
||||
'Content-type': 'application/json;odata=verbose',
|
||||
'X-SP-REQUESTRESOURCES': 'listUrl=' + encodeURIComponent(listUrl),
|
||||
'odata-version': '',
|
||||
},
|
||||
};
|
||||
const formValues = this.GetFormValues(fieldsSchema, data, originalData);
|
||||
let createAttachmetns = this.GetAttachmentsCreate(data);
|
||||
let deleteAttachmetns = this.GetAttachmentsDelete(data, originalData);
|
||||
httpClientOptions.body = JSON.stringify({
|
||||
bNewDocumentUpdate: false,
|
||||
checkInComment: null,
|
||||
formValues,
|
||||
});
|
||||
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> {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
const formValues = this.GetFormValues(fieldsSchema, data, {});
|
||||
const formAttachmetns = this.GetAttachmentsCreate(data);
|
||||
const httpClientOptions: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
'Accept': 'application/json;odata=verbose',
|
||||
|
@ -198,6 +205,20 @@ export class ListFormService implements IListFormService {
|
|||
}
|
||||
})
|
||||
.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);
|
||||
})
|
||||
.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)
|
||||
: Array<{ FieldName: string, FieldValue: any, HasException: boolean, ErrorMessage: string }> {
|
||||
return fieldsSchema.filter(
|
||||
|
@ -214,8 +288,8 @@ export class ListFormService implements IListFormService {
|
|||
&& (field.InternalName in data)
|
||||
&& (data[field.InternalName] !== null)
|
||||
&& (data[field.InternalName] !== originalData[field.InternalName])
|
||||
),
|
||||
)
|
||||
&& (field.InternalName != "Attachments")
|
||||
))
|
||||
.map((field) => {
|
||||
return {
|
||||
ErrorMessage: null,
|
||||
|
@ -223,8 +297,37 @@ export class ListFormService implements IListFormService {
|
|||
FieldValue: data[field.InternalName],
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -99,6 +99,7 @@ export interface IFieldSchema {
|
|||
Disable?: boolean;
|
||||
WebServiceUrl: string;
|
||||
HiddenListInternalName: string;
|
||||
HoursOptions?: string[];
|
||||
}
|
||||
|
||||
export interface IFormSchema {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export interface IAttachment {
|
||||
AttachmentId?: string;
|
||||
FileName: string;
|
||||
FileBuffer?: ArrayBuffer;
|
||||
RedirectUrl?: string;
|
||||
}
|
|
@ -7,6 +7,7 @@ import {
|
|||
IPropertyPaneConfiguration, PropertyPaneDropdown,
|
||||
PropertyPaneTextField, PropertyPaneToggle, IPropertyPaneField
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { initializeIcons } from '@uifabric/icons';
|
||||
|
||||
import * as strings from 'ListFormWebPartStrings';
|
||||
import ListForm from './components/ListForm';
|
||||
|
@ -23,6 +24,7 @@ import { update, get } from '@microsoft/sp-lodash-subset';
|
|||
import { ListService } from '../../common/services/ListService';
|
||||
import { ControlMode } from '../../common/datatypes/ControlMode';
|
||||
|
||||
initializeIcons();
|
||||
|
||||
export default class ListFormWebPart extends BaseClientSideWebPart<IListFormWebPartProps> {
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import HTML5Backend from 'react-dnd-html5-backend';
|
|||
import * as strings from 'ListFormStrings';
|
||||
|
||||
import styles from './ListForm.module.scss';
|
||||
import { Validate } from '@microsoft/sp-core-library';
|
||||
|
||||
/*************************************************************************************
|
||||
* React Component to render a SharePoint list form on any page.
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,26 +1,144 @@
|
|||
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 { ComboBox, IComboBoxOption, IComboBox } from 'office-ui-fabric-react/lib/ComboBox';
|
||||
|
||||
import * as strings from 'FormFieldStrings';
|
||||
import { IFieldSchema } from '../../../../common/services/datatypes/RenderListData';
|
||||
|
||||
|
||||
export interface IDateFormFieldProps extends IDatePickerProps {
|
||||
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) {
|
||||
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() {
|
||||
return (
|
||||
<DatePicker
|
||||
{...this.props}
|
||||
parseDateFromString={(dateStr: string) => new Date(Date.parse(dateStr))}
|
||||
formatDate={(date: Date) => (typeof date.toLocaleDateString === 'function') ? date.toLocaleDateString(this.props.locale) : ''}
|
||||
strings={strings}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<DatePicker
|
||||
allowTextInput={this.props.allowTextInput}
|
||||
ariaLabel={this.props.ariaLabel}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -11,16 +11,17 @@ 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')}
|
||||
className={css(styles.dateFormField, 'ard-dateFormField', 'ms-Grid-col')}
|
||||
placeholder={strings.DateFormFieldPlaceholder}
|
||||
isRequired={props.fieldSchema.Required}
|
||||
ariaLabel={props.fieldSchema.Title}
|
||||
locale={Locales[locale]}
|
||||
locale={locale}
|
||||
firstDayOfWeek={props.fieldSchema.FirstDayOfWeek}
|
||||
allowTextInput
|
||||
onSelectDate={(date) => props.valueChanged(date.toLocaleDateString(locale))}
|
||||
fieldSchema={props.fieldSchema}
|
||||
value={props.value}
|
||||
valueChanged={props.valueChanged}
|
||||
/>;
|
||||
};
|
||||
|
||||
export default SPFieldDateEdit;
|
||||
export default SPFieldDateEdit;
|
|
@ -21,6 +21,8 @@ import SPFieldLookupDisplay from './SPFieldLookupDisplay';
|
|||
import SPFieldUserDisplay from './SPFieldUserDisplay';
|
||||
import SPFieldUrlDisplay from './SPFieldUrlDisplay';
|
||||
|
||||
import SPAttachmentFormFieldEdit from './SPAttachmentFormFieldEdit';
|
||||
|
||||
import * as strings from 'FormFieldStrings';
|
||||
import styles from './SPFormField.module.scss';
|
||||
|
||||
|
@ -37,6 +39,7 @@ const EditFieldTypeMappings: { [fieldType: string]: React.StatelessComponent<ISP
|
|||
DateTime: SPFieldDateEdit,
|
||||
Boolean: SPFieldBooleanEdit,
|
||||
File: SPFieldTextEdit,
|
||||
Attachments: SPAttachmentFormFieldEdit
|
||||
/* The following are known but unsupported types as of now:
|
||||
User: null,
|
||||
UserMulti: null,
|
||||
|
@ -91,7 +94,8 @@ const SPFormField: React.SFC<ISPFormFieldProps> = (props) => {
|
|||
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) {
|
||||
}
|
||||
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>
|
||||
|
|
|
@ -22,7 +22,25 @@ define([], function () {
|
|||
prevYearAriaLabel: 'Go to previous year',
|
||||
nextYearAriaLabel: 'Go to next year',
|
||||
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",
|
||||
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,6 +22,12 @@ declare interface IFormFieldStrings {
|
|||
nextMonthAriaLabel?: string;
|
||||
prevYearAriaLabel?: string;
|
||||
nextYearAriaLabel?: string;
|
||||
|
||||
//Attachment
|
||||
RemoveButtonLabel: string;
|
||||
ReplaceButtonLabel: string;
|
||||
AttachmentTermsConditionTitleText: string;
|
||||
AttachmentTermsConditionText: string;
|
||||
}
|
||||
|
||||
declare module 'FormFieldStrings' {
|
||||
|
|
Loading…
Reference in New Issue