I'm trying to make an Excel-like editor with HandsOnTable but I haven't yet figured out how to change a cell's style dynamically, borders in this case.
I have tried to use
setCellMeta(row,col,"borders", My_borders_Object);
and then
MyHotInstance.render();
but this had no effect.
What could I do to solve this problem?
Any help will be very appreciated.
Not sure what my_borders_object is or why you're passing "borders" as a cell meta data argument, but here's a good way of doing it:
There is an initialization option called customBorders; see below for excerpt from documentation:
customBorders : Boolean (default false)
customBorders : Array [{ row: 2, col: 2, left: {width:2, color: 'red'}, right: {width:1, color: 'green'}, top: /*...*/, bottom: /*...*/ }]
customBorders : Array [{ range:{ from:{ row: 1, col: 1 }, to:{ row: 3, col: 4 } }, left: { /*...*/ }, right: { /*...*/ }, top: { /*...*/ }, bottom: { /*...*/ } }]
If true, enables Custom Borders plugin, which enables applying custom borders through the context menu (configurable with context menu key borders).
To initialize Handsontable with predefined custom borders, provide cell coordinates and border styles in form of an array.
See Custom Borders demo for examples.
Version added: 0.11.0
What this means is that at any given point, if you wanted to do a dynamic update of borders, you can use
hotInstance.updateSettings({
customBorders: new_borders_array
})
I'm trying to accomplish the same right now actually. I have tried the following:
ht is the handsontable instance
ht.updateSettings({
customBorders: [
{ range:
{
from: { row: 1, col: 15 },
to: { row: 1, col: 16 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
]
});
Without ht.init() it does not work:
ht.init();
In version 0.17 this worked fine, however after the update in version 0.18 ht.init(); it creates another instance of the table below the current one - very frustrating.
So now I'm again stuck, or I will downgrade to 0.17 until this is fixed in 0.18.
After going thought the handsontable.full.js I managed to do it by extracting some function from the code and building the borders objects:
var container = document.getElementById('ht_container');
var data = function () {
return Handsontable.helper.createSpreadsheetData(20, 12);
};
var hot = new Handsontable(container, {
data: data(),
height: 396,
colHeaders: true,
rowHeaders: true,
stretchH: 'all',
customBorders: true,
});
//get handsontable instance
var instance = hot;
//copy required functions from the JS.... not pretty, but easy enough
//instead of building the required objects manually
var getSettingIndex = function(className) {
for (var i = 0; i < instance.view.wt.selections.length; i++) {
if (instance.view.wt.selections[i].settings.className == className) {
return i;
}
}
return -1;
};
var insertBorderIntoSettings = function(border) {
var coordinates = {
row: border.row,
col: border.col
};
var selection = new WalkontableSelection(border, new WalkontableCellRange(coordinates, coordinates, coordinates));
var index = getSettingIndex(border.className);
if (index >= 0) {
instance.view.wt.selections[index] = selection;
} else {
instance.view.wt.selections.push(selection);
}
};
var createClassName = function(row, col) {
return "border_row" + row + "col" + col;
};
var createDefaultCustomBorder = function() {
return {
width: 1,
color: '#000'
};
};
var createSingleEmptyBorder = function() {
return {hide: true};
};
var createDefaultHtBorder = function() {
return {
width: 1,
color: '#000',
cornerVisible: false
};
};
var createEmptyBorders = function(row, col) {
return {
className: createClassName(row, col),
border: createDefaultHtBorder(),
row: row,
col: col,
top: createSingleEmptyBorder(),
right: createSingleEmptyBorder(),
bottom: createSingleEmptyBorder(),
left: createSingleEmptyBorder()
};
};
var prepareBorderFromCustomAddedRange = function(rowObj) {
var range = rowObj.range;
for (var row = range.from.row; row <= range.to.row; row++) {
for (var col = range.from.col; col <= range.to.col; col++) {
var border = createEmptyBorders(row, col);
var add = 0;
if (row == range.from.row) {
add++;
if (rowObj.hasOwnProperty('top')) {
border.top = rowObj.top;
}
}
if (row == range.to.row) {
add++;
if (rowObj.hasOwnProperty('bottom')) {
border.bottom = rowObj.bottom;
}
}
if (col == range.from.col) {
add++;
if (rowObj.hasOwnProperty('left')) {
border.left = rowObj.left;
}
}
if (col == range.to.col) {
add++;
if (rowObj.hasOwnProperty('right')) {
border.right = rowObj.right;
}
}
if (add > 0) {
this.setCellMeta(row, col, 'borders', border);
insertBorderIntoSettings(border);
}
}
}
};
$(document).ready(function () {
//create my borders object
var customBorders = [
{ range:
{
from: { row: 1, col: 2 },
to: { row: 4, col: 4 }
},
top: { width: 3, color: 'red' },
left: { width: 2, color: 'red' },
bottom: { width: 2, color: 'red' },
right: { width: 2, color: 'red' }
},
];
//used the 'stolen' functions to add them to the HT in
prepareBorderFromCustomAddedRange.call(instance, customBorders[0]);
instance.render();
instance.view.wt.draw(true);
instance.customBorders = customBorders;
});
</style>
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<link href="http://handsontable.com//styles/main.css" rel="stylesheet">
<link href="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.css" rel="stylesheet">
<script src="http://handsontable.com//bower_components/handsontable/dist/handsontable.full.min.js"></script>
<style type="text/css">
body {background: white; margin: 20px;}
h2 {margin: 20px 0;}
<div id="ht_container"></div>
But if you are not lazy, you can build pretty much build your 'border' object and use, insertBorderIntoSettings to add them to your table or write custom code that does the same.
Related
I am trying to apply smart routing of links with the use of ports using JointJS. This documentation shows the one I am trying to achieve. The example on the docs though shows only the programmatic way of adding Link from point A to point B. How do you do this with the use of ports?
Here's my code: JSFiddle.
HTML:
<html>
<body>
<button id="btnAdd">Add Table</button>
<div id="dbLookupCanvas"></div>
</body>
</html>
JS
$(document).ready(function() {
$('#btnAdd').on('click', function() {
AddTable();
});
InitializeCanvas();
// Adding of two sample tables on first load
AddTable(50, 50);
AddTable(250, 50);
});
var graph;
var paper
var selectedElement;
var namespace;
function InitializeCanvas() {
let canvasContainer = $('#dbLookupCanvas').parent();
namespace = joint.shapes;
graph = new joint.dia.Graph({}, {
cellNamespace: namespace
});
paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
}
});
//Dragging navigation on canvas
var dragStartPosition;
paper.on('blank:pointerdown',
function(event, x, y) {
dragStartPosition = {
x: x,
y: y
};
}
);
paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
dragStartPosition = null;
});
$("#dbLookupCanvas")
.mousemove(function(event) {
if (dragStartPosition)
paper.translate(
event.offsetX - dragStartPosition.x,
event.offsetY - dragStartPosition.y);
});
// Remove links not connected to anything
paper.model.on('batch:stop', function() {
var links = paper.model.getLinks();
_.each(links, function(link) {
var source = link.get('source');
var target = link.get('target');
if (source.id === undefined || target.id === undefined) {
link.remove();
}
});
});
paper.on('cell:pointerdown', function(elementView) {
resetAll(this);
let isElement = elementView.model.isElement();
if (isElement) {
var currentElement = elementView.model;
currentElement.attr('body/stroke', 'orange');
selectedElement = elementView.model;
} else
selectedElement = null;
});
paper.on('blank:pointerdown', function(elementView) {
resetAll(this);
});
$('#dbLookupCanvas')
.attr('tabindex', 0)
.on('mouseover', function() {
this.focus();
})
.on('keydown', function(e) {
if (e.keyCode == 46)
if (selectedElement) selectedElement.remove();
});
}
function AddTable(xCoord = undefined, yCoord = undefined) {
// This is a sample database data here
let data = [
{columnName: "radomData1"},
{columnName: "radomData2"}
];
if (xCoord == undefined && yCoord == undefined)
{
xCoord = 50;
yCoord = 50;
}
const rect = new joint.shapes.standard.Rectangle({
position: {
x: xCoord,
y: yCoord
},
size: {
width: 150,
height: 200
},
ports: {
groups: {
'a': {},
'b': {}
}
}
});
$.each(data, (i, v) => {
const port = {
group: 'a',
args: {}, // Extra arguments for the port layout function, see `layout.Port` section
label: {
position: {
name: 'right',
args: {
y: 6
} // Extra arguments for the label layout function, see `layout.PortLabel` section
},
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
body: {
magnet: true,
width: 16,
height: 16,
x: -8,
y: -4,
stroke: 'red',
fill: 'gray'
},
label: {
text: v.columnName,
fill: 'black'
}
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};
rect.addPort(port);
});
rect.resize(150, data.length * 40);
graph.addCell(rect);
}
function resetAll(paper) {
paper.drawBackground({
color: 'white'
});
var elements = paper.model.getElements();
for (var i = 0, ii = elements.length; i < ii; i++) {
var currentElement = elements[i];
currentElement.attr('body/stroke', 'black');
}
var links = paper.model.getLinks();
for (var j = 0, jj = links.length; j < jj; j++) {
var currentLink = links[j];
currentLink.attr('line/stroke', 'black');
currentLink.label(0, {
attrs: {
body: {
stroke: 'black'
}
}
});
}
}
Any help would be appreciated. Thanks!
The default link created when you draw a link from a port is joint.dia.Link.
To change this you can use the defaultLink paper option, and configure the router you would like.
defaultLink documentation reference
const paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
},
defaultLink: () => new joint.shapes.standard.Link({
router: { name: 'manhattan' },
connector: { name: 'rounded' },
})
});
You could also provide several default options in the paper.
defaultLink: () => new joint.shapes.standard.Link(),
defaultRouter: { name: 'manhattan' },
defaultConnector: { name: 'rounded' }
I am using network graph in Highcharts.
The requirement is that "there should be an interconnection between the nodes" as mentioned in the diagram using javascript.
But I could achieve only the basic structure as shown here in:
function showConnection(sample, prefix) {
const new_array = [];
for (let i = 1; i <= 10; i++) {
new_array.push([sample, prefix + i]);
}
return new_array;
}
JSfiddle: Network Graph
Could someone check how to implement it ?
You can push the required connections also in the array and they will be available in the graph as the connections.
Example:
new_array.push([prefix+10, prefix + 1]);
new_array.push([prefix+5, prefix + 9]);
new_array.push([prefix+10, prefix + 9]);
/* This below code snippet showConnection() is for generating nodes,
How to modify this to implement interconnection between nodes??? */
function showConnection(sample, prefix) {
const new_array = [];
for (let i = 1; i <= 10; i++) {
new_array.push([sample, prefix + i]);
}
new_array.push([prefix+10, prefix + 1]);
new_array.push([prefix+5, prefix + 9]);
new_array.push([prefix+10, prefix + 9]);
return new_array;
}
Highcharts.addEvent(
Highcharts.Series,
'afterSetOptions',
function (e) {
var colors = Highcharts.getOptions().colors,
i = 0,
nodes = {};
if (
this instanceof Highcharts.seriesTypes.networkgraph &&
e.options.id === 'lang-tree'
) {
e.options.data.forEach(function (link) {
if (link[0] === 'A') {
nodes['A'] = {
id: 'A',
marker: {
radius: 20
}
};
nodes[link[1]] = {
id: link[1],
marker: {
radius: 10
},
color: colors[i++]
};
} else if (nodes[link[0]] && nodes[link[0]].color) {
nodes[link[1]] = {
id: link[1],
color: nodes[link[0]].color
};
}
});
e.options.nodes = Object.keys(nodes).map(function (id) {
return nodes[id];
});
}
}
);
Highcharts.chart('container', {
chart: {
type: 'networkgraph',
height: '100%',
zoomType: 'xy'
},
title: {
text: 'The Indo-European Language Tree'
},
subtitle: {
text: 'A Force-Directed Network Graph in Highcharts'
},
plotOptions: {
networkgraph: {
keys: ['from', 'to'],
layoutAlgorithm: {
enableSimulation: true,
friction: -0.9
}
}
},
series: [{
dataLabels: {
enabled: true,
linkFormat: ''
},
id: 'lang-tree',
data: showConnection('Item', 'SubItem')
}]
});
#container {
min-width: 320px;
max-width: 800px;
margin: 0 auto;
}
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/networkgraph.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<div id="container"></div>
I'll need to use correct component to implement this https://imgur.com/a/P2VHX2i . I'm currently looking at TreeList Examples ( https://demos.telerik.com/kendo-ui/treelist/remote-data-binding ) using json data from server. Can you give some advice on creating this treelist?
I did a PoC once for a similar scenario like yours. I've simulated a TreeList with a Grid, and it worked:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Untitled</title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.common.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.rtl.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.default.min.css">
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2019.3.917/styles/kendo.mobile.all.min.css">
<style type="text/css">
.item-level {
display: inline-block;
width: 10px;
}
.level-arrow-expanded:before {
content: "\00bb"
}
.level-arrow-collapsed:before {
content: "\00ab"
}
.item-cell {
cursor: pointer
}
td[role="gridcell"] {
padding: 0;
}
.cell-container {
display: inline-block;
padding-bottom: 6.4px;
padding-left: 9.6px;
padding-right: 9.6px;
padding-top: 6.4px;
box-sizing: border-box;
width: 100%;
}
.header-container {
padding: 0
}
.header-editable-cell {
color: #9cc3e5;
font-weight: bold
}
.k-grid tr:hover td .cell-container {
background-color: #bdb4af !important
}
</style>
<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/angular.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/jszip.min.js"></script>
<script src="https://kendo.cdn.telerik.com/2019.3.917/js/kendo.all.min.js"></script>
<script id="line-template" type="text/x-kendo-template">
<div class='cell-container'>
<div class='item-level #= (data.Level < 4 ? "level-arrow-" + (data.Collapsed ? "collapsed" : "expanded") : "") #'></div>
# for (let i = -1; i < (data.Level - 1); i++) { #
<div class='item-level level-space'></div>
# } #
#= data.Line #
</div>
</script>
<script>
$(function() {
const lineTemplate = $("#line-template").html();
let data = [];
let cols = [{
title: " ",
field: "Line",
locked: true,
width: 200,
template: lineTemplate,
attributes: { "class": "item-cell" }
}, {
title: "Customer Type",
field: "CustomerType",
width: 300
}];
for (var n = 0; n < 50000; n++) {
let level = (n % 5),
dataItem = {
Line: n,
CustomerType: (n % 2 == 0 ? "All" : "3rd Party"),
Level: level,
Show: true,
Index: n,
Collapsed: false
};
data.push(dataItem);
}
let grid = $("#grid").kendoGrid({
dataSource: {
data: data,
pageSize: 20,
filter: { field: "Show", operator: "eq", value: true }
},
height: 500,
columns: cols,
scrollable: {
virtual: true
}
}).data("kendoGrid");
grid.lockedTable.on("click", "td.item-cell", function() {
let data = grid.dataSource.data(),
dataItem = grid.dataItem($(this).closest("tr")),
item;
for (let i = 0, count = data.length; i < count; i++) {
item = data[i];
if (item.Index == dataItem.Index) {
dataItem.Collapsed = !dataItem.Collapsed;
}
else if (item.Index > dataItem.Index) {
if (dataItem.Index != i && item.Level == 0) {
break;
}
item.Show = !dataItem.Collapsed;
}
}
grid.dataSource.fetch();
});
});
</script>
</head>
<body>
<div id="grid"></div>
</body>
</html>
Demo in Dojo
Why I did that? To work with huge datasets which I don't know if is your case tho. As you can see, the above TreeList is displaying 50k rows, virtualized. The problem is that TreeList does not virtualizes(check out it's docs), but Grid does.
How that works:
Lock the first column to be the collpase/expand column. You can also lock more columns;
In your dataItems(row's data), you have to use some control properties, which are: Collapsed, Level, Show and Index. They control the collpase/expand behaviour and the hierarchical display. Add a console.log(data) before Grid's initialization to checkout how it's been filled;
The Show property handles the collpase/expand displaying status. Once a row is Show = false, it will be hidden. That works because of a filter in the dataSource: filter: { field: "Show", operator: "eq", value: true };
A click event on the item-cell class manages the row's children display status. That class is added in the first column as: attributes: { "class": "item-cell" }. The event changes the Show property for the children rows;
The template line-template will display the right level indetation and the arrows for the collpase/expand column;
I was recently asked this question: How to approach this problem? Create a tool that will allow designers to configure animations. In order to facilitate this, implement an AnimationSequence in JavaScript that can render these animations.
For example, if a designer wanted to configure the filling of a bar element, the usage of AnimationSequence would look something like this
var barSequence = new AnimationSequence(bar, [
[100, { width: '10%' }],
[200, { width: '20%' }],
[200, { width: '50%' }],
[200, { width: '80%' }],
[300, { width: '90%' }],
[100, { width: '100%' }]
]);
barSequence.animate();
where the first element of each step in the sequence is the number of milliseconds until the step occurs and the second element is an object containing any number of CSS properties.
How would you implement an AnimationSequence?
You need to build a queue system then call each animation frame based on the first value. So something like this...
var AnimationSequence = function(elem, opts) {
this.element = (typeof elem == "object") ? elem : $(elem); //jQuery
this.options = opts;
this.queue = [];
this.timer = 0;
this.init(opts);
}
AnimationSequence.prototype = {
init: function(opts) {
var that = this;
for(var i = 0, l = opts.length; i < l; i++) {
this.queue.push({delay: opts[i][0], command: opts[i][1]});
}
this.deQueue();
},
deQueue: function() {
if(this.queue.length) {
var animation = this.queue.shift(),
that = this;
this.timer = setTimeout(function() {
that.element.animate(animation.command, function() {
that.deQueue();
});
}, animation.delay);
}
}
};
$(function() {
var barSequence = new AnimationSequence(".bar", [
[100, { width: '10%' }],
[200, { width: '20%' }],
[200, { width: '50%' }],
[200, { width: '80%' }],
[300, { width: '90%' }],
[100, { width: '100%' }]
]);
});
Obviously you would have the html...
<div id="bar-holder">
<div class="bar"></div>
</div>
And Css...
#bar-holder {
width: 100%;
padding: 5px;
background: #ccc;
}
.bar {
height: 25px;
background: red;
}
Working jsfiddle example... https://jsfiddle.net/kprxcos4/
Obviously you might want to beef it up a bit, but that is the start of an animation queue system that can handle arguments and custom fields.
I am developing a JointJs Application,I need to set font-size for a text inside a rectangle.
$('#FText,#FTextHeight,#FTextWidth,#FTextSize').on('keyup change', function () {
var FtHeight = $('#FTextHeight').val();
var FtWidth = $('#FTextWidth').val();
var FtSize = parseInt($('#FTextSize').val());
var txt = $('#FText').val();
graph2.clear();
if (txt.length > 0) {
$('#FTexterror').empty();
var myFtext = new joint.shapes.basic.Rect({
position: { x: 50, y: 50 },
size: { width: FtWidth, height: FtHeight },
attrs: {
rect: {
fill: 'white', stroke: outerColor, 'class': 'customtext',
},
text: {
text: txt, 'letter-spacing': 1, 'font-size': FtSize,
fill: outerColor, 'font-size': 11, 'y-alignment': 'middle',
}
}
});
graph2.addCells([myFtext]);
}
else {
$('#FTexterror').empty().append('*Enter valid text');
}
});
the above code is not working while setting font-size for the text.
Kindly help me on this
try this
$('.input-text')
.val(rect.attr('text/font-size')) //set initial value
.on('keyup', function () {
var val = $(this).val();
rect.attr('text/font-size', val);
});
complete demo: https://jsfiddle.net/vtalas/sav49mj4/