Create a modal dialog from the selection panel

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Create a modal dialog from the selection panel

Jamo
Jackie I'm porting over some of my selection panel customization to the new react based programming language I've managed to set it up find when a property of a certain value is hit and change it to a href or show an image or interpret the data accordingly ....

I'm struggling with showing a modal dialog similar to the help / about dialogs that appear..

Currently the modal I'm creating is bounded by the selection panel .... I see a ModalLauncher container but I don't know how to use it.....

Ideally I'm trying to create a ImageModal or something that shows an image in the selection panel then when the user clicks it it opens a modal in the center of the map area showing the image in question.

I've used an existing tsx file and fiddled with it a little to get the bones working.

image-modal.tsx
import * as React from "react";
import {ModalDialog} from "./modal-dialog";

export interface IImageModalProps {
  src: string;
}

export class ImageModal extends React.Component<IImageModalProps, any> {

  constructor(props: IImageModalProps) {
    super(props);
    this.state = {
      showModal: false
    };
  }

  setModalState(showModal: boolean) {
    this.setState({
      showModal: showModal
    });
  }

  render() {
    return (
      <div>
        <img src={this.props.src} onClick={this.setModalState.bind(this, true)} width="100%"/>

        <ModalDialog isOpen={this.state.showModal} backdrop={true} title={"Image"} >
        <img src={this.props.src} onClick={this.setModalState.bind(this, false)} width="100%"/>
        </ModalDialog>
      </div>
    )
  }
}

selection-panel.tsx changes
<tbody>
                                {props.map(prop => {
                                    if ((prop.Name == 'Image' || prop.Name == 'image') && (prop.Value.length > 0)) {
                                        var imgUrl = "file://vmapp01/spatial_data/imagery/terrain" + prop.Value;
                                        return <tr key={prop.Name}>
                                            <td>{prop.Name}</td>
                                            <td><ImageModal src={imgUrl}></ImageModal></td>
                                        </tr>;
                                    } else {
                                        return <tr key={prop.Name}>
                                            <td>{prop.Name}</td>
                                            <td>{prop.Value}</td>
                                        </tr>;
                                    }
                                })
                                }
                            </tbody>
MapGuide Maestro 6.0.0.8587
MapGuide Opensource 3.0.0.8701
Fusion, PHP, Apache
Windows 7 Pro SP1
Reply | Threaded
Open this post in threaded view
|

Re: Create a modal dialog from the selection panel

Jackie Ng
You were getting warm with ModalLauncher, but obviously the lack of documentation about this class obviously didn't help you in understanding what it does, so allow me to explain.

An easy way to understand what ModalLauncher does, is to imagine it as an (application-level) invisible "frame" that you can run InvokeURL commands into. Each URL you run in this ModalLauncher, it will spawn a modal dialog for it.

So how you do open URLs with the ModalLauncher? By dispatching any of the following actions under (src/actions/modal.ts):

 - showModalComponent
 - showModalUrl
 - hideModal

These action methods push their respective arguments (name, url, postition, size, etc) to the "modal" branch (src/reducers/modal.ts) of the redux application state. The ModalLauncher is set to listen to changes on this branch and will show/hide modal dialogs based on the modal actions you dispatch.

Which brings to the next question of how you dispatch these actions.

Before I start, it may help to read up on this very informative article about container/presentational (aka. smart/dumb) components:

https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

The way the mapguide-react-layout component source is laid out follows the same smart/dumb delineation:

 - "smart" components are under src/containers
 - "dumb" components are under src/components
 - Viewer layout template components are "smart" components, but are unique enough that they reside in src/layouts

To dispatch an action, you need to be dispatching from a "smart" component. A "smart" component is decorated by the redux connect() function that is passed 2 functions into it.

 - mapStateToProps: A function that is given the whole redux application state and selectively returns portions of the application state the component is interested in listening for changes
 - mapDispatchToProps: A function that is given the redux action dispatcher (this is the thing you use to dispatch any of the actions we define under src/actions), and returns an object with functions that dispatch your desired actions.

So to bring this all together.

Your image-modal.tsx? Probably don't need it.

Your selection-panel.tsx? Unfortunately, I can't tell whether you are modifying the "smart" one (src/containers/selection-panel.tsx) or the "dumb" one (src/containers/selection-panel.tsx). Regardless, this is what you need to do.

In the "smart" selection-panel, there should already be a mapDispatchToProps() function there that returns a Partial<ISelectionPanelContainerDispatch>. You'll need to modify the interface to include a function that opens image URLs.

src/containers/selection-panel.tsx
>>>

export interface ISelectionPanelContainerDispatch {
    ...
    openImageInModal: (src: string) => void;
}

<<<

Then you have to modify mapDispatchToProps() to map this method to dispatching an "open modal" action

src/containers/selection-panel.tsx
>>>

import { showModalUrl } from "../actions/modal";

...

function mapDispatchToProps(dispatch: ReduxDispatch): Partial<ISelectionPanelContainerDispatch> {
    return {
        ...
        openImageInModal: (src) => dispatch(showModalUrl({ url: src, name: "ImageModal" }))
    };
}

<<<

Now at this point, if your selection-panel modifications were in the "smart" one, your link click handler just needs to to call the newly mapped openImageInModal() function to launch the computed image URL in a modal dialog.

If the modifications were in a "dumb" one, you'll need an extra step of defining an onOpenImageInModal in the "dumb" component props interface, your link click handlers should then be calling this onOpenImageInModal function. Your "smart" component that wraps the "dumb" one would then provide a handler for onOpenImageInModal, to call the openImageInModal() function to dispatch the open modal action with your desired image URL.

Hope that helps (and what I said makes sense).

- Jackie
Reply | Threaded
Open this post in threaded view
|

Re: Create a modal dialog from the selection panel

Jamo
Jackie,

Thanks for the info pointed me in the right direction !!!

I've managed to get the dialog to pop up as intended the changes we're in fact on the dumb side of the selection-panel(component) as I check the property name before making any changes on how to display the property ....

There's a few things I can't quiet figure out .... how can I inject html instead of just opening the image in an iframe (this would give me control over the size of the image and or how to display the value content if it was something else) ...

Just wanted to put my changes here for anyone else that was interested, these are obviously specific to my needs but perhaps there's a smarter way of extending the selectionpanel properties?

...\containers\selection-panel.tsx
Add import for showModalUrl
import { showModalUrl } from "../actions/modal";


Add openDocumentUrlInModal to ISelectionPanelContainerDispatch
export interface ISelectionPanelContainerDispatch {
    setCurrentView: (view: IMapView) => void;
    openDocumentUrlInModal: (src: string) => void;
}
Add openDocumentUrlInModal to mapDispatchToProps (Bit of fumbling around here to realize I needed to define the additional modal properties)
function mapDispatchToProps(dispatch: ReduxDispatch): Partial<ISelectionPanelContainerDispatch> {
    return {
        setCurrentView: (view) => dispatch(MapActions.setCurrentView(view)),
        openDocumentUrlInModal: (src) => dispatch(showModalUrl({url:src, name:"DocumentContainer", modal:{size:[500,300], title:"Document",backdrop:false}})),
    };
}
Add fnOpenDocumentUrlInModal and private onOpenDocumentUrlInModal to SelectionPanelContainer
and add ?entryPoint? onOpenDocumentUrlInModal={this.fnOpenDocumentUrlInModal} function to the selectionPanel element

export class SelectionPanelContainer extends React.Component<SelectionPanelContainerProps, any> {
    private fnZoomToSelectedFeature: (feature: SelectedFeature) => void;
    private fnOpenDocumentUrlInModal: (src: string) => void;
    constructor(props: SelectionPanelContainerProps) {
        super(props);
        this.fnZoomToSelectedFeature = this.onZoomToSelectedFeature.bind(this);
        this.fnOpenDocumentUrlInModal = this.onOpenDocumentUrlInModal.bind(this);
    }
    private onZoomToSelectedFeature(feature: SelectedFeature) {
        const bbox: any = feature.Bounds.split(" ").map(s => parseFloat(s));
        const viewer = getViewer();
        if (viewer && this.props.setCurrentView) {
            const view = viewer.getViewForExtent(bbox);
            this.props.setCurrentView(view);
        }
    }
    private onOpenDocumentUrlInModal(src: string):any {
        if(this.props.openDocumentUrlInModal){
            this.props.openDocumentUrlInModal(src);
        }
    }
    private getLocale(): string {
        return this.props.config ? this.props.config.locale : "en";
    }
    render(): JSX.Element {
        const { selection, config, maxHeight } = this.props;
        const locale = this.getLocale();
        if (selection != null &&
            selection.SelectedFeatures != null) {
                return <SelectionPanel locale={locale} selection={selection.SelectedFeatures} onRequestZoomToFeature={this.fnZoomToSelectedFeature} maxHeight={maxHeight} onOpenDocumentUrlInModal={this.fnOpenDocumentUrlInModal} />;
        } else {
            return <div className="pt-callout pt-intent-primary pt-icon-info-sign">
                <p className="selection-panel-no-selection">{tr("NO_SELECTED_FEATURES", locale)}</p>
            </div>;
        }
    }
}

...\components\selection-panel.tsx
Add onOpenDocumentUrlInModal to the interface ISelectionPanelProps
export interface ISelectionPanelProps {
    locale?: string;
    selection: SelectedFeatureSet;
    onRequestZoomToFeature: (feat: SelectedFeature) => void;
    onOpenDocumentUrlInModal: (src: string) => void;
    maxHeight?: number;
}
Add openDocumentUrlInModal function to SelectionPanel
...
    onSelectedLayerChanged(e: GenericEvent) {
        this.setState({ selectedLayerIndex: e.target.value, featureIndex: 0 });
    }
    openDocumentUrlInModal(src: string): any {
        this.props.onOpenDocumentUrlInModal(src);
    }
    render(): JSX.Element {
...

Add the new openDocumentUrlInModal to the returned property /value mapping where required
<tbody>
{props.map(prop => {
  if ((prop.Name == 'Image' || prop.Name == 'image') && (prop.Value != null)) {
    var imgUrl = "file://someserver/somepath" + prop.Value;
    return <tr key={prop.Name}>
      <td>{prop.Name}</td>
      <td><a href="#" onClick={() => this.openDocumentUrlInModal(imgUrl)} >{prop.Value}</a></td>
      </tr>;
  } else {
    return <tr key={prop.Name}>
      <td>{prop.Name}</td>
      <td>{prop.Value}</td>
      </tr>;
  }
})}
</tbody>


MapGuide Maestro 6.0.0.8587
MapGuide Opensource 3.0.0.8701
Fusion, PHP, Apache
Windows 7 Pro SP1