Convergence with Vue and Syncfusion

Hi! I am referencing the mxgraph demo code to replicate the diagram component from Syncfusion in vuejs. But i am facing some issues with the reactivity data.

Basically, vue appends it’s own methods to an object that i am trying to send to convergence. This is not ideal and it results in some error. I can convert the data to a pure javascipt object, but the linkage between the model on the convergence server will be delinked from the data that is being rendered on syncfusion and i will have to be able to merge the changes from the remote data from the server with the local data.

Is there a good way of doing this? I would appreciate it if there are some comments on how the mxgraph integration is done. Thanks alot!

Hi Bryan, hard to diagnose this sort of thing without looking at code, but it sounds like a problem with proper separation of concerns. You wouldn’t want to mirror the actual component but rather just the data that describes the current state of the component. Of course, you’d have to share some code for me to provide more specific guidance.

I’ve never used Syncfusion but we have built apps with Convergence and Vue and found them to work well together.

Hi Alec,

Thanks for replying!

This is the code that i have under App.vue, all these is testing code so the logic to load the data from my own db is not in yet.

<template>
  <div id="app">
    <svg
      :width="width"
      :height="height"
      id="pointer_canvas"
      style="position:absolute; top:0;"
    ></svg>
    <ejs-diagram
      id="diagram"
      :width="width"
      :height="height"
      :nodes="graphData.nodes"
      :connectors="graphData.connectors"
    ></ejs-diagram>
    <presence-list
      ...
    />
    <chat
      ...
    />
  </div>
</template>
...
<script>
  ...
  export default {
    name: "app",
    data() {
      return {
        ...
        graphData: {
          nodes: [
            {
              id: "node1",
              offsetY: 50,
              shape: { type: "Flow", shape: "Terminator" },
              annotations: [
                {
                  content: "Start"
                }
              ]
            },
            {
              id: "node2",
              offsetY: 140,
              shape: { type: "Flow", shape: "Process" },
              annotations: [
                {
                  content: "var i = 0;"
                }
              ]
            },
            {
              id: "node3",
              offsetY: 230,
              shape: { type: "Flow", shape: "Decision" },
              annotations: [
                {
                  content: "i < 10?"
                }
              ]
            },
            {
              id: "node4",
              offsetY: 320,
              shape: { type: "Flow", shape: "PreDefinedProcess" },
              annotations: [
                {
                  content: 'print("Hello!!");',
                  style: { fill: "white" }
                }
              ]
            },
        {
          id: "node5",
          offsetY: 410,
          shape: { type: "Flow", shape: "Process" },
          annotations: [
            {
              content: "i++;"
            }
          ]
        },
        {
          id: "node6",
          offsetY: 500,
          shape: { type: "Flow", shape: "Terminator" },
          annotations: [
            {
              content: "End"
            }
          ]
        }
      ],
      connectors: [
        {
          id: "connector1",
          sourceID: "node1",
          targetID: "node2"
        },
        {
          id: "connector2",
          sourceID: "node2",
          targetID: "node3"
        },
        {
          id: "connector3",
          sourceID: "node3",
          targetID: "node4",
          annotations: [{ text: "Yes" }]
        },
        {
          id: "connector4",
          sourceID: "node3",
          targetID: "node6",
          labels: [{ text: "No" }],
          segments: [
            { length: 30, direction: "Right" },
            { length: 300, direction: "Bottom" }
          ]
        },
        {
          id: "connector5",
          sourceID: "node4",
          targetID: "node5"
        },
        {
          id: "connector6",
          sourceID: "node5",
          targetID: "node3",
          segments: [
            { length: 30, direction: "Left" },
            { length: 200, direction: "Top" }
          ]
        }
      ]
    },
   ...
  };
},
</script

The component ejs-diagram takes in node and connectors to render the graph. graphdata will be passed to convergence domain controller to open/create the model in convergence.

  _openModel(modelId, graphData) {
    console.log("graphData", graphData);
    return this._domain
      .models()
      .openAutoCreate({
        id: modelId,
        collection: "testcollection",
        data: this.graphData
      })
      .then(model => {
        // returns the realtime collaboration model, any edits should be done to this model
        this._model = model;
      });
  }

So if i were to just pass in this.graphData, this is the error i get from the javascript console

So I suspect that the graphData is bloated with vue reactivity methods which causes this error.

To get the code to work. I pass in a parsed instance of the data to remove the bloat. So instead of

data: this.graphData

I used

data:JSON.parse(JSON.stringify(this.graphData))

So what came to my mind is that the realTimeModel that is returned by the domain object will not be in synced with the data used to render the graph.

Do let me know if I am being clear on the problem. Thanks!

Yes, I think the Convergence integration would have to occur at a lower level, within the actual diagram component. I assume that the ejs-diagram is a Vue wrapper of a SyncFusion component? Is it open source? Again, it’s not really semantically correct to sync the entire component rather than the state rendered by the component (e.g. the vectors, lines etc of the actual drawn content).

ejs-diagram is a component from the syncfusion library. It is not open source.

Just to clarify, I am syncing the data and not the entire component. But I have since gotten it to work yesterday. I combed through the mxgraph demo code to see how did your do it and followed along.

But as i went through, I realized that there are two instances of the data. One is the realTimeObject (i.e. _rtcells) and the other is the actual data in used in the displayedGraph (i.e. _mxGraph). I am wondering if this will cause any issues with regards to collaboration. Where there would be some lag time from listening to the events at mxGraph and then deserializing it to a format the realTimeObject will accept. and vice versa when a remote change comes in.

Thanks for your help so far!

Yeah, so long as the data to be serialized isn’t that big it probably wouldn’t have an effect on the user experience. You can often get away with some latency in these sorts of scenarios, too, as it’s not always crucial for everybody to be seeing exactly the same thing at the same time. Unlike something like videoconferencing, where any lag in the video or audio is immediately noticeable because the A/V comprises the entire experience!

Ic. Thanks for your reply! On this note, I have a few other technical questions but I will create a separate discussion thread for that matter.

Generally speaking what we see is that, for example a listener firing on mXGraph will fire when the use changes the data. In this event listener we will mutate the corresponding mirrored real time element in convergence. Convergence then immediately sends off the synchronization to the server / other clients. This happens in the same event loop tick, so from the users perspective it happens immediately.