bumbeishvili / org-chart

Highly customizable org chart. Integrations available for Angular, React, Vue
https://stackblitz.com/edit/web-platform-o5t1ha
MIT License
918 stars 328 forks source link

How to add on click event handler to children inside nodeContent #131

Closed pradshan closed 2 years ago

pradshan commented 2 years ago

Before submitting an issue, please make sure to check whether the similar question was asked before. You can search for it here - https://github.com/bumbeishvili/d3-organization-chart/issues Hello,

Currently I see if I click on the nodeContent any where the .nodeClick() gets triggered. I have a couple of questions here:

1). I have a badge inside my nodeContent which I try to click. When I click on that badge I should trigger the onclick event handler associated with that badge. If I click outside of the badge but inside the nodeContent area the nodeClick event should trigger. I see you have a nodeClick and buttonClick. I am looking at something similar to it.

I want my handleClick callback to get triggered whenever I click on the Badge which is inside the nodeContent.

Removed unwanted code. This is just a snippet

const handleClick = (event: React.MouseEvent<HTMLElement>, text: string) => {
    console.log(event.target);
    console.log(text);
};

chart
.nodeContent(function (d: any, i, arr, state) {
    return ReactDOMServer.renderToStaticMarkup(
        <div style={nodeStyling}>
             <div style={nodeReporteeStyling}>
                 <Badge badgeContent={d.data.reporteeCount} onClick={(e) => handleClick(e, "clicked")} color="primary">
                     <PeopleIcon/>
                  </Badge>
             </div>
             <div style={nodeCenterContentStyling}>
                  <div style={nodeNameStyling}> {d.data.displayFeName}</div>
                  <div style={nodePositionNameStyling}> {d.data.displayFeLabel || ''} </div>
                  <br/>
                  <div style={nodeLineStyling}> </div>
             </div>
        </div>
    )
}

2). I am using Typescript & React framework. I noticed that .nodeClick is accepting only one argument and it just gives the id of the node that is clicked. However in some of the stackblitz code I see it accepts more arguments for .onNodeClick. I am looking to get the content of the node inside .onNodeClick function but the only argument that is getting passed is the ID of the node that has been clicked. Not sure why is this happening. I want the entire nodeContent inside this function like we see in the .nodeUpdate function. Am i missing anything here?

.onNodeClick((d, i, arr) => {
    console.log(d, 'Id of clicked node ');
    props.onNodeClick(d);
})

but in the index.d.ts file I see the following :

onNodeClick(): (node: HierarchyNode<Datum>) => void;
usmanaslam1555 commented 2 years ago

https://github.com/bumbeishvili/org-chart/issues/94 same issue here, facing the same issue as you and found this in closed so here u go

pradshan commented 2 years ago

Thanks for your answering to the query. I am able to get the event to trigger when I click on the Badge now. However I am unable to get the Dialog (Modal) to pop up with my content, Instead I get a warning message in the console.

When I have this

component outside of the nodeConent anywhere in the webpage I am able to get the Dialog to open and display the content that I intend to show, however when I place the same component inside the .nodeContent and try to handle the open event via nodeUpdate it shows me this warning below and does NOT open the Dialog. Not sure what am I missing here!!

I have removed the extra code and given only the snip of the code which would be relevant to this question. Any assistance would be of great help.

Warning Snip:

Clicked on this button with event [object PointerEvent] data data,height,depth,parent,id,children,width,x0,y0,firstCompact,compactEven,flexCompactDim,firstCompactNode,x,y
Clicked on this button with event [object PointerEvent] data [object Object],150,0,,ST-33,[object Object],[object Object],[object Object],[object Object],375,0,0,,,,,0,0
Clicked on this button with event [object PointerEvent] data ST-33
Clicked on this button with event [object PointerEvent] data 12
 inside handleClick
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://reactjs.org/link/uselayouteffect-ssr for common fixes.
    at ButtonBase (http://127.0.0.1:3000/static/js/bundle.js:8585:22)
    at WithStyles (http://127.0.0.1:3000/static/js/bundle.js:25770:31)
    at Button (http://127.0.0.1:3000/static/js/bundle.js:8342:24)
    at WithStyles (http://127.0.0.1:3000/static/js/bundle.js:25770:31)
    at div
    at FullScreenDialog (http://127.0.0.1:3000/static/js/bundle.js:2626:19)
    at div
    at div
    at div

Code Snip:

import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import IconButton from '@material-ui/core/IconButton';
import CloseIcon from '@material-ui/icons/Close';
import { TransitionProps  } from '@material-ui/core/transitions';
import Slide from '@material-ui/core/Slide';
import Button from '@material-ui/core/Button';

export const InternalDirectoryStructure = (props: InternalStructProps) => {
  const d3Container = useRef(null);
  //let chart = null;
  let [chart, setChart] = React.useState(new OrgChart<UserData>());
  const handleClick = (d: any) => {
      console.log("inside handleClick");
      setOpen(true);
  };

  const handleClose = () => {
      setOpen(false);
  };

  const [open, setOpen] = React.useState(false);
   chart
    .nodeContent(function (d: any, i, arr, state) {
            let nodeColor = '#904C3F';

            const nodeStyling: CSS.Properties = {
                fontFamily : 'arial',
                backgroundColor : nodeColor,
                position:   'absolute',
                marginTop : '-1px',
                marginLeft: '-1px',
                width: `${d.width}px`,
                height: `${d.height}px`,
                borderRadius: '10px',
            }
            const nodeReporteeStyling: CSS.Properties = {
                color: '#ffffff',
                marginRight:   '30px',
                marginTop: '15px',
                textAlign: 'right',
            }
            const nodeCenterContentStyling: CSS.Properties = {
                padding: '20px',
                paddingTop: '5px',
                textAlign: 'center',
            }
            const nodeNameStyling: CSS.Properties = {
                color: '#ffffff',
                fontSize: '24px',
                fontWeight: 'bold',
            }
            const nodePositionNameStyling: CSS.Properties = {
                color: '#ffffff',
                fontSize: '24px',
                marginTop : '4px',
            }
            const nodeLineStyling: CSS.Properties = {
                borderBottom : '1px solid white',
            }

                return ReactDOMServer.renderToStaticMarkup(
                    <div style={nodeStyling}>
                        <div style={nodeReporteeStyling}>
                            <Badge className={'reportees'} badgeContent={d.data.reporteeCount} color="primary">
                                <PeopleIcon/>
                            </Badge>
                            <Dialog fullScreen open={open} onClose={handleClose} TransitionComponent={Transition}>
                                <IconButton edge="start" color="inherit" onClick={handleClose} aria-label="close">
                                    <CloseIcon />
                                </IconButton>
                                <DialogContent>
                                    Hello
                                </DialogContent>
                            </Dialog>
                        </div>

                        <div style={nodeCenterContentStyling}>
                            <div style={nodeNameStyling}> {d.data.displayFeName}</div>
                            <div style={nodePositionNameStyling}> {d.data.displayFeLabel || ''} </div>
                            <br/>
                            <div style={nodeLineStyling}> </div>
                        </div>
                    </div>
                )
        })
         .nodeUpdate(function (d: any) {
            select(this)
            .select('.node-rect')
            .attr('stroke', (d: any) =>
              d.data._highlighted || d.data._upToTheRootHighlighted
                ? '#FFFFFF'
                : 'none'
            )
            .attr(
              'stroke-width',
              d.data._highlighted || d.data._upToTheRootHighlighted ? 15 : 1
            );
            select(this).on('click.node-update',(event,data: any)=>{
                  if ([...event.srcElement.classList].includes("node-button-foreign-object")) {
                       chart.clearHighlighting();
                       chart.setUpToTheRootHighlighted(data.id).render();
                  }
            });
            select(this)
            .select('.reportees').on('click',(event,d: any)=>{
                console.log(`Clicked on this button with event ${event} data ${Object.keys(d)}`)
                console.log(`Clicked on this button with event ${event} data ${Object.values(d)}`)
                console.log(`Clicked on this button with event ${event} data ${d.id}`)
                console.log(`Clicked on this button with event ${event} data ${d.data.reporteeCount}`)
                handleClick(d)
            })
        })
        .onNodeClick(function (d:any) {
            chart.clearHighlighting();
            chart.setUpToTheRootHighlighted(d).render();
            //If Node has some reportee count then we need to display the reportee contact
            let nodeData:UserData =  props.data.find((obj:UserData) => obj.id === d);
        })
       .render().fit();

        select(d3Container.current).style(
          'background-color', '#333333',
        )
}, [props.data, d3Container.current]);
pradshan commented 2 years ago

Thanks for your support. I got it to work after referring to how to place Dialogs inside the nodeContent by referring to this https://github.com/bumbeishvili/org-chart/issues/125 , now I can see the popups as well as the the warnings have been suppressed with some help from stackoverflow

marcellino-ornelas commented 1 year ago

Want to use more react and less d3? check out this stackblitz link

https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-2chluf?file=index.js,OrgChart.js

Instead of striping everything react away in the chart (React.renderToString) we were able to use this org chart but keep all react functionality by drawing the graph and linking react components to it by using createPortal

Now you get the benefits of using this library to draw the graph but get all the benefits of react. We've been using this route for about 2 months now and have not run into any issues. We do use the basics of this graph (just draw the graph and thats it) although we do plan to implement some other functionality this library provides (centering, expanding, etc)

Shadowfita commented 1 year ago

Want to use more react and less d3? check out this stackblitz link

https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-2chluf?file=index.js,OrgChart.js

Instead of striping everything react away in the chart (React.renderToString) we were able to use this org chart but keep all react functionality by drawing the graph and linking react components to it by using createPortal

Now you get the benefits of using this library to draw the graph but get all the benefits of react. We've been using this route for about 2 months now and have not run into any issues. We do use the basics of this graph (just draw the graph and thats it) although we do plan to implement some other functionality this library provides (centering, expanding, etc)

You are a legend. I was looking for this exact solution, thank you!

Just need to sort out the react elements disappearing once the chart updates, haha.

marcellino-ornelas commented 1 year ago

@Shadowfita

I haven't tried updating the graph yet, in our modal we just build the graph and let react take care of the rest šŸ˜ƒ. I can see a need for updating the graph if you adding nodes, maybe highlighting lines or using other functionality this library provides, or etc but in our case we didn't need to do this. If you want to get a stackblitz up and running similar to your use case I would be happy to play around with it and see if I can get something working

Shadowfita commented 1 year ago

@Shadowfita

I haven't tried updating the graph yet quick yet, in our modal we just build the graph and let react take care of the rest šŸ˜ƒ. I can see a need for updating the graph if you adding nodes, maybe highlighting lines or using other functionality this library provides, or etc but in our case we didn't need to do this. If you want to get a stackblitz up and running similar to your use case I would be happy to play around with it and see if I can get something working

I've been able to resolve my issue with a hacky workaround.

With this force update function

const [, forceUpdate] = useReducer((x) => x + 1, 0);

called by

chart.nodeUpdate

with a check to see if it's the top level node to prevent 35 refreshes depending on how many nodes are present on screen.

I'm assuming I was running into an issue with the chart re-rendering, but the rest of the react page not, and reactjs not seeing any reason to re-render my portals because their state shouldn't require a re-render.

bry221996 commented 1 year ago

Want to use more react and less d3? check out this stackblitz link https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-2chluf?file=index.js,OrgChart.js Instead of striping everything react away in the chart (React.renderToString) we were able to use this org chart but keep all react functionality by drawing the graph and linking react components to it by using createPortal Now you get the benefits of using this library to draw the graph but get all the benefits of react. We've been using this route for about 2 months now and have not run into any issues. We do use the basics of this graph (just draw the graph and thats it) although we do plan to implement some other functionality this library provides (centering, expanding, etc)

You are a legend. I was looking for this exact solution, thank you!

Just need to sort out the react elements disappearing once the chart updates, haha.

Hi . are you able to fixed the disappearing of elements when the charts updates ?

Shadowfita commented 1 year ago

Want to use more react and less d3? check out this stackblitz link https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-2chluf?file=index.js,OrgChart.js Instead of striping everything react away in the chart (React.renderToString) we were able to use this org chart but keep all react functionality by drawing the graph and linking react components to it by using createPortal Now you get the benefits of using this library to draw the graph but get all the benefits of react. We've been using this route for about 2 months now and have not run into any issues. We do use the basics of this graph (just draw the graph and thats it) although we do plan to implement some other functionality this library provides (centering, expanding, etc)

You are a legend. I was looking for this exact solution, thank you! Just need to sort out the react elements disappearing once the chart updates, haha.

Hi . are you able to fixed the disappearing of elements when the charts updates ?

Hi @bry221996 , my comment above addresses that issue. It may be a bit hacky for your liking, but worked for me with a fairly large chart.

marcellino-ornelas commented 1 year ago

@Shadowfita @bry221996 I was able to reproduce the following and have a somewhat solution for it

Check it out https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-hdgvst?file=index.js,OrgChart.js,Counter.tsx

I removed the old chart.nodeUpdate from the code because when using react your probably gonna be using state and updating state vs updating only the chart itself. This also takes away the need to add a react update in the nodeUpdate function

Your now able to add more nodes to the chart and preserve there presence. However, the only thing I wasn't able to figure out yet is why child components cant hold there own state. If you check out the stackblitz link you can press the count button and your count will increase. however, once you add a new node, the count gets reset back to 0. I think this may have something to do with the chart re-rendering causing new HTML elements to be created. which is causing the portal to losing its connection the old component so it just creates a new version with the default state back.

Expanding falls victim to this as well, haven't looked to much into it yet tho

Splashx0 commented 1 year ago

@Shadowfita @bry221996 I was able to reproduce the following and have a somewhat solution for it

Check it out https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-hdgvst?file=index.js,OrgChart.js,Counter.tsx

I removed the old chart.nodeUpdate from the code because when using react your probably gonna be using state and updating state vs updating only the chart itself. This also takes away the need to add a react update in the nodeUpdate function

Your now able to add more nodes to the chart and preserve there presence. However, the only thing I wasn't able to figure out yet is why child components cant hold there own state. If you check out the stackblitz link you can press the count button and your count will increase. however, once you add a new node, the count gets reset back to 0. I think this may have something to do with the chart re-rendering causing new HTML elements to be created. which is causing the portal to losing its connection the old component so it just creates a new version with the default state back.

Expanding falls victim to this as well, haven't looked to much into it yet tho

hello bro please how can i add a new node from a user input form , i have a problem like issue #125 pls can you help me!

Splashx0 commented 1 year ago

@Shadowfita @bry221996 I was able to reproduce the following and have a somewhat solution for it

Check it out https://stackblitz.com/edit/d3-org-chart-react-integration-hooks-hdgvst?file=index.js,OrgChart.js,Counter.tsx

I removed the old chart.nodeUpdate from the code because when using react your probably gonna be using state and updating state vs updating only the chart itself. This also takes away the need to add a react update in the nodeUpdate function

Your now able to add more nodes to the chart and preserve there presence. However, the only thing I wasn't able to figure out yet is why child components cant hold there own state. If you check out the stackblitz link you can press the count button and your count will increase. however, once you add a new node, the count gets reset back to 0. I think this may have something to do with the chart re-rendering causing new HTML elements to be created. which is causing the portal to losing its connection the old component so it just creates a new version with the default state back.

Expanding falls victim to this as well, haven't looked to much into it yet tho

hello bro please how can i add a new node from a user input form , i have a problem like issue #125 pls can you help me!

marcellino-ornelas commented 1 year ago

@Splashx0 can you reproduce in stackblitz or JS fiddle?

Standin-Alone commented 1 year ago

where did you get the "select" code?