Closed MehbubRashid closed 4 years ago
Hi @MehbubRashid
You have tried to add the new html in the node.
editor.drawflow.drawflow.Home.data[5].html = "...."
@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
In which case do you need to add more html?
@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.
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.
Okay, i have understood..so i need to modify the html property everytime i add custom html
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.
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.
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>
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:
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
i want my block to look something like this.
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?
hey @cage81 , i'm facing a similar problem
i want my block to look something like this.
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 , i'm facing a similar problem i want my block to look something like this. 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.
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.
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.
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.
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.
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:
Hi cage81 , looks very good!, can you send css for having lines like --->
and not ---
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