The table columnCount

No, I still cannot reproduce a problem…

Is it still using an old version of GoJS? Force a complete reload.

I take a gif wait a seconds

Screen Recording 2021-08-14 at 6.37.53 AM

I confirmed the version is

the mouse should little bit right side of separator line.

I see. Yes, initially there’s no RowColumnDefinition for that column, but there’s still a separate separator handle for it in the Adornment created by the ColumnResizingTool. The user’s resizing of that column is just updating the RowColumnDefinition.width for that column.

I can see reasonable people choosing either policy on this. (And probably several other plausible policies exist too.) If you don’t like what the ColumnResizingTool is doing, allowing the user to resize a column for which there is no RowColumnDefinition, you can change the tool not to create that resize handle. But if you do that, I should warn you that then columns that have the default column definition properties would not be resizable. Which I suspect is not what most people would want. Maybe a policy that you could accept is not to create the resize handle if the actual width is zero?

I need ColumnResizingTool it is so good for my project. the last not so good solution is delete the whole table and rebuild it again, but it is bad User Experience, I think. table resizing is really need if use table.

One more step, i think , please, please fix it for me, please.

??? Could you please explain what the problem is? Do you understand the explanation of why the ColumnResizingTool provides a resize handle for each column?

You have the complete source code for that tool, so if you really want a different behavior, despite my opinions, you can modify it as I suggested.

the original problem is delete the model data, the view data is not be deleted. here, the view is table panel. delete the item array items the views item or property not change properly.

(maybe just maybe) the problem is column number, if you delete the 3 from 0,1,2,3,4 the column number become 0,1,2,4
then even we set back the column number to 0,1,2,3 the column count is not change.
kindly look at this console log ↓

And, If the ColumnResizingTool it is the problem, I can change it, I understand this.

Your code is basically the AddRemoveColumns sample. You have added a ColumnResizingTool, but you have not added the support for remembering the RowColumnDefinition properties of individual columns.

Also your removeColumn function always tries to remove column #3, whereas I think you want to make sure you delete the last one. I’m concerned that your code to shift the column definitions is not deleting the last column definition.

I just simply think add column and delete column via model the corresponding RowColumnDefinition will be gone automatically. of course, I don’t know inner process.
the sample code is also just addArrayItem to table, then RowColumnDefinition automatically added i think.

yes, in real world not always #3 column, it may be last, or maybe other middle column. it depends.
please kindly look console log the column number not decrease after deleted column.

Maybe I not deep understand the inner feature, forgive my ignorance, I think if we add delete column easy way, like the sample code addArrayItem/removeArrayItem, just do this, corresponding view item automatically appear or disappear, the my ideal function is this.

I guess, may be column number is the problem, it have a number keep in data. like column: 0, column:1,
if we don’t use this number just use the not number key. maybe delete or remove things become much easy. just idea, i dont know how much impact to refact it.

Maybe I can look into this later today. [EDIT: sorry, I was busy with other matters yesterday…]

Here’s what I just tried, starting from the published AddRemoveColumns.html sample:

<script src="https://unpkg.com/gojs"></script>
<script src="ColumnResizingTool copy.js"></script>
<script id="code">
  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
        { "undoManager.isEnabled": true });

    myDiagram.toolManager.mouseDownTools.add(new ColumnResizingTool());

    myDiagram.nodeTemplate =
      $(go.Node, "Auto",
        $(go.Shape, { fill: "white" }),
        $(go.Panel, "Table",
          new go.Binding("itemArray", "people"),
          $(go.RowColumnDefinition,
            { row: 0, background: "lightgray" }),
          $(go.RowColumnDefinition,
            { row: 1, separatorStroke: "black" }),
          // the table headers -- remains even if people itemArray is empty
          $(go.Panel, "TableRow",
            { isPanelMain: true },
            new go.Binding("itemArray", "columnDefinitions",
              function(arr, tablerow) {
                // update all of the column RowColumnDefinitions with their declared widths
                var table = tablerow.panel;  // RowColumnDefinitions are on the Table panel
                for (var i = 0; i < arr.length; i++) {
                  var desc = arr[i];
                  var coldef = table.getColumnDefinition(desc.column);
                  if (coldef && typeof desc.width === "number") {
                    coldef.width = desc.width;
                  }
                }
                return arr;
              }),
            {
              itemTemplate:  // bound to a column definition object
                $(go.Panel,
                  new go.Binding("column"),
                  $(go.TextBlock,
                    { margin: new go.Margin(2, 2, 0, 2), font: "bold 10pt sans-serif" },
                    new go.Binding("text"))
                )
            }
          ),
          { // the rows for the people
            name: "TABLE",
            defaultAlignment: go.Spot.Left,
            defaultColumnSeparatorStroke: "black",
            itemTemplate:  // bound to a person/row data object
              $(go.Panel, "TableRow",
                // which in turn consists of a collection of cell objects,
                // held by the "columns" property in an Array
                new go.Binding("itemArray", "columns"),
                // you could also have other Bindings here for the whole row
                {
                  itemTemplate:  // bound to a cell object
                    $(go.Panel,  // each of which as "attr" and "text" properties
                      { stretch: go.GraphObject.Fill, alignment: go.Spot.TopLeft },
                      new go.Binding("column", "attr",
                        function(a, elt) {  // ELT is this bound item/cell Panel
                          // elt.data will be the cell object
                          // elt.panel.data will be the person/row data object
                          // elt.part.data will be the node data object
                          // "columnDefinitions" is on the node data object, so:
                          var cd = findColumnDefinitionForName(elt.part.data, a);
                          if (cd !== null) return cd.column;
                          throw new Error("unknown column name: " + a);
                        }),
                      // you could also have other Bindings here for this cell
                      $(go.TextBlock, { editable: true },
                        { margin: new go.Margin(2, 2, 0, 2), wrap: go.TextBlock.None },
                        new go.Binding("text").makeTwoWay())
                    )
                }
              )
          }
        )
      );

    myDiagram.model =
      $(go.GraphLinksModel,
        {
          copiesArrays: true,
          copiesArrayObjects: true,
          nodeDataArray: [
            { // second node
              key: 2,
              columnDefinitions: [
                { attr: "name", text: "Name", column: 0, width: NaN },
                { attr: "phone", text: "Phone #", column: 2, width: NaN },  // note the different order of columns
                { attr: "office", text: "Office", column: 1, width: NaN }
              ],
              people: [
                { columns: [{ attr: "name", text: "Robert" }, { attr: "phone", text: "5656" }, { attr: "office", text: "B1-A27" }] },
                { columns: [{ attr: "name", text: "Natalie" }, { attr: "phone", text: "5698" }, { attr: "office", text: "B1-B6" }] }
              ]
            }
          ]
        }
      );

    save();
  }

  // Add or remove a person row from the selected node's table of people.

  function insertIntoArray() {
    var n = myDiagram.selection.first();
    if (n === null) return;
    var d = n.data;
    myDiagram.startTransaction("insertIntoTable");
    // add item as second in the list, at index #1
    // of course this new data could be more realistic:
    myDiagram.model.insertArrayItem(d.people, 1, {
      columns: [{ attr: "name", text: "Elena" },
      { attr: "phone", text: "456" },
      { attr: "office", text: "LA" }]
    });
    myDiagram.commitTransaction("insertIntoTable");
  }

  function removeFromArray() {
    var n = myDiagram.selection.first();
    if (n === null) return;
    var d = n.data;
    myDiagram.startTransaction("removeFromTable");
    // remove second item of list, at index #1
    myDiagram.model.removeArrayItem(d.people, 1);
    myDiagram.commitTransaction("removeFromTable");
  }

  // add or remove a column from the selected node's table of people

  function findColumnDefinitionForName(nodedata, attrname) {
    var columns = nodedata.columnDefinitions;
    for (var i = 0; i < columns.length; i++) {
      if (columns[i].attr === attrname) return columns[i];
    }
    return null;
  }

  function findColumnDefinitionForColumn(nodedata, idx) {
    var columns = nodedata.columnDefinitions;
    for (var i = 0; i < columns.length; i++) {
      if (columns[i].column === idx) return columns[i];
    }
    return null;
  }

  function addColumn(attrname) {
    var n = myDiagram.selection.first();
    if (n === null) return;
    var d = n.data;
    // if name is not given, find an unused column name
    if (attrname === undefined || attrname === "") {
      attrname = "new";
      var count = 1;
      while (findColumnDefinitionForName(d, attrname) !== null) {
        attrname = "new" + (count++).toString();
      }
    }
    // find an unused column #
    var col = 3;
    while (findColumnDefinitionForColumn(d, col) !== null) {
      col++;
    }
    myDiagram.startTransaction("addColumn");
    var model = myDiagram.model;
    // add a column definition for the node's whole table
    model.addArrayItem(d.columnDefinitions, {
      attr: attrname,
      text: attrname,
      column: col
    });
    // add cell to each person in the node's table of people
    var people = d.people;
    for (var j = 0; j < people.length; j++) {
      var person = people[j];
      model.addArrayItem(person.columns, {
        attr: attrname,
        text: Math.floor(Math.random() * 1000).toString()
      });
    }
    myDiagram.commitTransaction("addColumn");
    n.clearAdornments();
    n.updateAdornments();
  }

  function removeColumn() {
    var n = myDiagram.selection.first();
    if (n === null) return;
    var d = n.data;
    var coldef = d.columnDefinitions[3];  // get the fourth column
    if (coldef === undefined) return;
    var attrname = coldef.attr;
    myDiagram.startTransaction("removeColumn");
    var model = myDiagram.model;
    model.removeArrayItem(d.columnDefinitions, 3);
    n.findObject("TABLE").removeColumnDefinition(coldef.column);
    // update columns for each person in this table
    var people = d.people;
    for (var j = 0; j < people.length; j++) {
      var person = people[j];
      var columns = person.columns;
      for (var k = 0; k < columns.length; k++) {
        var cell = columns[k];
        if (cell.attr === attrname) {
          // get rid of this attribute cell from the person.columns Array
          model.removeArrayItem(columns, k);
          break;
        }
      }
    }
    myDiagram.commitTransaction("removeColumn");
    n.clearAdornments();
    n.updateAdornments();
  }

  function swapTwoColumns() {
    myDiagram.startTransaction("swapColumns");
    var model = myDiagram.model;
    myDiagram.selection.each(function(n) {
      if (!(n instanceof go.Node)) return;
      var d = n.data;
      var phonedef = findColumnDefinitionForName(d, "phone");
      if (phonedef === null) return;
      var phonecolumn = phonedef.column;  // remember the column number
      var officedef = findColumnDefinitionForName(d, "office");
      if (officedef === null) return;
      var officecolumn = officedef.column;  // and this one too
      model.setDataProperty(phonedef, "column", officecolumn);
      model.setDataProperty(officedef, "column", phonecolumn);
      model.updateTargetBindings(d);  // update all bindings, to get the cells right
    });
    myDiagram.commitTransaction("swapColumns");
    n.clearAdornments();
    n.updateAdornments();
  }

  // save a model to and load a model from Json text, displayed below the Diagram
  function save() {
    var str = myDiagram.model.toJson();
    document.getElementById("mySavedModel").value = str;
  }
  function load() {
    var str = document.getElementById("mySavedModel").value;
    myDiagram.model = go.Model.fromJson(str);
  }

  window.addEventListener('DOMContentLoaded', init);
</script>

<div id="sample">
<div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:400px"></div>
<p>Add a row or Remove the second row of the table held by the selected node:</p>
<button onclick="insertIntoArray()">Insert Into Array</button>
<button onclick="removeFromArray()">Remove From Array</button>
<p>Add a column or Remove the fourth column from the table of the selected node:</p>
<button onclick="addColumn()">Add Column</button>
<button onclick="removeColumn()">Remove Column</button>
<p>Swap the "phone" and "office" columns for each selected node:</p>
<button onclick="swapTwoColumns()">Swap Two Columns</button>
<button id="loadModel" onclick="load()">Load</button>
<button id="saveModel" onclick="save()">Save</button>
<textarea id="mySavedModel" style="width:100%;height:250px"></textarea>
</div>

This depends on a custom ColumnResizingTool, shown below.

The principle change is the addition of a conversion function for the table headers itemArray binding to data.columnDefinitions. It assumes there is an addition property in each column descriptor that specifies the RowColumnDefinition.width. Since one cannot have a binding on that property inside a “TableRow” Panel, the conversion function is what updates that property from the data. Note that it is the “Table” Panel that has RowColumnDefinitions, not the “TableRow” Panel.

The other changes to the sample are just to save and load the model in a textarea, so that you can see the changes to the model after using the customized ColumnResizingTool or after undo/redo.

The published ColumnResizingTool, like all of the published tools, does not know anything about the model schema. So it does not have any knowledge about exactly which data properties are used nor what their types and values are. That’s what Bindings are for.

But since we cannot use Bindings in this case, I have customized the ColumnResizingTool.resize method. Here is the additional code, at the end of the method:

  // originally present:
  coldef.width = Math.max(0, locpt.x - pad.left - coldef.position - (coldef.total - coldef.actual) - sep/2);
  // additional code to update the columnDefinitions data:
  var node = table.part;
  var coldefarray = node.data.columnDefinitions;
  if (coldefarray) {
    for (var i = 0; i < coldefarray.length; i++) {
      var coldefdata = coldefarray[i];
      if (coldefdata && coldefdata.column === h.column) {
        node.diagram.model.set(coldefdata, "width", coldef.width);
        break;
      }
    }
  }

Also I updated ColumnResizingTool.makeAdornment to skip over zero-width columns, as you requested. That’s in the code below.

Here is the complete modified file:

"use strict";
/*
*  Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
*/

// A custom Tool for resizing each column of a named Table Panel in a selected Part.

/*
* This is an extension and not part of the main GoJS library.
* Note that the API for this class may change with any version, even point releases.
* If you intend to use an extension in production, you should copy the code to your own source directory.
* Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
* See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
*/

/**
* @constructor
* @extends Tool
* @class
*/
function ColumnResizingTool() {
	go.Tool.call(this);
	this.name = "ColumnResizing";

	var h = new go.Shape();
  h.geometryString = "M0 0 V14 M2 0 V14";
  h.desiredSize = new go.Size(2, 14);
  h.cursor = "col-resize";
  h.geometryStretch = go.GraphObject.None;
  h.background = "rgba(255,255,255,0.5)";
  h.stroke = "rgba(30,144,255,0.5)";
	/** @type {GraphObject} */
	this._handleArchetype = h;

  /** @type {string} */
	this._tableName = "TABLE";

  // internal state
  /** @type {GraphObject} */
	this._handle = null;
  /** @type {Panel} */
	this._adornedTable = null;
}
go.Diagram.inherit(ColumnResizingTool, go.Tool);


/*
* A small GraphObject used as a resize handle for each column.
* This tool expects that this object's {@link GraphObject#desiredSize} (a.k.a width and height) has been set to real numbers.
* @name ColumnResizingTool#handleArchetype

* @return {GraphObject}
*/
Object.defineProperty(ColumnResizingTool.prototype, "handleArchetype", {
  get: function() { return this._handleArchetype; },
  set: function(value) { this._handleArchetype = value; }
});

/*
* The name of the Table Panel to be resized, by default the name "TABLE".
* @name ColumnResizingTool#tableName

* @return {string}
*/
Object.defineProperty(ColumnResizingTool.prototype, "tableName", {
  get: function() { return this._tableName; },
  set: function(value) { this._tableName = value; }
});

/*
* This read-only property returns the {@link GraphObject} that is the tool handle being dragged by the user.
* This will be contained by an {@link Adornment} whose category is "ColumnResizing".
* Its {@link Adornment#adornedObject} is the same as the {@link #adornedTable}.
* @name ColumnResizingTool#handle

* @return {GraphObject}
*/
Object.defineProperty(ColumnResizingTool.prototype, "handle", {
  get: function() { return this._handle; }
});

/*
* Gets the {@link Panel} of type {@link Panel#Table} whose columns may be resized.
* This must be contained within the selected Part.
* @name ColumnResizingTool#adornedTable

* @return {Panel}
*/
Object.defineProperty(ColumnResizingTool.prototype, "adornedTable", {
  get: function() { return this._adornedTable; }
});


/**
* Show an {@link Adornment} with a resize handle at each column.
* Don't show anything if {@link #tableName} doesn't identify a {@link Panel}
* that has a {@link Panel#type} of type {@link Panel#Table}.
* @this {ColumnResizingTool}
* @param {Part} part the part.
*/
ColumnResizingTool.prototype.updateAdornments = function(part) {
  if (part === null || part instanceof go.Link) return;  // this tool never applies to Links
  if (part.isSelected && !this.diagram.isReadOnly) {
    var selelt = part.findObject(this.tableName);
    if (selelt instanceof go.Panel && selelt.actualBounds.isReal() && selelt.isVisibleObject() &&
        part.actualBounds.isReal() && part.isVisible() &&
        selelt.type === go.Panel.Table) {
      var table = selelt;
      var adornment = part.findAdornment(this.name);
      if (adornment === null) {
        adornment = this.makeAdornment(table);
        part.addAdornment(this.name, adornment);
      }
      if (adornment !== null) {
        var pad = table.padding;
        var numcols = table.columnCount;
        // update the position/alignment of each handle
        adornment.elements.each(function(h) {
          if (!h.pickable) return;
          var coldef = table.getColumnDefinition(h.column);
          var wid = coldef.actual;
          if (wid > 0) wid = coldef.total;
          var sep = 0;
          // find next non-zero-width column's separatorStrokeWidth
          var idx = h.column + 1;
          while (idx < numcols && table.getColumnDefinition(idx).actual === 0) idx++;
          if (idx < numcols) {
            sep = table.getColumnDefinition(idx).separatorStrokeWidth;
            if (isNaN(sep)) sep = table.defaultColumnSeparatorStrokeWidth;
          }
          h.alignment = new go.Spot(0, 0, pad.left + coldef.position + wid + sep/2, pad.top + h.height/2);
        });
        adornment.locationObject.desiredSize = table.actualBounds.size;
        adornment.location = table.getDocumentPoint(adornment.locationSpot);
        adornment.angle = table.getDocumentAngle();
        return;
      }
    }
  }
  part.removeAdornment(this.name);
};

/*
* @this {ColumnResizingTool}
* @param {Panel} table the Table Panel whose columns may be resized
* @return {Adornment}
*/
ColumnResizingTool.prototype.makeAdornment = function(table) {
  // the Adornment is a Spot Panel holding resize handles
  var adornment = new go.Adornment();
  adornment.category = this.name;
  adornment.adornedObject = table;
  adornment.type = go.Panel.Spot;
  adornment.locationObjectName = "BLOCK";
  // create the "main" element of the Spot Panel
  var block = new go.TextBlock();  // doesn't matter much what this is
  block.name = "BLOCK";
  block.pickable = false;  // it's transparent and not pickable
  adornment.add(block);
  // now add resize handles for each column
  for (var i = 0; i < table.columnCount; i++) {
    var coldef = table.getColumnDefinition(i);
    if (coldef.actual > 0) {
      adornment.add(this.makeHandle(table, coldef));
    }
  }
  return adornment;
};

/*
* @this {ColumnResizingTool}
* @param {Panel} table the Table Panel whose columns may be resized
* @param {RowColumnDefinition} coldef the column definition to be resized
* @return a copy of the {@link #handleArchetype}
*/
ColumnResizingTool.prototype.makeHandle = function(table, coldef) {
  var h = this.handleArchetype;
  if (h === null) return null;
  var c = h.copy();
  c.column = coldef.index;
  return c;
};


/*
* This predicate is true when there is a resize handle at the mouse down point.
* @this {ColumnResizingTool}
* @return {boolean}
*/
ColumnResizingTool.prototype.canStart = function() {
  if (!this.isEnabled) return false;

  var diagram = this.diagram;
  if (diagram === null || diagram.isReadOnly) return false;
  if (!diagram.lastInput.left) return false;
  var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
  return (h !== null);
};

/**
* @this {ColumnResizingTool}
*/
ColumnResizingTool.prototype.doActivate = function() {
  var diagram = this.diagram;
  if (diagram === null) return;
  this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
  if (this.handle === null) return;
  var panel = this.handle.part.adornedObject;
  if (!panel || panel.type !== go.Panel.Table) return;
  this._adornedTable = panel;
  diagram.isMouseCaptured = true;
  this.startTransaction(this.name);
  this.isActive = true;
};

/**
* @this {ColumnResizingTool}
*/
ColumnResizingTool.prototype.doDeactivate = function() {
  this.stopTransaction();
  this._handle = null;
  this._adornedTable = null;
  var diagram = this.diagram;
  if (diagram !== null) diagram.isMouseCaptured = false;
  this.isActive = false;
};

/**
* @this {ColumnResizingTool}
*/
ColumnResizingTool.prototype.doMouseMove = function() {
  var diagram = this.diagram;
  if (this.isActive && diagram !== null) {
    var newpt = this.computeResize(diagram.lastInput.documentPoint);
    this.resize(newpt);
  }
};

/**
* @this {ColumnResizingTool}
*/
ColumnResizingTool.prototype.doMouseUp = function() {
  var diagram = this.diagram;
  if (this.isActive && diagram !== null) {
    var newpt = this.computeResize(diagram.lastInput.documentPoint);
    this.resize(newpt);
    this.transactionResult = this.name;  // success
  }
  this.stopTool();
};

/**
* This should change the {@link RowColumnDefinition#width} of the column being resized
* to a value corresponding to the given mouse point.
* @expose
* @this {ColumnResizingTool}
* @param {Point} newPoint the value of the call to {@link #computeResize}.
*/
ColumnResizingTool.prototype.resize = function(newPoint) {
  var table = this.adornedTable;
  var pad = table.padding;
  var numcols = table.columnCount;
  var locpt = table.getLocalPoint(newPoint);
  var h = this.handle;
  var coldef = table.getColumnDefinition(h.column);
  var sep = 0;
  var idx = h.column + 1;
  while (idx < numcols && table.getColumnDefinition(idx).actual === 0) idx++;
  if (idx < numcols) {
    sep = table.getColumnDefinition(idx).separatorStrokeWidth;
    if (isNaN(sep)) sep = table.defaultColumnSeparatorStrokeWidth;
  }
  coldef.width = Math.max(0, locpt.x - pad.left - coldef.position - (coldef.total - coldef.actual) - sep/2);
  var node = table.part;
  var coldefarray = node.data.columnDefinitions;
  if (coldefarray) {
    for (var i = 0; i < coldefarray.length; i++) {
      var coldefdata = coldefarray[i];
      if (coldefdata && coldefdata.column === h.column) {
        node.diagram.model.set(coldefdata, "width", coldef.width);
        break;
      }
    }
  }
};


/**
* This can be overridden in order to customize the resizing process.
* @expose
* @this {ColumnResizingTool}
* @param {Point} p the point where the handle is being dragged.
* @return {Point}
*/
ColumnResizingTool.prototype.computeResize = function(p) {
  return p;
};

/**
* Pressing the Delete key removes any column width setting and stops this tool.
* @this {ColumnResizingTool}
*/
ColumnResizingTool.prototype.doKeyDown = function() {
  if (!this.isActive) return;
  var e = this.diagram.lastInput;
  if (e.key === 'Del' || e.key === '\t') {  // remove width setting
    var coldef = this.adornedTable.getColumnDefinition(this.handle.column);
    coldef.width = NaN;
    this.transactionResult = this.name;  // success
    this.stopTool();
  } else {
    go.Tool.prototype.doKeyDown.call(this);
  }
};

I will test it, but first did you test delete 3rd column after resize 4th and 5th column? total columns is 5.

by the way, I did almost same as yours code in my project. I added cwidth property to column array. you added the width property to the column array. :)

I tested, thank you for long sample code. the work around method is skip over zero-width columns i understand this. it can hide the deleted column after delete. but the column count still not change.
I need correct column numbers and column count number.
i still can’t complete my code in my project.

look at this

and kindly look the console log when add column and delete them.

And there is also have other problem,

applyIncrementalJson
the reproduce way is, add a column, resize the column, and delete the column.

The Panel.columnCount property returns the length of the Array used to hold column RowColumnDefinitions for that panel.

If you call Panel.getColumnDefinition you will always get an instance of RowColumnDefinition. Internally the Array is sparse, so you will get behavior such as:

var tab = node.findObject("TABLE");
assert(tab.columnCount === 3);

tab.getColumnDefinition(17);  // zero-based index
assert(tab.columnCount === 18); 

tab.getColumnDefinition(6);
assert(tab.columnCount === 18); 

tab.removeColumnDefinition(6);
assert(tab.columnCount === 18); 

tab.removeColumnDefinition(17);
assert(tab.columnCount === 3);

If you want to count the number of non-zero width columns, you could count them yourself. But don’t go beyond columnCount-1, or else you will be adding columns…

Thank you for kindly explain for getColumnDefinition, it always return an instance, I got it.

So how about I mentioned those problems. the original one is, we delete the Item from array, the columnCount and the column number not get correctly, look the sample, after delete it still return 5 or something bigger than actual columns number. And, second one is, today I added, the applyIncrementalJson problem.

Just my thinking not much sure, I think workaround is limited for solve those problems. actually my production code is much Similar to your sample. again thank you long code sample. I really want use table, because it so convenient.