Hi, can I somehow collapse and expand resources smoothly at the click of a button?
Hello,
You can try to achieve this by applying CSS transitions to the height property:
.smooth-transition {
transition: height 0.5s ease-in-out;
}
gantt.$root.querySelector(".collapse-button").onclick = function() {
const newHeight = 0;
const container = gantt.$root.querySelectorAll(".gantt_layout_cell")[1];
container.classList.add("smooth-transition");
container.style.height = newHeight + "px";
};
Here is an example: DHTMLX Snippet Tool . Please note that this example requires further development and testing.
Hello, is there a possibility that the height of the resource table will always be according to the amount of content, without scrolling, and the rest of the space will already be occupied by a gantt chart?
Hello,
There is no built-in solution for this, but you can achieve it manually. Here’s one way to do it:
gantt.attachEvent("onGanttReady", () => {
const resourcesStore = gantt.getDatastore("resource").pull;
gantt.config.layout.rows[2].height = Object.keys(resourcesStore).length * 36 + gantt.config.scale_height + 1;
gantt.resetLayout();
});
Here’s a working example: DHTMLX Snippet Tool
thanks for response, but only by default, my first-level rows are expanded, and the deeper-level rows are collapsed, also in your example, if you collapse a row, there is empty space at the bottom, and the resource table is on top, also when opening and closing rows, the height should change
Hello,
I’ve slightly improved the solution. Now it tracks when a resource is collapsed, recalculates the height, and updates the resource view accordingly:
gantt.attachEvent("onGanttReady", () => {
updateResourceHeight();
});
function updateResourceHeight() {
const resourcesStore = gantt.getDatastore("resource").pull;
let visibleResources = 0;
// Iterate through all resources and count only visible ones
for (let id in resourcesStore) {
let resource = resourcesStore[id];
if (isParentVisible(resource)) {
visibleResources++;
}
}
// Calculate the new height based on the number of visible resources
const newHeight = visibleResources * 36 + gantt.config.scale_height + 1;
gantt.config.layout.rows[2].height = newHeight;
gantt.resetLayout();
}
function isParentVisible(resource) {
if (!resource.parent) return true;
let parent = gantt.getDatastore("resource").getItem(resource.parent);
if (parent && !parent.$open) {
return false;
}
return isParentVisible(parent);
}
gantt.attachEvent("onEmptyClick", (e) => {
if (e.target.closest('.gantt_tree_icon.gantt_close')) {
let parent = e.target.closest('.resourceGrid_cell');
if (parent) {
gantt.message(`Update height`);
setTimeout(() => {
updateResourceHeight();
}, 0);
}
}
});
Here is an example: DHTMLX Snippet Tool . Please note that this example is not complete, and you will need to refine the height recalculation logic when expanding a resource.
Hello, I have a question: my resource table is on top, and I made it collapse and expand, how can I make the gantt itself stretch to the entire remaining height when the resource table is collapsed and expanded?
Here my function
export const calculateLayout = (gantt: GanttStatic, isCollapse: boolean) => {
const ganttContainer = document.querySelector(GANTT_CONTAINER_SELECTOR) as HTMLElement;
if (!ganttContainer) return;
const resourceTable = ganttContainer.querySelector(RESOURCE_TABLE_SELECTOR) as HTMLElement;
const mainGantt = ganttContainer.querySelector(GANTT_SELECTOR) as HTMLElement;
if (!resourceTable || !mainGantt) return;
const ganttContainerHeight = ganttContainer.offsetHeight;
const resourseTableHeight = resourceTable.offsetHeight;
const newGanttHeight = ganttContainerHeight - resourseTableHeight;
// mainGantt.style.height = `${newGanttHeight}px`;
gantt.config.layout.rows[2].height = newGanttHeight;
//gantt.render()// doesnt work
//gantt.resetLayout // doesnt work
//gantt.setSizes // doesnt work
};
UPD:
I have already tried many options, but nothing worked, I want to achieve the ability to collapse and expand the resource table (collapse not completely, but to the first line) with animation (but although it can be done without), and the gantt chart itself should occupy the free space of the container. In the video, I achieved collapsing and expanding with animation, but I used your first implementation option together with the second) but the problem is that when calling gantt.render() in any place, everything is reset.
So I have two problems:
- Make the gantt take up all the remaining space in the container when collapsing and expanding
- So that nothing is reset when gantt.render()
it’s my collapse code
const isParentVisible = (gantt: GanttStatic, resource: ResoureParent) => {
if (!resource.parent) return true;
const parent = gantt.getDatastore('resource').getItem(resource.parent) as ResoureParent;
if (parent && !parent.$open) {
return false;
}
return isParentVisible(gantt, parent);
};
const getVisibleResource = (gantt: GanttStatic) => {
const resourcesStore = gantt.getDatastore('resource') as unknown as ResourceStore;
const pull = resourcesStore.pull;
let visibleResources = 0;
for (let id in pull) {
let resource = pull[id];
if (!resource) continue;
if (isParentVisible(gantt, resource)) {
visibleResources++;
}
}
return visibleResources;
};
const updateResourceHeight = (gantt: GanttStatic, isCollapse: boolean) => {
const rowHeight = getResourceHeight(gantt);
const visibleResources = getVisibleResource(gantt);
const container = gantt.$root.querySelector('.resources_grid') as HTMLElement;
if (container) {
const newHeight = visibleResources * rowHeight;
container.style.height = isCollapse ? `${rowHeight + 1}px` : `${newHeight}px`;
}
};
export const collapse = (gantt: GanttStatic, isCollapse: boolean, skipAnimation = false) => {
const container = gantt.$root.querySelector('.resources_grid') as HTMLElement;
if (!container) return;
requestAnimationFrame(() => {
if (skipAnimation) {
container.classList.remove('smooth-transition');
} else {
container.classList.add('smooth-transition');
}
requestAnimationFrame(() => {
updateResourceHeight(gantt, isCollapse);
});
});
};
Hello,
Without animation, this can be achieved by changing the height of the bottom view:
gantt.$root.querySelector(".collapse-button").onclick = () => {
const ganttContainer = gantt.$root;
const ganttContainerwHeight = ganttContainer.clientHeight;
const topContentHeight = gantt.config.scale_height + gantt.config.row_height;
gantt.config.layout.rows[0].height = topContentHeight;
gantt.config.layout.rows[3].height = ganttContainerwHeight - topContentHeight;
gantt.resetLayout();
};
Please see an example: DHTMLX Snippet Tool
With animation, it will be a more complex solution. You can try to implement it by setting the height of the container via CSS style, but there is a difficulty with adapting the height of internal containers for tasks in the grid and timeline, as well as the vertical scrollbar.
i use react, but when i execute gantt.resetLayout() i got this error
NotFoundError: Failed to execute ‘removeChild’ on ‘Node’: The node to be removed is not a child of this node.
Hello,
Unfortunately, it’s hard to determine the problem without seeing the full code and configuration. Could you prepare a demo that reproduces the issue and send it over?
Sure, Lets’s start from code:
The first my BaseGantt where i init gantt
export const GanttBaseComponent = <
TGanttTask extends GanttTask,
TGanttLink extends GanttLink,
TGanttResource extends GanttResource,
>({
gantt,
date,
ganttViewModel,
activePeriod,
initialTemplates,
initialGanttConfig,
layout,
data,
}: GantBodyProps<TGanttTask, TGanttLink, TGanttResource>) => {
const { config, templates } = ganttViewModel;
const container = useRef<Nullable<HTMLDivElement>>(null);
useEffect(() => {
if (container?.current) {
gantt.plugins({
multiselect: true,
keyboard_navigation: true,
export_api: true,
resource_grid: true,
tooltip: true,
});
gantt.config = { ...gantt.config, ...initialGanttConfig, ...config };
gantt.templates = { ...gantt.templates, ...initialTemplates, ...templates };
gantt.i18n.setLocale(gantLocalization);
gantt.config.layout = layout;
gantt.init(container.current);
gantt.setSizes();
}
return () => {
console.log('unmount');
gantt.destructor();
gantt.detachAllEvents();
};
}, [container]);
useEffect(() => {
gantt.parse(data);
}, [data]);
useAddPastLayer(container, gantt);
useChangePeriod(gantt, activePeriod);
useChangeTime(gantt, date);
useCustomClass(gantt, activePeriod);
useCustomTooltip(gantt);
return <div id="gantt" ref={container} className={styles.wrapper} />;
};
here is the gantt I use
export const GanttCell = observer(
<TGanttTask extends GanttTask, TGanttLink extends GanttLink, TGanttResource extends GanttResource>({
data,
links,
ganttViewModel,
resources,
}: GanttCellProps<TGanttTask, TGanttLink, TGanttResource>) => {
const { resourcesConfig } = ganttViewModel;
const { gantt } = useGetGanttInstance();
const { date, activePeriod, handleClickFilter } = useGetGanttControllerParams();
const ganttData = useMemo(() => ({ data, links, resources }), [data, links, resources]);
const initialTemplates = getInitialTemplates(gantt);
useCustomizationResources(gantt);
useStrokeFirstElement({ gantt });
const layout = getLayout({ resourcesConfig });
return (
<>
<div className={styles.gantt}>
<GanttBase
data={ganttData}
activePeriod={activePeriod}
date={date}
gantt={gantt}
ganttViewModel={ganttViewModel}
initialTemplates={initialTemplates}
initialGanttConfig={initialGanttConfig}
layout={layout}
/>
<div className={styles.scroll} />
</div>
<GanttSettings handleClickFilter={handleClickFilter} gantt={gantt} />
<Collapser gantt={gantt} />
</>
);
},
);
I create gantt itself in the context like this so that it is available in all other components
export const GanttContextProvider: FC<GanttContextProvider> = ({ children, ganttViewModel }) => {
const gantt = GanttDhtmlx.getGanttInstance({});
const ganttValue = useMemo(() => ({ gantt }), [gantt]);
return <GanttContext value={ganttValue}>{children}</GanttContext>;
};
And my button for collapse:
export const Collapser: FC<CollapserProps> = ({ gantt }) => {
const { isCollapse, toggleCollapse } = useGetGanttControllerParams();
const isFirstRenderCollapse = useRef<boolean>(false);
const Icon = isCollapse ? ChevronDownIcon : ChevronUpIcon;
const { top, left } = useResizeObserverButton(gantt, isCollapse);
useEffect(() => {
if (isFirstRenderCollapse.current) {
collapse(gantt, isCollapse);
}
isFirstRenderCollapse.current = true;
}, [isCollapse, isFirstRenderCollapse]);
if (!gantt.$container) return null;
return createPortal(
<div role="button" onClick={toggleCollapse} style={{ top, left }} className={styles.button}>
<Icon width={8} height={8} className={styles.icon} />
</div>,
gantt.$container,
);
};
and my function collapse:
const isParentVisible = (gantt: GanttStatic, resource: ResoureParent) => {
if (!resource.parent) return true;
const parent = gantt.getDatastore('resource').getItem(resource.parent) as ResoureParent;
if (parent && !parent.$open) {
return false;
}
return isParentVisible(gantt, parent);
};
const getVisibleResource = (gantt: GanttStatic) => {
const resourcesStore = gantt.getDatastore('resource') as unknown as ResourceStore;
const pull = resourcesStore.pull;
let visibleResources = 0;
for (let id in pull) {
let resource = pull[id];
if (!resource) continue;
if (isParentVisible(gantt, resource)) {
visibleResources++;
}
}
return visibleResources;
};
const updateResourceHeight = (gantt: GanttStatic, isCollapse: boolean) => {
const rowHeight = getResourceHeight(gantt);
const visibleResources = getVisibleResource(gantt);
const newHeight = visibleResources * rowHeight;
gantt.config.layout.rows[0].height = isCollapse ? rowHeight + 1 : newHeight;
gantt.resetLayout();
};
export const collapse = (gantt: GanttStatic, isCollapse: boolean, skipAnimation = false) => {
updateResourceHeight(gantt, isCollapse);
};
here demo what happened
error:
console error:
also i have there is the topmost component
export const GanttFactory = observer(
<TGanttTask extends GanttTask, TGanttLink extends GanttLink, TGanttResource extends GanttResource>({
type,
...props
}: GanttProps<TGanttTask, TGanttLink, TGanttResource>) => {
switch (type) {
case GanttType.CELL: {
return <GanttCell type={type} {...props} />;
}
default: {
return null;
}
}
},
);
export const Gantt = observer(
<TGanttTask extends GanttTask, TGanttLink extends GanttLink, TGanttResource extends GanttResource>({
type,
...props
}: GanttProps<TGanttTask, TGanttLink, TGanttResource>) => {
return (
<GanttContextProvider ganttViewModel={props.ganttViewModel}>
<GanttControllerContextProvider>
<GanttLayout headerProps={props.headerProps}>
<GanttFactory type={type} {...props} />
</GanttLayout>
</GanttControllerContextProvider>
</GanttContextProvider>
);
},
);
also my GanttControllerContextProvider
export const useGanttController = () => {
const zoomRef = useRef<Nullable<HTMLInputElement>>(null);
const [isCollapse, setIsCollapse] = useState<boolean>(true);
const [date, setDate] = useState<Dayjs>(dayjs());
const [activePeriod, setActivePeriod] = useState<GanttPeriod>(GanttPeriod.MONTH);
const handleChangeActivePeriod = useCallback((type: GanttPeriod) => {
setActivePeriod(type);
}, []);
const onChnageDate = useCallback((_date: Dayjs) => {
setDate(_date);
}, []);
const toggleCollapse = () => {
setIsCollapse((prev) => !prev);
};
const handleClickFilter = useCallback((filter: ControlPanelType) => {
console.log(filter);
}, []);
return {
date,
activePeriod,
handleChangeActivePeriod,
handleClickFilter,
onChnageDate,
zoomRef,
toggleCollapse,
isCollapse,
};
export const GanttControllerContextProvider: FC<GanttControllerContextProviderProps> = ({ children }) => {
const controller = useGanttController();
return <GanttControllerContext value={controller}>{children}</GanttControllerContext>;
};
};
before the first gantt.reset Layout() is executed, everything works well, after the first gantt.resetLayout() I get an error when I try to change some state from the context
Most likely the problem is due to the fact that gantt.resetLayout() causes changes in the DOM, and after I want to change the state from the context, react does not understand how to render since the dom has been changed, but I don’t know how to fix it
Hello,
Thank you for sharing your configuration. I’ll check the code and try to find the issue. I need a bit more time for this.
Do you maybe have a ready-to-use demo that I could run on my side? It could help speed things up.
Unfortunately I can’t provide a demo, but the error is that after gantt.resetLayout(), the DOM is rebuilt, and after I want to change the state using react, an error occurs, since gantt.resetLayout() changed the dom
Also, this method with gantt.resetLayout resets the scroll state, all layers and custom solutions
Hello,
Regrettably, I haven’t been able to reproduce the issue on my side. If you can prepare a minimal test application, something like this: react-gantt-resetLayout.zip, that reproduces the problem and send it to me, it would be very helpful.