Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dynamically added html is not included in the export data #40

Closed
MehbubRashid opened this issue Sep 15, 2020 · 16 comments
Closed

dynamically added html is not included in the export data #40

MehbubRashid opened this issue Sep 15, 2020 · 16 comments
Assignees

Comments

@MehbubRashid
Copy link
Contributor

HI @jerosoler , found another issue..if i dynamically add any html into any node and later if i export, the dynamically added html is not present in the html of the export

@jerosoler
Copy link
Owner

Hi @MehbubRashid

You have tried to add the new html in the node.

editor.drawflow.drawflow.Home.data[5].html = "...."

@jerosoler jerosoler self-assigned this Sep 15, 2020
@MehbubRashid
Copy link
Contributor Author

@jerosoler in this way it works, but in my opinion its not a good solution..the current html should get exported when editor.export is called

@jerosoler
Copy link
Owner

In which case do you need to add more html?

@MehbubRashid
Copy link
Contributor Author

@jerosoler suppose i am making a chatbot builder using this library.user can ask one question and then add options to their questions...each option will be paired with one output port (using addnodeoutput method)...so in this case, the user need to have the ability to dynamically add html (options of his questions)...this is a very very common use case of flow builders and i am pretty sure, almost every developer will modify the html content of the node...its very common.

@jerosoler
Copy link
Owner

A function could be added.

Type:

addnodeoutput(...);
/* You code 
update 
html node.
*/
updateNodeHtml(id_node);

And not having to go through all the nodes.

If it were done during the export, we would have to take into account vue components and registered components.

It could also affect the changeModule function as it does not export the content to change the view.

@MehbubRashid
Copy link
Contributor Author

Okay, i have understood..so i need to modify the html property everytime i add custom html

@cage81
Copy link

cage81 commented Nov 5, 2020

Hello @MehbubRashid , I'm doing the same you talked about (a chatbot builder) in VueJS, using this amazing JS library (thanks Jero <3 )...I let the users to change nodes' contents (buttons or other fields type) but after the saved data import, I have only the canvas draw (nodes and connections) and each node is "empty" (like it was their first instance)...I know I have to use Vue component's props to fill some fixed empty fields on the nodes, but I don't understand how to redraw the dynamic node content (buttons and other fields type)...they are not html, they are VueJS components...

Any suggestions? Thanks.

@MehbubRashid
Copy link
Contributor Author

Hi @cage81 , unfortunately i am not experienced in vue..i think @jerosoler can suggest you a solution for it..its a very common problem and i know many users will face this too..For the dynamic node content, i have kept a hidden field which saves the dynamic content as string of html..and when i call editor.import, i retrive the html value and simply append it as html in the node.This way it works. I think you can do the same with vue props..on dynamically adding any component or other things, you can save it as a seperate data of the node and when importing, you can construct the props and components from the node data accordingly.

@jerosoler
Copy link
Owner

Hello @cage81

You could use the data element in the node.

In this case I use the setup property to save the buttons.

I show you an example in vue.
App.vue

<template>
  <div id="app">
    <div id="drawflow"></div>
    <div class="btn" @click="dfExport">Export</div>
    <div class="btn" @click="dfImport">Import</div>
  </div>
</template>

<script>
import Vue from 'vue'
import bus from './components/event-bus.js';
/*eslint-disable */
import NodeClick from './components/NodeClick.vue'
import NodeExample from './components/NodeExample.vue'
import NodeExample2 from './components/NodeExample2.vue'
import Drawflow from 'drawflow'
import styleDrawflow from 'drawflow/dist/drawflow.min.css' // eslint-disable-line no-use-before-define
/*eslint-enable */

/*eslint-disable */
export default {
  name: 'App',
  data() {
    return {
      editor: null,
      exportdata: null,
    }
  },
  mounted() {

    const id = document.getElementById("drawflow");
    this.editor = new Drawflow(id, Vue);
    this.editor.start();




    bus.$on('getData', (id) =>  {
      const dataNode = this.editor.getNodeFromId(id.slice(5)).data;
      bus.$emit('SendData', {id, dataNode})
    })

    bus.$on('saveData', (id, setup) =>  {
      this.editor.drawflow.drawflow['Home'].data.[id.slice(5)].data.setup = setup;
    })

    bus.$on('refreshNode', (id) =>  {
      this.editor.updateConnectionNodes(id);
    })


    const props = {};
    const options = {};
    this.editor.registerNode('NodeExample', NodeExample, props, options);
    this.editor.registerNode('NodeExample2', NodeExample2, props, options);

    //const data = {};
    this.editor.addNode('Name', 3, 2, 10, 200, 'Class', { action : 'move' ,  options:  'up'  }, 'NodeExample', 'vue');
    this.editor.addNode('Name', 3, 2, 300, 200, 'Class', { action : 'move2' ,  options:  'down'  }, 'NodeExample', 'vue');

    this.editor.addNode('Name', 1,1, 100, 300, 'Class', { setup: { button: ["hello", "World"] } }, 'NodeExample2', 'vue');

  },
  methods: {
    dfExport() {
      this.exportdata = this.editor.export();
      console.log(JSON.stringify(this.exportdata));
      alert("Export");
    },
    dfImport() {
      this.editor.import(this.exportdata);
      alert("Import");
    }
  }

}
  /*eslint-enable */
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
#drawflow {
  text-align: initial;
  position: relative;
  width: 100%;
  height: 800px;
  border: 1px solid red;
}

.btn {
  display: inline-block;
  width: 100px;
  border: 1px solid blue;
  border-radius: 4px;
  padding: 5px 10px;
  margin-right: 5px;
  margin-top: 5px;
  cursor: pointer;
}
</style>

components/event-bus.js

import Vue from 'vue';
const bus = new Vue();
export default bus;

components/NodeExample2.vue

<template>
  <div>
    <h1>Hello</h1>
    <div v-for="item in setup.button" :key="item">
      <button>{{ item }}</button>
    </div>
    <button @click="add()">add</button>
    <button @click="save()">Save</button>
  </div>
</template>

<script>
import bus from './event-bus.js';
export default {
  data() {
    return {
      dataId: null,
      dataNode: null,
      setup: {},
    }
  },
  mounted() {
    this.$nextTick(() => {
      const id = this.$el.parentElement.parentElement.id;
      this.dataId = id;
      bus.$emit('getData',id);
    });

    bus.$on('SendData',  (data) => {
      if(data.id === this.dataId) {
        this.dataNode = data.dataNode;
        this.setup = data.dataNode.setup;
        bus.$emit('refreshNode', this.dataId);
      }
    });

  },
  methods: {
    save() {
      bus.$emit('saveData', this.dataId, this.setup);
    },
    add() {
      this.setup.button.push("Drawflow");

    }
  }
}
</script>

@cage81
Copy link

cage81 commented Nov 11, 2020

Hi @jerosoler thanks for your answer! I was working on it and I did what I aimed! Thanks for your amazing library, for your code (in the issues' answers too...) and all the things you do and time you are spending in its improvement!

I let you here an example (sorry for the poor quality) of what I did in VueJS using your drawflow's library:

out

@iamnikhilrohan
Copy link

Hi @cage81 , unfortunately i am not experienced in vue..i think @jerosoler can suggest you a solution for it..its a very common problem and i know many users will face this too..For the dynamic node content, i have kept a hidden field which saves the dynamic content as string of html..and when i call editor.import, i retrive the html value and simply append it as html in the node.This way it works. I think you can do the same with vue props..on dynamically adding any component or other things, you can save it as a seperate data of the node and when importing, you can construct the props and components from the node data accordingly.

hey @cage81 , i'm facing a similar problem
blah
blah 1

i want my block to look something like this.
blah2

when i click on the add button education_fields() should get called.
the problem i am facing is that when i export the canvas, the data from dynamically created input fields are not added in the exported data.and i also what those fields to get recreated when i import() as well.

is there any solution for this?

@cage81
Copy link

cage81 commented Apr 13, 2021

hey @cage81 , i'm facing a similar problem
blah
blah 1

i want my block to look something like this.
blah2

when i click on the add button education_fields() should get called.
the problem i am facing is that when i export the canvas, the data from dynamically created input fields are not added in the exported data.and i also what those fields to get recreated when i import() as well.

is there any solution for this?

Hi @iamnikhilrohan,

as you can see in the gif I attached, I solved it. It's pure JavaScript, it doesn't matter if you're using or not VueJs. Tomorrow I will provide you suggestions, sorry, it's late in my country. Meanwhile, if you want to share your code, please invite me in a your repository, I will contribute.

@iamnikhilrohan
Copy link

iamnikhilrohan commented Apr 14, 2021

hey @cage81 , i'm facing a similar problem
blah
blah 1
i want my block to look something like this.
blah2
when i click on the add button education_fields() should get called.
the problem i am facing is that when i export the canvas, the data from dynamically created input fields are not added in the exported data.and i also what those fields to get recreated when i import() as well.
is there any solution for this?

Hi @iamnikhilrohan,

as you can see in the gif I attached, I solved it. It's pure JavaScript, it doesn't matter if you're using or not VueJs. Tomorrow I will provide you suggestions, sorry, it's late in my country. Meanwhile, if you want to share your code, please invite me in a your repository, I will contribute.

Hey @cage81, Thanks for replying.
Sorry but i cant share the repository because its for an organisation where im currently working as an intern and i dont have the permission to share them.
https://drive.google.com/drive/folders/1ziqOtH5-y_N0SFbdUyPPu-nEiFM8LtXT?usp=sharing
but i can share a files with which i'm working.
trainingphrase

My code dynamically adds a new input field when i click the Add icon.
All i want is, when i export, i want the datas in these fields to get exported and and when i import i want these data to to get imported.
Please feel free to use the files.

@cage81
Copy link

cage81 commented Apr 14, 2021

Hey @cage81, Thanks for replying.
Sorry but i cant share the repository because its for an organisation where im currently working as an intern and i dont have the permission to share them.
https://drive.google.com/drive/folders/1ziqOtH5-y_N0SFbdUyPPu-nEiFM8LtXT?usp=sharing
but i can share a files with which i'm working.
trainingphrase

My code dynamically adds a new input field when i click the Add icon.
All i want is, when i export, i want the datas in these fields to get exported and and when i import i want these data to to get imported.
Please feel free to use the files.

Hi, @iamnikhilrohan,

in my VueJs app, each node is a component with its props, here is how I save the data in the drawflow editor emitting to the parent, the following are the parent's methods:

handleAddNodeContent(e) {
    let moduleName = this.editor.getModuleFromNodeId(e.nodeId);
    const infoNode = this.editor.getNodeFromId(e.nodeId);
    this.editor.drawflow.drawflow[moduleName].data[e.nodeId].nodeText = e.nodeText;
    const nodeData = Object.keys(infoNode.data).length;
    this.editor.drawflow.drawflow[moduleName].data[e.nodeId].data['data_' + (nodeData + 1)] = e.nodeData;
}

here is how I reload the data (three methods: customImport, customLoad, customAddNodeImport):

customImport(data) {
      this.editor.precanvas.innerHTML = "";
      this.editor.drawflow = JSON.parse(JSON.stringify(data));

      this.customLoad();
      this.editor.dispatch('import', 'import');
    }
    
 customLoad() {
      for (let key in this.editor.drawflow.drawflow[this.editor.module].data) {
        this.customAddNodeImport(this.editor.drawflow.drawflow[this.editor.module].data[key], this.editor.precanvas);
      }

      if(this.editor.reroute) {
        for (let key in this.editor.drawflow.drawflow[this.editor.module].data) {
          this.editor.addRerouteImport(this.editor.drawflow.drawflow[this.editor.module].data[key]);
        }
      }

      for (let key in this.editor.drawflow.drawflow[this.editor.module].data) {
        this.editor.updateConnectionNodes('node-'+key);
      }

      const editor = this.editor.drawflow.drawflow
      let number = 1;
      Object.keys(editor).map(function(moduleName, index) {
        Object.keys(editor[moduleName].data).map(function(id, index2) {
          if(parseInt(id) >= number) {
            number = parseInt(id)+1;
          }
        })
      });
      this.editor.nodeId = number;
}

customAddNodeImport(dataNode, precanvas) {
      const parent = document.createElement('div');
      parent.classList.add('parent-node');

      const node = document.createElement('div');
      node.innerHTML = '';
      node.setAttribute('id', 'node-' + dataNode.id);
      node.classList.add('drawflow-node');
      if(dataNode.class != '') {
        node.classList.add(dataNode.class);
      }

      const inputs = document.createElement('div');
      inputs.classList.add('inputs');

      const outputs = document.createElement('div');
      outputs.classList.add('outputs');

      Object.keys(dataNode.inputs).map(function(input_item, index) {
        const input = document.createElement('div');
        input.classList.add('input');
        input.classList.add(input_item);

        input.style.top = '70px';
        if (dataNode.html.includes('EndNode')) {
          input.style.top = '0px';
        }

        inputs.appendChild(input);
        Object.keys(dataNode.inputs[input_item].connections).map(function(output_item, index) {

          let connection = document.createElementNS('http://www.w3.org/2000/svg','svg');
          let path = document.createElementNS('http://www.w3.org/2000/svg','path');
          path.classList.add('main-path');
          path.setAttributeNS(null, 'd', '');

          connection.classList.add('connection');
          connection.classList.add('node_in_node-' + dataNode.id);
          connection.classList.add('node_out_node-' + dataNode.inputs[input_item].connections[output_item].node);
          connection.classList.add(dataNode.inputs[input_item].connections[output_item].input);
          connection.classList.add(input_item);

          connection.appendChild(path);
          precanvas.appendChild(connection);

        });
      });

      for(let x = 0; x < Object.keys(dataNode.outputs).length; x++) {
        const output = document.createElement('div');
        output.classList.add('output');
        output.classList.add('output_' + (x + 1));

        // Custom output position
        output.style.top = '' + (220 + (x * 35)) + 'px';

        outputs.appendChild(output);
      }

      const content = document.createElement('div');
      content.classList.add('drawflow_content_node');

      if(dataNode.typenode === false) {
        content.innerHTML = dataNode.html;
      } else if (dataNode.typenode === true) {
        content.appendChild(this.editor.noderegister[dataNode.html].html.cloneNode(true));
      } else {
        let metaIntentsListLoaded = this.metaIntents;
        let nodeContent = [];
        Object.entries(dataNode.data).forEach(function (key, value) {
          nodeContent.push(key[1]);
        });

        let nodePopoverFormProp = [];
        if (dataNode.form) {
          Object.entries(dataNode.form).forEach(function (key, value) {
            let nodeFormRaw = key[1];
            let choicesRaw = '';
            if ((nodeFormRaw.choices) && (nodeFormRaw.choices.length > 0)) {
              for (let c of nodeFormRaw.choices) {
                choicesRaw = choicesRaw.concat(c.value).concat(',');
              }
              nodeFormRaw.choices = choicesRaw.slice(0, -1);
            } else {
              nodeFormRaw.choices = choicesRaw;
            }
            nodePopoverFormProp.push(nodeFormRaw);
          });
        }
        
        // **Here is the tricky part: here I set the node props that will be reloaded**
        let storedProps = { id: dataNode.id, nodeTypeProp: dataNode.name, nodeTextProp: dataNode.nodeText, metaIntentsList: metaIntentsListLoaded, nodeContentListProp: nodeContent, leadProp: dataNode.lead, nodeFormListProp: nodePopoverFormProp };
        let wrapper = new this.editor.render({
          render: h => h(this.editor.noderegister[dataNode.html].html, { props: storedProps }),
          ...this.editor.noderegister[dataNode.html].options
        }).$mount()
        content.appendChild(wrapper.$el);
      }

      node.appendChild(inputs);
      node.appendChild(content);
      node.appendChild(outputs);
      node.style.top = dataNode.pos_y + "px";
      node.style.left = dataNode.pos_x + "px";
      parent.appendChild(node);
      this.editor.precanvas.appendChild(parent);
}

I hope this code can help you and other which want use this amazing editor in VueJs.

@AnirudhWT
Copy link

is there any provision to add custom component in angular using
let html: any = <div class='have-no-docs' pDroppable="products" (onDrop)="drop($event)">random text</div>
this.editor.addNode('test', 1, 1, 150, 300, 'label', data, html)

Major requirement is to create a node where we can drop data which will be dragged from some draggable html.

@mushzak
Copy link

mushzak commented Jan 25, 2023

Hi @jerosoler thanks for your answer! I was working on it and I did what I aimed! Thanks for your amazing library, for your code (in the issues' answers too...) and all the things you do and time you are spending in its improvement!

I let you here an example (sorry for the poor quality) of what I did in VueJS using your drawflow's library:

out out

Hi cage81 , looks very good!, can you send css for having lines like ---> and not ---

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants