Skip to content
This repository has been archived by the owner on Jul 29, 2019. It is now read-only.

vis.js Network & Vue.js - the setData method is not working properly #2524

Closed
FilipQL opened this issue Jan 2, 2017 · 9 comments
Closed

Comments

@FilipQL
Copy link

FilipQL commented Jan 2, 2017

Hi,

I am using Vis Network with Vue.js and I created a very simple app but it is not working properly.

https://jsfiddle.net/Filip_Z/3ead4r51/1/

The problem is that after applying the setData method, wrong labels are displayed. As you can see in the example, when you click on the button - this Vue method will be executed:

                updateData() {
                    var nodes = [
                        { id: 0, label: '0' },
                        { id: 1, label: '1' },
                        { id: 4, label: '4' }
                    ];

                    this.network.setData({ nodes: nodes, edges: []});
                    console.log('Updated!');
                    this.disabled = true;
                }

So there will be three nodes but, for some reason, all these nodes will have the same label: '4'.

vis vue - dataset - jsfiddle - mozilla firefox 2017-01-02 02 58 55

In the browser console there are no errors.

Now, I'm not sure where the problem is (Vue or Vis).

@FilipQL FilipQL changed the title vis.js Network & Vue.js - not working properly vis.js Network & Vue.js - the setData method is not working properly Jan 2, 2017
@Tooa
Copy link
Member

Tooa commented Jan 3, 2017

Can you reproduce the problem without using vue?

Edit: Looks like this works without vue - See this example.

@FilipQL
Copy link
Author

FilipQL commented Jan 3, 2017

@Tooa Yeah, this works without Vue, but can't see why it doesn't work with Vue. Anyway, it seems that this has nothing to do with Vis. Thanks!

@Tooa
Copy link
Member

Tooa commented Jan 16, 2017

I debugged this issue the last three hours without success. However, I got some insights and I want to share here with you:

The rendering for the node labels is called here. The Label for a node is initialized here. During runtime the specific node label is set via updateLabelModule here. The options parameter contains the respective node label and will be stored in this.elementOptions here.

This all seems to work fine. However, for some strange reasons later on the value of this.elementOptions.label is defined as 4 here for all nodes.

@FilipQL
Copy link
Author

FilipQL commented Jan 19, 2017

@Tooa I'm not sure but I believe that the problem is not in Vis but in how Vue's reactivity works. From the Vue documentation:

Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive. For example:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive

Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method:

Vue.set(vm.someObject, 'b', 2)

You can also use the vm.$set instance method, which is just an alias to the global Vue.set:

this.$set(this.someObject, 'b', 2)

Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:

// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

There are also a few array-related caveats, which were discussed earlier in the list rendering section:

Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

  1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
  2. When you modify the length of the array, e.g. vm.items.length = newLength

To overcome caveat 1, both of the following will accomplish the same as vm.items[indexOfItem] = newValue, but will also trigger state updates in the reactivity system:

// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice`
example1.items.splice(indexOfItem, 1, newValue)

To deal with caveat 2, you can also use splice:

example1.items.splice(newLength)

... so I guess that the answer lies here:

    data() {
        return {
            network: null,
            ...
        }
    }
   ...
    mounted() {
        this.network = new vis.Network(this.container, data, this.options);
    }

I put the whole Vis instance in the Vue data... but I am still not sure if this is the reason.

I overcame this problem by moving Vis out of Vue, and now I am using "centralized event hub" to communicate with Vue: https://jsfiddle.net/9cpqjs9h/
This is not really good and definitely not an elegant solution, but it works for now.

@mojoaxel mojoaxel modified the milestone: Major Release v5 Feb 16, 2017
@mosinve
Copy link

mosinve commented Mar 1, 2017

if this.network changed to simply network seems it starts to work correctly....
http://codepen.io/cih/pen/jBbJZo
what consequences will be if use network as standalone prop, not the vue object's prop?

And one more thing, with babel preprocessor enabled in codepen - doesn't work.

@kolarski
Copy link

kolarski commented Mar 18, 2017

A workaround hack is to attach it to the global window object and avoid vue.js observer all together:
window.mynetwork = new vis.Network(this.container, this.data, this.options);

Here is working example: http://codepen.io/anon/pen/pepavJ

@wimrijnders
Copy link
Contributor

@kolarski Why is this a hack? I think moving the network instance out of the this-context in mounted() is concise and elegant. It avoids a lot of problems with the interaction between vue and vis.

@FilipQL
Copy link
Author

FilipQL commented Jun 8, 2017

Attaching Vis nodes, edges, data, options, network... to the global window object and using "centralized event hub" to communicate with Vue is what I did in my project:

https://github.com/FilipQL/OVS-Mesh-Script-Generator

entry (src / main.js):

/**
 * Vis nodes (switches and hosts).
 * http://visjs.org/docs/network
 *
 * @type {vis.DataSet}
 * @private
 */
window._nodes = new vis.DataSet([
    { id: 0, index: 0, group: 'switch', label: 'sw00', x: -147, y: -77, controller: 'tcp:127.0.0.1:6633', listen: 'ptcp:6634', ofv: 'OpenFlow13' },
    . . .
]);

/**
 * Vis edges (links between switches/host and switches).
 * http://visjs.org/docs/network
 *
 * @type {vis.DataSet}
 * @private
 */
window._edges = new vis.DataSet([
    { from: 0, to: 1 },
    . . .
]);

/**
 * Provide the data in the vis format.
 * http://visjs.org/docs/network
 *
 * @type {{nodes: (vis.DataSet|*), edges: (vis.DataSet|*)}}
 * @private
 */
window._data = {
    nodes: _nodes,
    edges: _edges
};

/**
 * Customize (configure) the network.
 * http://visjs.org/docs/network/#options
 */
window._options = {
    . . .

    manipulation: {
        enabled: true,
        initiallyActive: true,

        /**
         * Add a new switch or host.
         * If a node is going to be added by a user, this function will be called first.
         * http://visjs.org/docs/network/manipulation.html
         *
         * @param nodeData
         * @param callback
         */
        addNode(nodeData, callback) {
            // Set Modal's Title and Default Input Values
            document.getElementById('operation').innerHTML = "Add Node";
            document.getElementById('saveButton-text').innerHTML = " Add Node";
            document.getElementById('node-controller').value = _defaultConfiguration.controller;
            document.getElementById('node-listen').value = _defaultConfiguration.listen;
            document.getElementById('node-ofv').value = _defaultConfiguration.ofv;
            $('#node-type').prop('disabled', false);

            // Save Button Click
            document.getElementById('saveButton').onclick = function() {

                let nodeTypeSelectElement = document.getElementById("node-type");
                let nodeType = nodeTypeSelectElement.options[nodeTypeSelectElement.selectedIndex].value;

                if (nodeType == "switch") {
                    assignValues(nodeData);
                    eventHub.$emit('add-switch', nodeData);
                    callback(nodeData);

                    // Emit these events just to update the listed values of controllers, listens & ofvs of nodes
                    eventHub.$emit('change-default-controller');
                    eventHub.$emit('change-default-listen');
                    eventHub.$emit('change-default-ofv');

                    $('#network-popUp').modal('hide');
                }

                if (nodeType == "host") {
                    eventHub.$emit('add-host', nodeData);
                    callback(nodeData);

                    $('#network-popUp').modal('hide');
                }

            };

            $('#network-popUp').modal();
        },

        /**
         * Change the value of controller, listen and/or ofv for a selected switch.
         * If a node is going to be edited by a user, this function will be called first.
         * http://visjs.org/docs/network/manipulation.html
         *
         * @param nodeData
         * @param callback
         */
        editNode(nodeData, callback) {
            if (nodeData.group == "switch") {
                // Set Modal's Title and Input Values
                document.getElementById("node-type").selectedIndex = 0;
                document.getElementById('operation').innerHTML = "Edit Switch " + nodeData.label;
                document.getElementById('saveButton-text').innerHTML = " Save Changes";
                document.getElementById('node-controller').value = nodeData.controller;
                document.getElementById('node-listen').value = nodeData.listen;
                document.getElementById('node-ofv').value = nodeData.ofv;

                $('#node-type').prop('disabled', 'disabled');

                // Save Button Click
                document.getElementById('saveButton').onclick = function() {
                    assignValues(nodeData);
                    callback(nodeData);

                    // Emit these events just to update the listed values of controllers, listens & ofvs of nodes
                    eventHub.$emit('change-default-controller');
                    eventHub.$emit('change-default-listen');
                    eventHub.$emit('change-default-ofv');

                    $('#network-popUp').modal('hide');
                };

                // If a user cancels editing- Vis excepts: callback(null);
                $('#network-popUp').on('hide.bs.modal', function (e) {
                    callback(null);
                });

                // Display Popup with Form
                eventHub.$emit('edit-switch');
                $('#network-popUp').modal();

            } else {
                callback(null);
            }
        },

        /**
         * Delete selected switch(es) and/or host(s).
         * If a node is going to be deleted by a user, this function will be called first.
         * http://visjs.org/docs/network/manipulation.html
         *
         * @param deleteData
         * @param callback
         */
        deleteNode(deleteData, callback) {
            eventHub.$emit('delete-node', deleteData);
            callback(deleteData);

            // Emit these events just to update the listed values of controllers, listens & ofvs of nodes
            eventHub.$emit('change-default-controller');
            eventHub.$emit('change-default-listen');
            eventHub.$emit('change-default-ofv');
        },

        /**
         * Add a new link between two nodes (switches/host and switch).
         * If an edge (link) is going to be added by a user, this function will be called first.
         * http://visjs.org/docs/network/manipulation.html
         *
         * @param edgeData
         * @param callback
         */
        addEdge(edgeData, callback) {
            var nodeOne = _nodes.get(edgeData.from);
            var nodeTwo = _nodes.get(edgeData.to);

            if (edgeData.from === edgeData.to) {
                $('#add-link-error').modal(); // You cannot connect node (switch or host) to itself;
                callback(null);
            } else if (nodeOne.group === "host" && nodeTwo.group === "host") {
                $('#add-link-error').modal(); // Hosts cannot connect to other hosts.
                callback(null);
            } else if (nodeOne.group === "switch" && nodeTwo.group === "switch") {
                eventHub.$emit('add-s2s-link', edgeData);
                callback(edgeData);
            } else if (nodeOne.group !=  nodeTwo.group) {
                eventHub.$emit('add-h2s-link', edgeData);
                callback(edgeData)
            }
        },

        /**
         * Redirect the selected link which connects two switches or switch and host.
         * If an edge (link) is going to be edited by a user, this function will be called first.
         *
         * @param edgeData
         * @param callback
         */
        editEdge(edgeData, callback) {
            if (edgeData.from === edgeData.to) {
                $('#add-link-error').modal(); // You cannot connect node (switch or host) to itself;
                callback(null);
            } else if (_nodes.get(edgeData.from).group == 'host' && _nodes.get(edgeData.to).group == 'host') {
                $('#add-link-error').modal(); // Hosts cannot connect to other hosts.
                callback(null);
            } else {
                eventHub.$emit('edit-link', edgeData);
                callback(edgeData)
            }
        },

        /**
         * Delete the selected link.
         * If an edge (link) is going to be deleted by a user, this function will be called first.
         *
         * @param deleteData
         * @param callback
         */
        deleteEdge(deleteData, callback) {
            eventHub.$emit('delete-link', deleteData);
            callback(deleteData)
        }
    }, // manipulation

    . . .
}; // _options

/**
 * _network will contain the Vis instance when the "app" Vue application is mounted (see App.vue).
 *
 * @type {{}}
 * @private
 */
window._network = {};

src / App.vue:

        mounted() {
            var container = document.getElementById('mynetwork');
            _network = new vis.Network(container, _data, _options);
        }

@wimrijnders
Copy link
Contributor

wimrijnders commented Jun 9, 2017

Duplicate of #2567. See discussion there as to the cause of this issue.

#2567 gives an explanation of what is happening and what can be done about this. Closing this issue for that reason.

@wimrijnders wimrijnders marked this as a duplicate of #2567 Jul 16, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants