Select to view content in your preferred language

Toggle side bar widget open and closed on click

1189
1
06-29-2022 08:59 PM
helloFriend
New Contributor

I am running into trouble with trying to take the example of opening and closing a side widget programmatically.

I got the example from https://github.com/Esri/arcgis-experience-builder-sdk-resources/tree/master/widgets/control-the-widg... but I am want to make it so once you hit a specifc function that will toggle it open or closed I dont want it to be a button like the example.

 

I get an error of  Minified React error #321; I tried removing multple instance of React and same problem. 

 

Thanks if anybody can help.

 

This is my code.

 

 

/** @jsx jsx */
// import { React, AllWidgetProps, jsx } from 'jimu-core';
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis';
import { TextArea } from 'jimu-ui';
import { Button, Icon } from 'jimu-ui'; // import components
import { Table } from 'jimu-ui';
import { Spinner } from 'reactstrap';
import { StarFilled } from 'jimu-icons/filled/application/star';
import Graphic from 'esri/Graphic';
import Point from 'esri/geometry/Point';
import PopupTemplate from 'esri/PopupTemplate';
import AttachmentQuery from 'esri/rest/support/AttachmentQuery';
import RelationshipQuery from 'esri/rest/support/RelationshipQuery';
import FeatureLayer from 'esri/layers/FeatureLayer';
import { Dropdown, DropdownItem, DropdownMenu, DropdownButton, Checkbox } from 'jimu-ui';
import { TextInput } from 'jimu-ui';
import { TextArea } from 'jimu-ui';
import { useState } from "react";
import { React, AllWidgetProps, getAppStore, appActions, ReactRedux, WidgetProps, WidgetManager, IMState,jsx } from 'jimu-core';

// import { Button, Label, Row, Col, Select, Option } from 'jimu-ui';

import './styles.css';

const iconNode = <StarFilled />;
const { useState, useEffect, } = React;
const { useSelector } = ReactRedux;

export default class Widget extends React.PureComponent<AllWidgetProps<any>, any> {

    state = {
        jimuMapView: null,
        latitude: '',
        longitude: '',
        feature: { attributes:
            { GlobalID: null,
                ID:'',
                SignalNumber:'',
                SignalCategory:'',
                Hamlet: '' ,
                Intersection1:'',
                Intersection2:'',
                SignalType:'',
                Jurisdiction:'',
                YearBuilt:'',
                LatestBuild:'',
                AssetCost:"",
                LEDManufacturerdel:"",
                NumberPhase:'',
                CycleLength:'',
                PoleType:'',
                DetType:'',
                SupportType:'',
                F8LED:'',
                F8Incan:'',
                F12LED:'',
                F12Incan:'',
                Contractor:'',
                LipaAccount:'',
                email:'',
                SignalNote:'',
            }, layer: null },
        workOrderFeatureLayer: null,
    };
       
     handleChange = (e) => {
        const [file, setFile] = useState();

        console.log(e.target.files);
        setFile(URL.createObjectURL(e.target.files[0]));
};

sidebarWidget = () => {
    // Establish state properties, initial values and their corresponding set state actions
    const { useState, useEffect, } = React;
    const { useSelector } = ReactRedux;
    const [sidebarWidgetId, setSidebarWidgetId] = useState(null as string);
    const [openCloseWidgetId, setOpenCloseWidgetId] = useState(null as string);
    const [sidebarVisible] = useState(true as boolean);
    const [openness, setOpenness] = useState(false as boolean);
    const [appWidgets, setAppWidgets] = useState({} as Object);
    const [widgetsArray, setWidgetsArray] = useState([] as Array<any>);
    const [sidebarWidgetsArray, setSidebarWidgetsArray] = useState([] as Array<any>);
    // Get the widget state - because the sidebar state may change in the runtime, via Redux's useSelector hook
    const widgetState = useSelector((state: IMState) => {
        const widgetState = state.widgetsState[sidebarWidgetId];
        return widgetState;
    });

        // Update the appWidgets property once, on page load
        useEffect(() => {
            const widgets = getAppStore().getState().appConfig.widgets;
            setAppWidgets(widgets);
        }, []);
   
        // Update the widgetsArray and sidebarWidgetsArray properties every time appWidgets changes
        useEffect(() => {
            if (appWidgets) {
                const widgetsArray = Object.values(appWidgets);
                setWidgetsArray(widgetsArray);
                setSidebarWidgetsArray(widgetsArray.filter(w => w.uri === 'widgets/layout/sidebar/'));
            }
        }, [appWidgets]);


    // Toggle the sidebar widget
    function handleToggleSidebar () {
        // If widget state's collapse property is true, collapse
        if (widgetState && widgetState.collapse === true) {
        getAppStore().dispatch(
            appActions.widgetStatePropChange(
            sidebarWidgetId,
            "collapse",
            !sidebarVisible
            )
        );
        }
        // If widget state's collapse property is false, expand
        else if (widgetState && widgetState.collapse === false) {
        getAppStore().dispatch(
            appActions.widgetStatePropChange(
            sidebarWidgetId,
            "collapse",
            sidebarVisible
            )
        );
        } else {
        console.log("Hey it's your console log");
        }
    };
    // handleToggleSidebar()

    // Load the widget class prior to executing the open/close actions
    const loadWidgetClass = (widgetId: string😞 Promise<React.ComponentType<WidgetProps>> => {
        if (!widgetId) return;
        const isClassLoaded = getAppStore().getState().widgetsRuntimeInfo?.[widgetId]?.isClassLoaded;
        if (!isClassLoaded) {
            return WidgetManager.getInstance().loadWidgetClass(widgetId);
        } else {
            return Promise.resolve(WidgetManager.getInstance().getWidgetClass(widgetId));
        }
    };

       // Open widget method
       const handleOpenWidget = (): void => {
        // Construct the open action, then run the loadWidgetClass method, dipatch the open action
        // and, finally, set the openness to true
        const openAction = appActions.openWidget(openCloseWidgetId);
        loadWidgetClass(openCloseWidgetId).then(() => {
            getAppStore().dispatch(openAction);
        }).then(() => { setOpenness(true) });
    };

    const handleCloseWidget = (): void => {
        // Construct the close action, then run the loadWidgetClass function, dipatch the close action
        // and, finally, set the openness to false
        const closeAction = appActions.closeWidget(openCloseWidgetId);
        loadWidgetClass(openCloseWidgetId).then(() => {
            getAppStore().dispatch(closeAction);
        }).then(() => { setOpenness(false) });
    };

    // Handler for the openness toggle button
    // const handleToggleOpennessButton = (): void => {
    //     // Check the openness property value and run the appropriate function
    //     if (openness === false) { handleOpenWidget(); }
    //     else if (openness === true) { handleCloseWidget(); }
    //     else { console.error(defaultMessages.opennessError) }
    // };

    // Handler for the sidebar selection
    const handleSidebarSelect = evt => {
        setSidebarWidgetId(evt.currentTarget.value);
    };

    // Handler for the open/close selection
    const handleOpenCloseSelect = evt => {
        setOpenCloseWidgetId(evt.currentTarget.value);
    };

    }

    // ATTACHMENT FUNCTIONS
    createAttachment = () => {
        const self = this;
        console.log(this);
        // this function must be called from  a user
        // activation event (ie an onclick event)

        // Create an input element
        var inputElement = document.createElement('input');

        // Set its type to file
        inputElement.type = 'file';

        // Set accept to the file types you want the user to select.
        // Include both the file extension and the mime type
        inputElement.accept = 'png';

        // set onchange event to call callback when user has selected file
        inputElement.addEventListener('change', (file) => {
            const feature = this.state.feature;
            const layer = feature.layer;
            const form = new FormData();

            form.set('attachment', inputElement.files[0]);
            form.append('f', 'json');
            layer
                .addAttachment(feature, form)
                .then(function (result) {
                    console.log('attachment added: ', result);
                    self.readAttachment(feature);
                })
                .catch(function (err) {
                    console.log('attachment adding failed: ', err);
                });
        });

        // dispatch a click event to open the file dialog
        inputElement.dispatchEvent(new MouseEvent('click'));
    };
    readAttachment = (feature) => {
        const self = this;
        const featureAttachments = document.getElementById('featureAttachments');
        const fragment = document.createDocumentFragment();
        const objectID = feature.attributes.ObjectID;
        const query = new AttachmentQuery({
            objectIds: [objectID],
        });

        featureAttachments.innerHTML = '';

        feature.layer.queryAttachments(query).then((attachments) => {
            if (attachments && attachments[objectID]) {
                attachments[objectID].forEach((attachment) => {
                    const row = document.createElement('tr');
                    const attachmentID = document.createElement('td');
                    const name = document.createElement('td');
                    const contentType = document.createElement('td');
                    const size = document.createElement('td');
                    const keywords = document.createElement('td');
                    const removeFeature = document.createElement('td');

                    attachmentID.innerHTML = attachment.id;
                    name.innerHTML = attachment.name;
                    contentType.innerHTML = attachment.contentType;
                    size.innerHTML = attachment.size;
                    keywords.innerHTML = attachment.keywords;

                    removeFeature.innerHTML = 'Delete?';
                    removeFeature.style.cursor = 'pointer';
                    removeFeature.addEventListener('click', () => {
                        self.deleteAttachment(feature.layer, attachment.globalId);
                    });

                    row.append(attachmentID, name, contentType, size, keywords, removeFeature);
                    fragment.append(row);
                });

                featureAttachments.append(fragment);
            }
        });
    };
    updateSignal = () => {
        const hamletField = document.getElementById('hamlet-field');
        const feature = this.state.feature;
        const featureLayer = this.state.feature.layer;
        const edits = {
            updateFeatures: [feature],
        };

        feature.attributes.Hamlet = hamletField.value;

        featureLayer
            .applyEdits(edits)
            .then((editsResult) => {
                console.log(editsResult);
            })
            .catch((error) => {
                console.log('error = ', error);
            });
    };
    deleteAttachment = (layer, attachmentID) => {
        const self = this;
        const edits = {
            deleteAttachments: [attachmentID],
        };

        layer
            .applyEdits(edits, { globalIdUsed: true, rollbackOnFailureEnabled: true })
            .then((editsResult) => {
                console.log(editsResult);
                self.readAttachment(self.state.feature);
            })
            .catch((error) => {
                console.log('error = ', error);
            });
    };

    // WORK ORDER FUNCTIONS
    createWorkOrder = () => {
        const self = this;
        const feature = this.state.feature;
        let featureLayerURL = this.state.feature.layer.url;
        const workOrderLayerNumber = feature.layer.relationships[0].relatedTableId;
        const workOrderDescription = document.getElementById('work-order-description');
        const workOrderGraphic = new Graphic({
            attributes: {
                ParentGlobalID: feature.attributes.GlobalID,
                TextField1: workOrderDescription.innerHTML || 'things',
                IntegerField: 12345678,
            },
        });
        const edits = {
            addFeatures: [workOrderGraphic],
        };

        if (this.state.workOrderFeatureLayer === null) {
            this.state.workOrderFeatureLayer = new FeatureLayer({
                url: `${featureLayerURL}/${workOrderLayerNumber}`,
            });
        }

        this.state.workOrderFeatureLayer
            .applyEdits(edits)
            .then((editsResult) => {
                self.readWorkOrder(feature);
            })
            .catch((error) => {
                console.log('error = ', error);
            });
    };

   
    readWorkOrder = (feature) => {
        const editFormButton = document.getElementsByClassName('esri-icon-edit')
        const self = this;
        const featureWorkOrders = document.getElementById('workOrderEntries');
        const fragment = document.createDocumentFragment();
        const objectID = feature.attributes.ObjectID;
        const query = new RelationshipQuery({
            objectIds: [objectID],
            outFields: ['*'],
        });
       
        featureWorkOrders.innerHTML = '';
        feature.layer.queryRelatedFeatures(query).then((workOrders) => {
            if (workOrders && workOrders[objectID]) {
                workOrders[objectID].features.forEach((workOrder) => {
                    const row = document.createElement('tr');
                    const date = document.createElement('td');
                    const double = document.createElement('td');
                    const integer = document.createElement('td');
                    const text = document.createElement('td');
                    const removeFeature = document.createElement('td');

                    date.innerHTML = workOrder.attributes.DateField;
                    double.innerHTML = workOrder.attributes.DoubleField;
                    integer.innerHTML = workOrder.attributes.IntegerField;
                    text.innerHTML = workOrder.attributes.TextField1;

                    removeFeature.innerHTML = 'Delete?';
                    removeFeature.style.cursor = 'pointer';
                    removeFeature.addEventListener('click', () => {
                        self.deleteWorkOrder(feature, workOrder);
                    });

                    row.append(date, text, double, integer, removeFeature);
                    fragment.append(row);
                });

                featureWorkOrders.append(fragment);
            }
        });
    };
    updateWorkOrder = (feature) => {};
    deleteWorkOrder = (feature, workOrder) => {
        const self = this;
        const featureLayerURL = feature.layer.url;
        const workOrderLayerNumber = feature.layer.relationships[0].relatedTableId;
        const edits = {
            deleteFeatures: [workOrder],
        };

        if (this.state.workOrderFeatureLayer === null) {
            this.state.workOrderFeatureLayer = new FeatureLayer({
                url: `${featureLayerURL}/${workOrderLayerNumber}`,
            });
        }

        this.state.workOrderFeatureLayer
            .applyEdits(edits)
            .then((editsResult) => {
                self.readWorkOrder(feature);
            })
            .catch((error) => {
                console.log('error = ', error);
            });
    };

    // WIDGET CONTROLS
    openSignalsForm = (feature) => {    
        const editSignalButton = document.querySelectorAll('.esri-icon-edit')

        editSignalButton[0].addEventListener('click', () => {
            alert("hello world")
        });

        this.setState({
            feature: feature,
        });
        this.readAttachment(feature);
        this.readWorkOrder(feature);
        this.sidebarWidget();

        //open sidemenu here
    };


    activeViewChangeHandler = (jmv: JimuMapView) => {

        let self = this;

        if (jmv) {
            this.setState({
                jimuMapView: jmv,
            });

            jmv.view.map.layers.forEach((layer) => {
                if (layer.editingEnabled && layer.fields) {
                    let popupTemplate = new PopupTemplate({
                        // autocasts as new PopupTemplate()
                        title: '{Intersection1} @ {Intersection2}',
                        outFields: ['*'],
                        content: [
                            {
                                // add all the fields in the layer to the popup
                                // things could be filtered out here
                                // An alias could also be added to the popup
                                type: 'fields',
                                fieldInfos: layer.fields.map((field) => {
                                    return { fieldName: field.name, label: field.alias };
                                }),
                            },
                        ],
                    });

                    let openSignalsFormAction = {
                        title: `Edit ${layer.title}`,
                        id: 'open-signals-form',
                        className: 'esri-icon-edit',
                    };

                    popupTemplate.actions = [openSignalsFormAction];
                    layer.popupTemplate = popupTemplate;
                } else {
                }
            });

            // The function to execute when the zoom-out action is clicked

            // This event fires for each click on any action
            jmv.view.popup.on('trigger-action', function (event) {
                // If the zoom-out action is clicked, fire the zoomOut() function
                if (event.action.id === 'open-signals-form') {
                    let feature = jmv.view.popup.viewModel.selectedFeature;
                    self.openSignalsForm(feature);
                }
            });

            jmv.view.on('pointer-move', (evt) => {
                const point: Point = this.state.jimuMapView.view.toMap({
                    x: evt.x,
                    y: evt.y,
                });
                this.setState({
                    latitude: point.latitude.toFixed(3),
                    longitude: point.longitude.toFixed(3),
                });
            });
        }
    };
    onViewsCreateHandler = () => {
        console.log(this.state);
    };

    render() {
        return (
            <div className="widget-starter jimu-widget p-3 overflow-auto">
                {this.props.hasOwnProperty('useMapWidgetIds') &&
                    this.props.useMapWidgetIds &&
                    this.props.useMapWidgetIds[0] && (
                        <JimuMapViewComponent
                            useMapWidgetId={this.props.useMapWidgetIds?.[0]}
                            onActiveViewChange={this.activeViewChangeHandler}
                            onViewsCreate={this.onViewsCreateHandler}
                        />
                    )}
                <div className="content-widget-box py-2 m-0 row">
                    <div className=" col-6 col-sm-6 col-md-4 col-lg-4 divider">
                        <div className="icj-box pt-2">
                            <h1>Location Information</h1>
                            <div className="row">
                                <div className="col-md-6">
                                    <div className="w-100">
                                        <div className="">
                                            <span className="pr-2">ID</span>
                                            <TextInput value={this.state.feature.attributes.ID} size="sm" type="text" />
                                        </div>
                                    </div>
                                </div>
                                <div className="col-md-6">
                                    <div className="w-100">
                                        <div className="">
                                            <span className="pr-2">Cat.</span>
                                            <TextInput
                                                value={this.state.feature.attributes.SignalCategory}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                    </div>
                                </div>
                                <div className="col-md-12 pb-1">
                                    <div>
                                        <span className="pr-2">Jurisdiction</span>
                                        <TextInput
                                            value={this.state.feature.attributes.Jurisdiction}
                                            size="sm"
                                            type="text"
                                        />
                                    </div>
                                </div>
                                <div className="col-md-12">
                                    <div>
                                        <span className="pr-2">Hamlet</span>
                                        <TextInput
                                            id="work-order-description"
                                            value={this.state.feature.attributes.Hamlet}
                                            size="sm"
                                        />
                                    </div>
                                </div>
                                <div className="col-md-12">
                                    <div>
                                        <span className="pr-2">Intersection</span>
                                        <TextInput
                                            id="work-order-description"
                                            value={
                                                this.state.feature.attributes.Intersection1 +
                                                ' ' +
                                                this.state.feature.attributes.Intersection2
                                            }
                                            size="sm"
                                            className="mb-3"
                                        />
                                    </div>
                                </div>
                                <div className="col-md-6">
                                    <div className="">
                                        <span className="pr-2">Signal Number</span>
                                        <TextInput value={this.state.feature.attributes.SignalNumber} type="text" />
                                    </div>
                                </div>
                                <div className="col-md-6">
                                    <div className=" pb-1">
                                        <span className="pr-2">Type</span>
                                        <TextInput value={this.state.feature.attributes.SignalType} type="text" />
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div className="spd-box pt-2">
                            <div>
                                <div className="col-md-12 no-pad">
                                    <h1>Signal Information</h1>
                                    <div className=" pb-1">
                                        <div className="">
                                            <span className="pr-2">Support Type</span>
                                        </div>
                                        <div className="w-100">
                                            <TextInput
                                                value={this.state.feature.attributes.SupportType}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                    </div>
                                    <div className=" pb-1">
                                        <div className="">
                                            <span className="pr-2">Pole Type</span>
                                        </div>
                                        <div className="w-100">
                                            <TextInput
                                                value={this.state.feature.attributes.PoleType}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                    </div>
                                    <div className="pb-1">
                                        <div className="">
                                            <span className="pr-2">Det Type</span>
                                        </div>
                                        <div className="w-100">
                                            <TextInput
                                                value={this.state.feature.attributes.DetType}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                    </div>
                                    {/* <div className="row checkBox-row">
                                        <div className="col-md-6">
                                            <span>8" LED </span>
                                            <TextInput
                                                value={this.state.feature.attributes.F8LED}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                        <div className="col-md-6">
                                            <span>8" Incandescent </span>
                                            <TextInput
                                                value={this.state.feature.attributes.F8Incan}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                        <div className="col-md-6">
                                            <span>12" LED </span>
                                            <TextInput
                                                value={this.state.feature.attributes.F12LED}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                        <div className="col-md-6">
                                            <span>12" Incandescent </span>
                                            <TextInput
                                                value={this.state.feature.attributes.F12Incan}
                                                size="sm"
                                                type="text"
                                            />
                                        </div>
                                    </div> */}
                                </div>
                            </div>
                        </div>
                        <div className="acm-box">
                            <div>
                                <div>
                                    <span>Asset Cost</span>
                                    <TextInput value={this.state.feature.attributes.AssetCost} size="sm" type="text" />
                                </div>
                                <div>
                                    <span>Maintenance</span>
                                    <TextInput
                                        value={this.state.feature.attributes.Maintenance}
                                        size="sm"
                                        type="text"
                                    />
                                </div>
                            </div>
                        </div>
                        <div className="clp-box">
                            <div>
                                <div>
                                    <span>Contractor</span>
                                    <TextInput value={this.state.feature.attributes.Contractor} size="sm" type="text" />
                                </div>
                                <div>
                                    <span>Latest Build</span>
                                    <TextInput
                                        value={this.state.feature.attributes.LatestBuild}
                                        size="sm"
                                        type="text"
                                    />
                                </div>
                                <div>
                                    <span>LIPA Account</span>
                                    <TextInput
                                        value={this.state.feature.attributes.LipaAccount}
                                        size="sm"
                                        type="text"
                                    />
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="col-6 col-sm-6 col-md-8 col-lg-8">
                        <div className="attachment-box">
                            <div className="attachment-controls">
                                <h1 className="mt-3">Attachments</h1>
                                <Button
                                    type="primary"
                                    className="mr-3"
                                    onClick={this.createAttachment}
                                    onChange={this.handleChange}
                                >
                                    Add an attachment!
                                </Button>
                            </div>
                            <div className="img-box">
                                <img
                                    className="img-attatchment"
                                ></img>
                                <span className="pt-2"> 06/02/2022</span>
                            </div>
                        </div>
                        <Table className="my-3" size="sm" hover striped>
                            <thead>
                                <tr>
                                    <th scope="col">ID</th>
                                    <th scope="col">Name</th>
                                    <th scope="col">Content Type</th>
                                    <th scope="col">Size</th>
                                    {/* <th scope="col">Keywords</th> */}
                                    <th scope="col"></th>
                                </tr>
                            </thead>
                            <tbody id="featureAttachments"></tbody>
                        </Table>
                        <div className="row work-order">
                            <h1>Work Orders</h1>
                            <Button type="primary" onClick={this.createWorkOrder}>
                                Add Work Order
                            </Button>
                        </div>
                        <Table className="my-3" size="sm" hover striped>
                            <thead>
                                <tr>
                                    <th scope="col">Date</th>
                                    <th scope="col">Description</th>
                                    <th scope="col">Quantity</th>
                                    <th scope="col">Amount</th>
                                    <th scope="col"></th>
                                </tr>
                            </thead>
                            <tbody id="workOrderEntries"></tbody>
                        </Table>
                        <div className="widget-demo noteSections px-3">
                            <div className="email-notes">
                                <div className="row flex-cr">
                                    <div className="">
                                        <label className="w-100">
                                            <span>General Notes:</span>
                                            <TextArea
                                                className="mb-3 general-notes"
                                                value={this.state.feature.attributes.SignalNote}
                                            />
                                        </label>
                                    </div>

                                    <div className="">
                                        <label className="w-100">
                                            <span>Emails:</span>
                                            <TextArea
                                                className="mb-3 email-notes"
                                                value={this.state.feature.attributes.email}
                                            />
                                        </label>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );
    }
}
0 Kudos
1 Reply
LefterisKoumis
Regular Contributor II

You are using useeffect which it can be used only in functional components. Your code is a class component.

You cannot use hooks in class components. Either convert this script to a function component or use the class component but use the componentDidMount instead of useeffect.

0 Kudos