Skip to content

Commit 8dde0db

Browse files
ravichandran-blogAJIXuMuK
authored andcommitted
Location Picker + Dynamic Form Changes
Location Picker + Dynamic Form Changes
1 parent 6803d8a commit 8dde0db

File tree

15 files changed

+509
-74
lines changed

15 files changed

+509
-74
lines changed
18.6 KB
Loading
3.8 KB
Loading
3.63 KB
Loading
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Location Picker
2+
3+
4+
5+
This control allows you to search and select the Location, also allows enter custom location.
6+
7+
8+
## How to use this control in your solutions
9+
10+
11+
12+
- Check that you installed the `@pnp/spfx-controls-react` dependency. Check out the [getting started](../../#getting-started) page for more information about installing the dependency.
13+
14+
- Import the following modules to your component:
15+
16+
17+
18+
```TypeScript
19+
20+
import { LocationPicker,ILocationPickerItem } from "fx-controls-react/lib/LocationPicker";
21+
22+
```
23+
24+
25+
26+
- Use the LocationPicker control in your code as follows:
27+
28+
29+
30+
```jsx
31+
32+
<LocationPicker
33+
context={this.props.context}
34+
label="Location"
35+
onSelectionChanged={(locValue: ILocationPickerItem) => {
36+
console.log(locValue.DisplayName + ", " + locValue.Address.Street)
37+
}
38+
}/>
39+
40+
41+
```
42+
43+
![Location Picker search](../assets/location1.png)
44+
45+
![Location Picker Edit](../assets/location2.png)
46+
47+
![Location Picker Read](../assets/location3.png)
48+
49+
50+
51+
## Implementation
52+
53+
54+
55+
The `LocationPicker` can be configured with the following properties:
56+
57+
58+
59+
| Property | Type | Required | Description |
60+
61+
| ---- | ---- | ---- | ---- |
62+
63+
| context | WebPartContext or ExtensionContext | yes | The context object of the SPFx loaded webpart or customizer. |
64+
65+
| disabled | boolean | no | Option allow to be enable or disable. Default value is `false`|
66+
67+
| defaultValue | ILocationPickerItem | no | Option allow set default value|
68+
69+
| errorMessage | string | no | Static error message displayed below the picker.|
70+
71+
| className | string | no | applies custom styling |
72+
73+
| label | string | no | Label to use for the control. |
74+
75+
| placeholder | string | no | Placeholder label to show in the dropdown. |
76+
77+
| onSelectionChanged | (locItem: ILocationPickerItem) => void | no | Method that returns location data JSON object. |
78+

src/LocationPicker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './controls/locationPicker/index';

src/controls/dynamicForm/DynamicForm.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
8585
if (val.required) {
8686
if (val.newValue === null) {
8787
if (val.fieldDefaultValue === null || val.fieldDefaultValue === '' || val.fieldDefaultValue.length === 0) {
88-
val.fieldDefaultValue = '';
88+
if (val.fieldType === "DateTime")
89+
val.fieldDefaultValue = null;
90+
else
91+
val.fieldDefaultValue = '';
8992
shouldBeReturnBack = true;
9093
}
9194
}
@@ -145,6 +148,9 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
145148
else if (fieldType === "MultiChoice") {
146149
objects[columnInternalName] = { results: val.newValue };
147150
}
151+
else if (fieldType === "Location") {
152+
objects[columnInternalName] = JSON.stringify(val.newValue);
153+
}
148154
else if (fieldType === "UserMulti") {
149155
objects[`${columnInternalName}Id`] = { results: val.newValue };
150156
}
@@ -169,7 +175,7 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
169175
}
170176

171177
if (onBeforeSubmit) {
172-
const isCancelled = await onBeforeSubmit(objects);
178+
const isCancelled = await onBeforeSubmit(objects);
173179

174180
if (isCancelled) {
175181
this.setState({
@@ -232,10 +238,15 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
232238
}
233239
else if (field.fieldType === "UserMulti" && newValue.length !== 0) {
234240
field.newValue = [];
235-
newValue.forEach(async element => {
236-
let result = await sp.web.ensureUser(element.secondaryText);
241+
for (let index = 0; index < newValue.length; index++) {
242+
const element = newValue[index];
243+
let user: string = element.secondaryText;
244+
if (user.indexOf('@') === -1) {
245+
user = element.loginName;
246+
}
247+
let result = await sp.web.ensureUser(user);
237248
field.newValue.push(result.data.Id);
238-
});
249+
}
239250
}
240251
this.setState({
241252
fieldCollection: fieldCol
@@ -392,6 +403,10 @@ export class DynamicForm extends React.Component<IDynamicFormProps, IDynamicForm
392403
defaultValue = [];
393404
}
394405
}
406+
else if (fieldType === "Location") {
407+
defaultValue = JSON.parse(defaultValue);
408+
}
409+
395410
tempFields.push({
396411
newValue: null,
397412
fieldTermSetId: termSetId,

src/controls/dynamicForm/dynamicField/DynamicField.tsx

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PeoplePicker, PrincipalType } from '../../peoplepicker';
99
import { FilePicker, IFilePickerResult } from '../../filePicker';
1010
import { TaxonomyPicker, IPickerTerms } from '../../taxonomyPicker';
1111
import { ListItemPicker } from '../../listItemPicker';
12+
import { LocationPicker } from '../../locationPicker';
1213
import { RichText } from '../../richText';
1314
import { Icon } from 'office-ui-fabric-react/lib/Icon';
1415
import { Shimmer } from 'office-ui-fabric-react/lib/Shimmer';
@@ -184,6 +185,22 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
184185
errorMessage={errorText} />
185186
</div>;
186187

188+
case 'Location':
189+
return <div className={styles.fieldContainer}>
190+
<div className={`${styles.labelContainer} ${styles.titleContainer}`}>
191+
<Icon className={styles.fieldIcon} iconName={"POI"} />
192+
{labelEl}
193+
</div>
194+
<LocationPicker
195+
context={context}
196+
disabled={disabled}
197+
placeholder={placeholder}
198+
onSelectionChanged={(newValue) => { this.onChange(newValue); }}
199+
defaultValue={defaultValue}
200+
errorMessage={errorText}
201+
/>
202+
</div>;
203+
187204
case 'Lookup':
188205
return <div>
189206
<div className={styles.titleContainer}>
@@ -504,6 +521,7 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
504521
this.setState({
505522
changedValue: currValue
506523
});
524+
this.props.onChanged(this.props.columnInternalName, currValue);
507525
}
508526

509527
private onChange = (value: any) => {
@@ -565,47 +583,19 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
565583

566584
private saveIntoSharePoint = async (files: IFilePickerResult[]) => {
567585
const {
568-
context,
569-
listId,
570-
listItemId,
571586
columnInternalName,
572-
onChanged,
573-
fieldType
587+
onChanged
574588
} = this.props;
575589

576590
let newValue: any;
577-
let preview: string | undefined;
578-
579591
if (!files.length) {
580592
return;
581593
}
582594

583595
try {
584596
const file = files[0];
585-
586-
587-
588597
if (file.fileAbsoluteUrl === null) {
589598
newValue = file.previewDataUrl;
590-
//await this.ensureSiteAssetsFolder();
591-
//const folderUrl = urlCombine(context.pageContext.web.serverRelativeUrl, `SiteAssets/Lists/${listId}`, false);
592-
593-
// const resultContent = await file.downloadFileContent();
594-
// const fileArrayBuffer = await resultContent.arrayBuffer;
595-
596-
// const fileResponse = await context.spHttpClient.post(`/_api/web/UplaodImage(imageName=@a2,listId=@a3,itemId=@4)?a2='${file.fileName}'&a3='${listId}'&a4=${listItemId || 0}`, SPHttpClient.configurations.v1, {
597-
// body: fileArrayBuffer,
598-
// headers: {
599-
// 'content-length': fileArrayBuffer.byteLength.toString()
600-
// }
601-
// });
602-
603-
// let fileResult = await fileResponse.json();
604-
// newValue = {
605-
// "__metadata": { "type": "SP.FieldUrlValue" },
606-
// "Description": file.fileName,
607-
// "Url": document.location.origin + fileResult.data.ServerRelativeUrl
608-
// };
609599
}
610600
else {
611601
newValue = file.fileAbsoluteUrl;
@@ -622,37 +612,4 @@ export class DynamicField extends React.Component<IDynamicFieldProps, IDynamicFi
622612
console.log(`Error save Into SharePoint`, error);
623613
}
624614
}
625-
626-
private ensureSiteAssetsFolder = async () => {
627-
const {
628-
context,
629-
listId
630-
} = this.props;
631-
const siteRelativeUrl = context.pageContext.web.serverRelativeUrl;
632-
const folderUrl = urlCombine(siteRelativeUrl, `SiteAssets/Lists/${listId}`, false);
633-
634-
let folder: IFolder | undefined;
635-
636-
try {
637-
await sp.web.getFolderByServerRelativeUrl(folderUrl).get();
638-
folder = sp.web.getFolderByServerRelativeUrl(folderUrl);
639-
}
640-
catch {
641-
folder = undefined;
642-
//const folderAddResult = await sp.web.folders.add(`SiteAssets/Lists/${docLibId}`);
643-
//folder = await folderAddResult.folder.get();
644-
}
645-
646-
if (!folder) { // we need to create a folder with all parents
647-
const folderPath = ['Lists', listId];
648-
// Site Assets root folder
649-
let mainFolder: IFolder = sp.web.getFolderByServerRelativeUrl(urlCombine(siteRelativeUrl, 'SiteAssets'));
650-
651-
for (let i = 0, len = folderPath.length; i < len; i++) {
652-
const folderName = folderPath[i];
653-
mainFolder = await mainFolder.addSubFolderUsingPath(folderName);
654-
}
655-
folder = mainFolder;
656-
}
657-
}
658615
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { ExtensionContext } from '@microsoft/sp-extension-base';
2+
import { WebPartContext } from '@microsoft/sp-webpart-base';
3+
import { IComboBoxOption } from 'office-ui-fabric-react/lib/';
4+
5+
export interface ILocationBoxOption extends IComboBoxOption {
6+
locationItem: ILocationPickerItem;
7+
}
8+
9+
export interface ILocationPickerItem {
10+
EntityType: string;
11+
LocationSource?: string;
12+
LocationUri?: string;
13+
UniqueId?: string;
14+
DisplayName: string;
15+
Address?: IAddress;
16+
Coordinates?: any;
17+
}
18+
19+
interface IAddress
20+
{
21+
City?:string;
22+
CountryOrRegion?:string;
23+
State?:string;
24+
Street?:string;
25+
}
26+
27+
export interface ILocationPickerProps {
28+
/**
29+
* The web part context
30+
*/
31+
context: WebPartContext | ExtensionContext;
32+
/**
33+
* If provided, additional class name to provide on the dropdown element.
34+
*/
35+
className?: string;
36+
/**
37+
* Whether or not the control is disabled
38+
*/
39+
disabled?: boolean;
40+
/**
41+
* The label to use
42+
*/
43+
label?: string;
44+
/**
45+
* Input placeholder text. Displayed until option is selected.
46+
*/
47+
placeholder?: string;
48+
/**
49+
* Input placeholder text. Displayed until option is selected.
50+
*/
51+
defaultValue?: ILocationPickerItem;
52+
/**
53+
* Callback issued when the selected option changes
54+
*/
55+
onSelectionChanged?: (newValue: ILocationPickerItem) => void;
56+
57+
/**
58+
* This can be use to show error message for combobox
59+
*/
60+
errorMessage?:string;
61+
}
62+
63+
export interface ILocationPickerState {
64+
currentMode: Mode;
65+
searchText: string;
66+
isCalloutVisible: boolean;
67+
seletedItem: any;
68+
/**
69+
* The options available to the listPicker
70+
*/
71+
options: ILocationBoxOption[];
72+
}
73+
74+
75+
export enum Mode {
76+
view,
77+
empty,
78+
editView,
79+
}

0 commit comments

Comments
 (0)