How does the `filter` object in the `configure` object in Vis.js work? - vis.js-network

The configure.filter can receive strings like nodes, edges, layout, interaction, manipulation, physics, selection, renderer. It also accepts a function, but I don't know how to tweak it. I tried both
function (enabled, path) {
return path.indexOf('physics') !== -1;
}
and
function (option, enabled) {
return enabled.indexOf('physics') !== -1;
}
but none of them works. The whole configuration panel for physics still displays.
In Vis Network | Physics | Playing with Physics there is an example code to display only the smooth option of the edges:
filter: function (option, path) {
if (path.indexOf("physics") !== -1) {
return true;
}
if (path.indexOf("smooth") !== -1 || option === "smooth") {
return true;
}
return false;
And from there I can go wild and do this:
return path.indexOf("smooth") !== -1 || path.indexOf("font") !== -1 || path.indexOf("smooth") !== -1
But again I don't know how it works. This still limits my choice of what options to display. For example I can't limit font to be only for nodes, not for edges, or use only font.size or hidden.
See also: Can labels be hidden by default, but shown when the node is selected?

In vis-network configure.filter allows a function to be set which filters the displayed configuration options. When the configuration options are loaded/updated this function is called once for each option to determine if the option should be displayed.
As per the function definition filter: function (option, path) { two arguments are passed for each configuration option. The first argument option is a string with the name of the option being checked, for example "size". The second argument path is an array of strings, giving the path to the option for example ["nodes", "font"]. These options and paths match the vis-network options documentation. The function should return true if the option should be displayed, or false if it should not.
To only display specific options the function should check:
The length of the path array path.length === 1 to ensure subcategories are not incorrectly displayed (unless all subcategories should be displayed)
The values in the path array match the options to be displayed in the correct positions path.indexOf("nodes") === 0
The option value is correct option === "hidden"
The below example displays the "nodes font" options (except colors), the "nodes hidden" option and the "edges arrows to" option. Comments are included to describe the checks and show the contents of path and options being checked for.
filter: function(option, path){
// Uncomment the below to print all options
//console.log(path, option);
// Display the general headers
// Without this the options can display in the wrong sections
// path: []
// option: "nodes" or "edges"
if (path.length === 0 && (option === "nodes" || option === "edges")){
return true;
}
// Display the options for node fonts
// path: ["nodes", "font"]
// options: <any>
if (path.indexOf("nodes") === 0 && path.indexOf("font") === 1){
// Do not display the options for color, background or strokeColor (these appear to be bugged even in the physics example on vis.js site)
if (option !== "color" && option !== "background" && option !== "strokeColor"){
return true;
}
}
// Display the option for nodes being hidden
// path: ["nodes"]
// option: "hidden"
if (path.length === 1 && path.indexOf("nodes") === 0 && option === "hidden"){
return true;
}
// Display the option for "to" arrows on edges
// path: ["edges", "arrows", "to"]
// option: "enabled"
if (path.length === 3 && path.indexOf("edges") === 0 && path.indexOf("arrows") === 1 && path.indexOf("to") === 2 && option === "enabled"){
return true;
}
// By default return false so other options are hidden
return false;
}
This is incorporated into the working example below.
// create an array with nodes
var nodes = new vis.DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" },
]);
// create an array with edges
var edges = new vis.DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 },
{ from: 3, to: 3 },
]);
// create a network
var container = document.getElementById("mynetwork");
var treeData = {
nodes: nodes,
edges: edges,
};
var options = {
configure: {
filter: function(option, path){
// Uncomment the below to print all options
//console.log(path, option);
// Display the general headers
// Without this the options can display in the wrong sections
// path: []
// option: "nodes" or "edges"
if (path.length === 0 && (option === "nodes" || option === "edges")){
return true;
}
// Display the options for node fonts
// path: ["nodes", "font"]
// options: <any>
if (path.indexOf("nodes") === 0 && path.indexOf("font") === 1){
// Do not display the options for color, background or strokeColor (these appear to be bugged even in the physics example on vis.js site)
if (option !== "color" && option !== "background" && option !== "strokeColor"){
return true;
}
}
// Display the option for nodes being hidden
// path: ["nodes"]
// option: "hidden"
if (path.length === 1 && path.indexOf("nodes") === 0 && option === "hidden"){
return true;
}
// Display the option for "to" arrows on edges
// path: ["edges", "arrows", "to"]
// option: "enabled"
if (path.length === 3 && path.indexOf("edges") === 0 && path.indexOf("arrows") === 1 && path.indexOf("to") === 2 && option === "enabled"){
return true;
}
// By default return false so option is hidden
return false;
}
}
};
var network = new vis.Network(container, treeData, options);
#mynetwork {
width: 600px;
height: 100px;
border: 1px solid lightgray;
}
<script src="https://visjs.github.io/vis-network/standalone/umd/vis-network.min.js"></script>
<div id="mynetwork"></div>

Related

Vue react to Setting array of an Object to another array and seeing reactive changes

I have a v-data-table on vue, which gets data and dynamically adds and deltes rows based on the incoming object of arrays, Vue is reactive to adding and deleting but doesn't seem to react to array replace.
My function to add, delete and replace is the setup the following way:
function update_helper(update_obj, dataObject, colObject) {
update_obj.Data.forEach((item) => {
if (typeof item.RowData !== 'undefined'){
let temp_list = updateRow(item, colObject);
temp_list.forEach((row_obj) => {
var found = dataObject.find(Element => Element.RowID === row_obj.RowID);
if (typeof found !== 'undefined'){
//Replace
var found = dataObject.findIndex(Element => Element.RowID === item.RowID);
//console.log(row_obj);
//console.log(dataObject[found]);
dataObject[found] = row_obj;
}
else{
// Add
dataObject.push(row_obj);
}
});
}
else if (typeof item.RowData === 'undefined') {
// Delete
var found = dataObject.findIndex(Element => Element.RowID === item.RowID);
dataObject = dataObject.splice(found, 1);
}
});
}
The function keeps track of the row Id . My replace function dataObject[found] = rowObj works but isn't reactive, i.e the change can only be seen when I switch tabs or refresh the page.
How do I workaround this.
Instead of passing it as argument, you could better have it as a data variable like
data() {
return {
dataObject: [],
}
}
and then define your function inside the methods section like
methods: {
update_helper(update_obj, colObject) {
update_obj.Data.forEach((item) => {
if (typeof item.RowData !== 'undefined'){
let temp_list = updateRow(item, colObject);
temp_list.forEach((row_obj) => {
var found = dataObject.findIndex(Element => Element.RowID === row_obj.RowID);
if (found !== -1){
this.dataObject[found] = row_obj;
}
else{
// Add
this.dataObject.push(row_obj);
}
});
}
else if (typeof item.RowData === 'undefined') {
// Delete
var found = this.dataObject.findIndex(Element => Element.RowID === item.RowID);
dataObject = this.dataObject.splice(found, 1);
}
});
}
}
If possible you can declare the colObject also in the data() section
Note: If you observe the above function body, I would have accessed the dataObject using this operator.

How to hide the row if it has no children in kendo-treeview in angular 8?

How to hide the parent row if it has no children to display.text i can hide it but Empty row still present in the DOM element. Can anyone help to get it resolved? angular 8 + kendo treeview.
<kendo-treeview kendoTreeViewExpandable [nodes]="data" [children]="children" [hasChildren]="hasChildren" textField="text"
>
<ng-template kendoTreeViewNodeTemplate let-dataItem>
<span>{{dataitem.title}}</span>
</ng-template>
</kendo-treeview>
Backend side, I have a parent row looking like this :
{
id: "session_client_1669"
id_linked: "1669"
id_regroup: null
libelle: "UN CLIENT PRESQUE PARFAIT"
menu: "client"
type: "parent"
}
Then, a child row taking for id_regroup its parent's id :
{
id: "new_DOC_RAPPORT_SESSION_1669"
idLinked: "1669"
id_regroup: "session_client_1669"
libelle: "Document à générer - Rapport de fin de formation"
menu: "client"
type: "generated"
}
Finally I use a function to recursively filter out elements of parent type with no children, thus getting rid of it in the DOM:
function removeEmptyParent(listDoc) {
try {
return new Promise(async resolve => {
let listDocTemp = (listDoc || []).filter(
item =>
(listDoc.findIndex(e => e.id_regroup === item.id) !== -1 && item.type === 'parent') || item.type !== 'parent',
);
const index = (listDocTemp || []).findIndex(
item => listDocTemp.findIndex(e => e.id_regroup === item.id) === -1 && item.type === 'parent',
);
if (index > -1) {
listDocTemp = await removeEmptyParent(listDocTemp);
}
resolve(listDocTemp);
});
} catch (error) {
console.log(`error`, error); // eslint-disable-line
}
}

Prevent Vue Multiple Select to Store an Empty Array

I want this select multiple to pre-select one option, and not be able to deselect all options.
Whenever the last selected option is deselected it should be reselected. In other words when the user tries to deselect the last selected option it should visually not be deselected.
<template>
<b-select
if="Object.keys(doc).length !== 0 /* wait until firebase has loaded */"
:options="computedOptions"
v-model="model"
multiple
#input="onChange"
/>
</template>
<script>
//import Vue from 'vue'
import { fb } from "../fbconf";
export default {
name: "MyMultiSelect",
props: {
doc: Object, // firestore document
},
data() {
return {
options: []
};
},
firestore() {
var options = fb.db.collection("options");
return {
options: options
};
},
computed: {
computedOptions: function() {
return this.options.map(function(option) {
return {
text: option.name,
value: option.id
};
});
},
// to make sure mySelectedOptions is an array, before this.doc is loaded
// I use the following custom model
// because not using 'get' below causes a warning:
// [Vue warn]: <select multiple v-model="localValue"> expects an Array value for its binding, but got Undefined
model: {
get: function() {
if (!this.doc.hasOwnProperty('mySelectedOptions')) return []; // empty array before this.doc is loaded
else return this.doc['mySelectedOptions'];
},
set: function(newValue) {
// here I can prevent the empty array from being stored
// but visually the user can deselect all options, which is bad UX
//if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
}
},
},
methods: {
onChange: function(newValue){
// I can manually store the array as I want here
// but I cannot in any way prevent the user from deselecting all options
if (Array.isArray(newValue) && newValue.length > 0) this.doc['mySelectedOptions'] = newValue;
else {
// none of these reselects the last selected option
var oldValue = this.doc['mySelectedOptions'];
this.doc['mySelectedOptions'] = this.doc['mySelectedOptions'];
//this.$forceUpdate();
//this.$emit("change", newValue);
//Vue.set(this.doc, 'mySelectedOptions', this.doc['mySelectedOptions']);
}
}
}
};
</script>
You could add watcher and when length becomes 0 just add previous value.
watch: {
model(val, oldVal) {
if(val.length == 0 && oldVal.length > 0) {
// take only one item in case there's clear button or etc.
this.model = [oldval[0]];
}
}
}

Vue tags input custom validation

I'm using vue-tags-input component. In its docs we can find validation. I'm trying to create validation so valid input must have:
min 3 signs
two numbers
comma between numbers
this is what I have:
validation: [{
classes: 'min-length',
rule: tag => tag.text.length < 3,
},{
classes: 'min-length',
rule: ({ text }) => {
const comma = text.indexOf(',') === -1;
if(comma) {
const arr = text.split(',')
if(arr[0] && arr[1]) {
if(arr[0].typeof === 'number' && arr[1].typeof === 'number') {
return true;
}
}
}
return false;
}
}]
So I'm spliting string to array by ,. In result I should have array with two elements. Then I check if both elemenets are numbers. How ever this not work properly because it treat 111 as valid but it shoudn't.
I've created demo on codesanbox.
To check if comma exists you have to check if indexOf comma not equals -1.
const comma = text.indexOf(",") !== -1;
You have to convert the string to number using Number(string).
if (typeof Number(arr[0]) === "number") {..
You have to return false if validation succeeds and true if there is an error,
you are doing the opposite.
The complete code will be:
{
classes: "custom",
rule: ({ text }) => {
const comma = text.indexOf(",") !== -1;
if (comma) {
const arr = text.split(",");
if (arr[0] && arr[1]) {
if (typeof Number(arr[0]) === "number" && typeof Number(arr[1]) === "number") {
return false;
}
}
}
return true;
}
}
A shorter regex rule will be:
{
classes: "custom",
rule: ({ text }) => {
return !text.match(/^\d+,\d+$/);
}
}

Using Ext.form.Basic.loadRecord to load data into Combo Box Fields with Remote Stores

I have a form that has multiple Combo Box fields that are attached to remote stores:
Ext.define('app.ux.form.MyCombo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.mycombo',
store: this.store,
displayField: 'displayField',
valueField: 'valueField',
forceSelection: true,
autoSelect: true,
initComponent: function() {
this.addEvents('selectitem');
this.enableBubble('selectitem');
this.callParent(arguments);
this.listeners = {
change: function(field, value) {
this.fireEvent('selectitem', field, value);
}
}
}
})
fieldLabel: 'DisabilityType',
name: 'f_disability_type',
xtype: 'combo',
valueField: 'valueField',
displayField: 'displayField',
forceSelection: true,
autoSelect: true,
store: 'DisabilityTypes'
DisabilityTypes is a basic Ext.data.store with autoLoad set to false and autoSync set to true. When you click on the dropdown tied to the store, the store loads and shows the list of values.
When I call loadRecord on the BasicForm Object that contains this dropdown and pass it a model, it fills in the combo boxes that use local stores, but doesn't load the combo boxes that use remote stores. This is because either the combo box store isn't loaded (autoLoad: false) or the combo box is loaded AFTER the form loads (autoLoad:true).
I am aware that this was a problem in Ext 3.3.x and that there was a plugin made to fix it:
/**
* When combo box is used on a form with dynamic store (remote mode)
* then sometimes the combobox store would load after the form data.
* And in that case the setValue method of combobox will not
* set the combobox value properly. This override makes sure that the
* combobox store is completely loaded before calling the setValue method.
*/
Ext.override(Ext.form.ComboBox, {
setValue : function(v){
var text = v;
if(this.valueField){
if(!Ext.isDefined(this.store.totalLength)){
this.store.on('load', this.setValue.createDelegate(this, arguments), null, {single: true});
if(this.store.lastOptions === null){
var params;
if(this.valueParam){
params = {};
params[this.valueParam] = v;
}else{
var q = this.allQuery;
this.lastQuery = q;
this.store.setBaseParam(this.queryParam, q);
params = this.getParams(q);
}
this.store.load({params: params});
}
return;
}
var r = this.findRecord(this.valueField, v);
if(r){
text = r.data[this.displayField];
}else if(this.valueNotFoundText !== undefined){
text = this.valueNotFoundText;
}
}
this.lastSelectionText = text;
if(this.hiddenField){
this.hiddenField.value = v;
}
Ext.form.ComboBox.superclass.setValue.call(this, text);
this.value = v;
}
});
Has this problem been fixed in Ext 4? Or do I need to find another plugin that's Ext 4 compatible?
My solution:
Ext.form.field.ComboBox.override( {
setValue: function(v) {
v = (v && v.toString) ? v.toString() : v;
if(!this.store.isLoaded && this.queryMode == 'remote') {
this.store.addListener('load', function() {
this.store.isLoaded = true;
this.setValue(v);
}, this);
this.store.load();
} else {
this.callOverridden(arguments);
}
}
});
Ext.form.field.ComboBox.override( {
setValue: function(value) {
if( typeof value != 'object' && !Ext.isEmpty(value) && !this.store.isLoaded && this.queryMode == 'remote') {
value = (value && value.toString) ? value.toString() : value;
this.store.addListener('load', function() {
this.store.isLoaded = true;
this.setValue(value);
}, this);
this.store.load();
} else {
this.callOverridden(arguments);
}
}
});
just another override - working for me, using the [form].loadRecord([model]) method.
beware: if you use the opposite way [form].updateReocrd([model]) the values of the options will not use the default delimiter, but just ',' instead.
so - if you have a loadRecord, do something, then call updateRecord an loadrecord later, the selections will got lost, due to the wrong delimiter. taht's why the "lower than 2" comparison is being performed here
Ext.form.field.ComboBox.override( {
setValue: function(v) {
if (this.multiSelect && typeof v != 'undefined' && typeof v.split == 'function'){
if (this.value.length < 2){
this.setValue(v.split(this.delimiter));
}
} else {
this.callOverridden(arguments);
}
}
});