Help with VueFlow + Custom Node Layout in WeWeb

Hi everyone,

I’m trying to create a coded component to integrate VueFlow with a shared node layout in WeWeb. I want each node to be editable, but I can’t figure out how to correctly pass the wwObject data so it renders properly.

Here’s a minimal example of what I have so far:

<template>
    <div class="vueflow-container">
        <VueFlow
            v-model:nodes="nodes"
            v-model:edges="edges"
            :fit-view-on-init="true"
        >
            <template #node-custom="{ data }">
                <div class="node-wrapper">
                    <wwLayout
                        path="nodeTemplate"
                        class="node-content"

                    >
                        <template #default="{ item }">
                            <wwLayoutItem>
                                <wwObject v-bind="item" />
                            </wwLayoutItem>
                        </template>
                    </wwLayout>
                </div>
            </template>
        </VueFlow>
    </div>
</template>

<script>
import { computed } from 'vue';
import { VueFlow } from '@vue-flow/core';

import '@vue-flow/core/dist/style.css';
import '@vue-flow/core/dist/theme-default.css';

export default {
    name: 'VueFlowWrapper',
    components: {
        VueFlow,
    },
    emits: ['update:content'],
    props: {
        content: {
            type: Object,
            required: true,
        },
    },
    methods: {
        update(event) {
            // Gérer les événements de mise à jour de la liste
        },
    },
    setup(props) {
        const nodes = computed(() => {
            const wwObjects = props.content?.wwObjects;
            if (!Array.isArray(wwObjects) || wwObjects.length === 0) {
                return [];
            }

            return wwObjects.map((entry, index) => {
                const payload = entry?.data ?? entry;
                return {
                    id: payload?.id ?? entry?.uid ?? `node-${index}`,
                    type: 'custom',
                    position: payload?.position ?? { x: index * 160, y: 0 },
                    data: {
                        label: payload?.label ?? `Node ${index + 1}`,
                        index,
                        wwObject: entry, // Passe l'objet complet
                    },
                };
            });
        });

        const edges = computed(() => {
            const links = props.content?.connections;
            if (!Array.isArray(links) || links.length === 0) {
                return [];
            }

            return links
                .filter(link => link?.source && link?.target)
                .map((link, index) => ({
                    id: link.id ?? `edge-${index}`,
                    source: link.source,
                    target: link.target,
                }));
        });

        return {
            nodes,
            edges,
            content: props.content,
        };
    },
};
</script>

I’d like each node to use the same layout, but with different props, and remain editable in WeWeb. Any tips on how to properly bind the wwObject data would be really helpful.

Thanks,

Victor

I’m also looking at vue flow.

Very interested to follow the topic and know how you’ve implemented it and how it renders

What do you mean by same layout and different props? You mean the context of the data being different, but editing one edits all of them?

Thank you for your answers. Yes that was it @Broberto

@Matt-apollodev here is how I handled it :

<template>
    <div class="vueflow-container">
        <VueFlow
            v-model:nodes="nodes"
            v-model:edges="edges"
            :fit-view-on-init="true"
        >
            <template #node-custom="{ data }">
                <div class="node-wrapper">
                    <wwLocalContext 
                            elementKey="tab" 
                            :data="data.wwObject"
                        >
                    <wwLayout
                        path="nodeTemplate"
                        class="node-content"

                    >
                        <template #default="{ item }">
                            <wwLayoutItem>
                                <wwObject v-bind="item" />
                            </wwLayoutItem>
                        </template>
                    </wwLayout>
                    </wwLocalContext>
                </div>
            </template>
        </VueFlow>
    </div>
</template>

<script>
import { computed } from 'vue';
import { VueFlow } from '@vue-flow/core';

import '@vue-flow/core/dist/style.css';
import '@vue-flow/core/dist/theme-default.css';

export default {
    name: 'VueFlowWrapper',
    components: {
        VueFlow,
    },
    emits: ['update:content'],
    props: {
        content: {
            type: Object,
            required: true,
        },
    },
    methods: {
        update(event) {
            // Gérer les événements de mise à jour de la liste
        },
    },
    setup(props) {
        const nodes = computed(() => {
            const wwObjects = props.content?.wwObjects;
            if (!Array.isArray(wwObjects) || wwObjects.length === 0) {
                return [];
            }

            return wwObjects.map((entry, index) => {
                const payload = entry?.data ?? entry;
                return {
                    id: payload?.id ?? entry?.uid ?? `node-${index}`,
                    type: 'custom',
                    position: payload?.position ?? { x: index * 160, y: 0 },
                    data: {
                        label: payload?.label ?? `Node ${index + 1}`,
                        index,
                        wwObject: entry, // Passe l'objet complet
                    },
                };
            });
        });

        const edges = computed(() => {
            const links = props.content?.connections;
            if (!Array.isArray(links) || links.length === 0) {
                return [];
            }

            return links
                .filter(link => link?.source && link?.target)
                .map((link, index) => ({
                    id: link.id ?? `edge-${index}`,
                    source: link.source,
                    target: link.target,
                }));
        });

        return {
            nodes,
            edges,
            content: props.content,
        };
    },
};
</script>

<style scoped>
.vueflow-container {
    width: 100%;
    height: 600px;
    min-height: 400px;
}

.node-wrapper {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: stretch;
    justify-content: stretch;
}

.node-slot {
    width: 100%;
    height: 100%;
}
</style>

I hope this may help you !

2 Likes