Skip to content

Commit

Permalink
Merge pull request pnp#2 from SPFxAppDev/feature/searchbox
Browse files Browse the repository at this point in the history
Search Box Implemtation
  • Loading branch information
SPFxAppDev authored Mar 4, 2022
2 parents f34d4c5 + f97c4a8 commit 574e807
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .ash_history
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
npm run serve
exit
npm run serve
gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
npm run serve
4 changes: 2 additions & 2 deletions src/components/autocomplete/Autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface IAutocompleteProps extends Omit<ITextFieldProps, "componentRef"
showSuggestionsOnFocus?: boolean;
minValueLength?: number;
onLoadSuggestions?(newValue: string): void;
onRenderSuggestions?(): JSX.Element;
onRenderSuggestions?(inputValue: string): JSX.Element;
textFieldRef?(fluentUITextField: ITextField, autocompleteComponent: Autocomplete, htmlInput?: HTMLInputElement);
onUpdated?(newValue: string);
calloutProps?: Omit<ICalloutProps, "hidden" | "target" | "preventDismissOnScroll" | "directionalHint" | "directionalHintFixed" | "isBeakVisible">;
Expand Down Expand Up @@ -128,7 +128,7 @@ export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompl
}}
preventDismissOnScroll={true}
directionalHint={DirectionalHint.bottomCenter}>
{isFunction(this.props.onRenderSuggestions) && this.props.onRenderSuggestions()}
{isFunction(this.props.onRenderSuggestions) && this.props.onRenderSuggestions(this.state.currentValue)}
</Callout>
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/webparts/map/MapWebPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);

const reloadIfOneOfProps = ["height", "tileLayerUrl", "minZoom", "maxZoom", "tileLayerAttribution", "plugins.zoomControl"]
const reloadIfOneOfProps = ["height", "tileLayerUrl", "minZoom", "maxZoom", "tileLayerAttribution", "plugins.zoomControl"];

if(reloadIfOneOfProps.Contains(p => p.Equals(propertyPath))) {
this.reload();
Expand Down Expand Up @@ -214,9 +214,9 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
isCollapsed: true,
groupName: strings.WebPartPropertyGroupPlugins,
groupFields: [
// PropertyPaneToggle('plugins.searchBox', {
// label: "searchBox"
// }),
PropertyPaneToggle('plugins.searchBox', {
label: strings.WebPartPropertyPluginSearchboxLabel
}),
PropertyPaneToggle('plugins.markercluster', {
label: strings.WebPartPropertyPluginMarkerClusterLabel,
}),
Expand Down
42 changes: 39 additions & 3 deletions src/webparts/map/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { isFunction } from 'lodash';
import { MarkerIcon } from './MarkerIcon';
import MarkerClusterGroup from 'react-leaflet-markercluster';
import * as strings from 'MapWebPartStrings';
import SearchPlugin from './plugin/SearchPlugin';

interface IMapState {
markerItems: IMarker[];
Expand Down Expand Up @@ -128,9 +129,6 @@ export default class Map extends React.Component<IMapProps, IMapState> {

});

map.on("zoom", (ev: any) => {
console.log("SSC", this.props.maxZoom, ev);
})
this.map = map;
}
}
Expand All @@ -151,6 +149,8 @@ export default class Map extends React.Component<IMapProps, IMapState> {
{!this.props.plugins.markercluster &&
this.renderMarker()
}

{this.renderSearchBox()}
</MapContainer>

{this.renderLegend()}
Expand Down Expand Up @@ -341,6 +341,42 @@ export default class Map extends React.Component<IMapProps, IMapState> {
</div>);
}

private renderSearchBox(): JSX.Element {

if(!this.props.plugins.searchBox) {
return (<></>);
}

return (
<SearchPlugin onLocationSelected={(lat: number, lon: number) => {
this.map.setView([lat, lon], this.props.maxZoom > 18 ? 18 : this.props.maxZoom);

const defaultRadius = 12;
const circleOptions = {
inner: {
color: '#136AEC',
fillColor: '#2A93EE',
fillOpacity: 1,
weight: 1.5,
opacity: 0.7,
radius: defaultRadius / 4
},
outer: {
color: "#136AEC",
fillColor: "#136AEC",
fillOpacity: 0.15,
opacity: 0.3,
weight: 1,
radius: defaultRadius
}
};

L.circle([lat, lon], circleOptions.outer).addTo(this.map);
L.circle([lat, lon], circleOptions.inner).addTo(this.map);
}} />
);
}

private showClickContent(): JSX.Element {
if(!this.state.showClickContent || isNullOrEmpty(this.state.currentMarker)) {
return (<></>);
Expand Down
61 changes: 61 additions & 0 deletions src/webparts/map/components/plugin/SearchPlugin.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';


.map-plugin-search {
display: block;
top: 20px;
right: 20px;
z-index: 1000;
position: absolute;

button {
outline: none;
border-radius: 2px;
height: 32px;
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
font-size: 1.4em;
background: #fff;
cursor: pointer;
}

.textbox {
display: inline-block !important;
border-width: 0px;
padding: 0;

div {
border-width: 0px;
}
}
}

.suggesstion {
display: block;

&-item {
display: flex;
padding: 5px 20px;
border-bottom: solid 1px $ms-color-themeLighter;
border-top: solid 1px $ms-color-themeLighter;
align-items: center;
cursor: pointer;

&:first-child {
border-top-width: 0;
}

&:last-child {
border-bottom-width: 0;
}

i {
padding-right: 20px;
font-size: 2em;
}

span {
font-size: 1em;
}
}
}
106 changes: 106 additions & 0 deletions src/webparts/map/components/plugin/SearchPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { isFunction, isNullOrEmpty } from '@spfxappdev/utility';
import { Autocomplete } from '@src/components/autocomplete/Autocomplete';
import { Icon } from 'office-ui-fabric-react';
import * as React from 'react';
import styles from './SearchPlugin.module.scss';

export interface ISearchPluginProps {
nominatimUrl?: string;
resultLimit?: number;
onLocationSelected?(latitude: number, longitude: number): void;
}

interface ISearchResult {
place_id: number;
display_name: string;
lat: string;
lon: string;
}

interface ISearchPluginState {
searchResult: Array<ISearchResult>;
isSearchBoxVisible: boolean;
}

export default class SearchPlugin extends React.Component<ISearchPluginProps, ISearchPluginState> {

public state: ISearchPluginState = {
searchResult: [],
isSearchBoxVisible: false
};

public static defaultProps: ISearchPluginProps = {
nominatimUrl: "https://nominatim.openstreetmap.org/search",
resultLimit: 3
};


public render(): React.ReactElement<ISearchPluginProps> {
return (
<div className={styles["map-plugin-search"]}>
{this.state.isSearchBoxVisible &&
<Autocomplete
className={styles['textbox']}
onRenderSuggestions={(searchTerm: string) => {
return this.renderSuggesstionsFlyout(searchTerm);
}}
onChange={async (ev: any, searchTerm: string) => {
const result = await this.makeSearchRequest(searchTerm);
this.setState({
searchResult: result
});
}} />
}

<button type="button" onClick={() => {
const isVisible: boolean = this.state.isSearchBoxVisible ? false : true;

this.setState({
isSearchBoxVisible: isVisible
});
}}>
<Icon iconName="Search" />
</button>
</div>);
}

private renderSuggesstionsFlyout(searchTerm: string): JSX.Element {

const results = this.state.searchResult;

if(isNullOrEmpty(results)) {
return (<>
No results
</>);
}

return (<div className={styles["suggesstion"]}>
{results.map((location: ISearchResult, index: number): JSX.Element => {
return (<div
key={`Icon_${index}_${location.place_id}`}
onClick={() => {

if(isFunction(this.props.onLocationSelected)) {
this.props.onLocationSelected(parseFloat(location.lat), parseFloat(location.lon));
}

this.setState({
isSearchBoxVisible: false
});
}}
className={styles["suggesstion-item"]}>
{location.display_name}
</div>);
})}
</div>);

}

private async makeSearchRequest(searchTerm: string): Promise<ISearchResult[]> {

const response = await fetch(`${this.props.nominatimUrl}?format=json&limit=${this.props.resultLimit}&q=${searchTerm}`);
const responseJson = await response.json();
console.log("SSC", responseJson);
return responseJson;
}
}

0 comments on commit 574e807

Please sign in to comment.