Links and two-way data binding to nested property

The example at Dynamic Ports shows how to do two-way binding assuming the data for the model is flat, but I’m trying to bind all positional information under a ‘metadata’ object in my model.

I can store and retrieve the location for nodes using the following code:
new go.Binding("location", "metadata", m => go.Point.parse(m.loc)).makeTwoWay((val, obj) => { obj.metadata.loc = go.Point.stringify(val); return obj.metadata; }),
(although please tell me if there’s a cleaner way)

but I can’t see a sensible way of doing the same with the List. Is there a hook into the code used by the underlying Model.toJSON that can be used?

Take a look at this sample: Binding data sub-properties

Note how it defines two functions, one to create a OneWay Binding and one to create a TwoWay Binding, which assume the data property is actually on a “details” sub-object. Of course you can rename that to be “metadata” or whatever you like, including more complex paths.

Note also that that sample just demonstrates subproperty data bindings. If you want to put the node “key” or link “from” or link “to” properties on subobjects, that can be done by providing a getter-and-setter function as the Model.nodeKeyProperty, GraphLinksModel.linkFromKeyProperty, or GraphLinksModel.linkToKeyProperty properties.

If you are binding Panel.itemArray, I don’t see why that would be treated any differently.

Thanks, that snippet is indeed much tidier, but this still does not work for a bezier curve points collection.
As the curve is moved, it does indeed fire the binding, and the underlying model is updated as expected, but as soon as a node is moved and the graph tries to recalculate the curve points it resets them all so the curve returns to the default shape (a slight, aesthetically pleasing curve).

As a side-note, I’m not sure if something is slightly out of date, but the bind.backConverter = function(value, data, model) function is no longer correct as backConverter only takes two arguments, so either the source is outdated or the definitions are wrong.

Below a sample of a persisted Link object. The values in metadata have been stored, but don’t seem to be treated properly when deserialised.

Even more confusing, is I’ve just replaced the whole thing with new go.Binding("points").makeTwoWay() to allow it to persist it on the main model object, and the same thing occurs. The value is stored, but moving a node regenerates the 4 points in the list.

{ "from": 3, "to": -2, "description": "transition", "metadata": { "points": { "__gohashid": 88121, "D": true, "n": [ { "x": -132.52920764881796, "y": 41.68902744281234, "D": true }, { "x": -97.15153936734711, "y": 33.77192340342489, "D": true }, { "x": -62.348602718417695, "y": 33.07586467044631, "D": true }, { "x": -26.66986146970431, "y": 38.95031471453555, "D": true } ], "F": 6, "Xb": null, "vj": null } }, "__gohashid": 85574 }

Ah, you are now asking about a routing feature, not about data binding. Did you want the behavior as you can see in the State Chart sample, State Chart ? If so, set the Link.adjusting property. Probably you want: adjusting: go.Link.Stretch.

Regarding the extra model argument to the Binding.backConverter function – that’s new in version 1.7, which is currently only available as an alpha release. (Replace “latest” with “alpha” in all URLs.) Sorry about that.

Argh! After all that, I must have accidentally deleted the ‘adjusting’ from my Link template!
That sample is exactly the one I was basing the code on, and I must have removed it while fiddling.
The behavior is present without that flag, but I assume the serialise/deserialise simply ignores them without it.

As a final ‘tidy things up’, is there a way to access the internal ‘Convert a list of points to an array of numbers’ as the Model.toJSON does, or do I have to code one?
i.e. convert it to the default of
"points": [-393.03405931480495, -7.465887042812934, -314.69627947843963, -6.830269061542473, -238.9917966650773, -2.443085523172494, -169.99999909595581, -2.5401204652682345],
?

Many thanks!!

Sorry, but that functionality is currently not exported for independent use. Here’s the code:

      var ap = /** @type {List.<Point>} */ (v);
      var s = '[';
      var it = ap.iterator;
      while (it.next()) {
        var pt = it.value;
        if (s.length > 1) s += ',';
        s += writeNumber(pt.x);
        s += ',';
        s += writeNumber(pt.y);
      }
      s += ']';

where:

function writeNumber(x) {
    if (x === Infinity) return '9e9999';
    if (x === -Infinity) return '-9e9999';
    if (isNaN(x)) return '{\"class\":\"NaN\"}';
    return x.toString();
}

Awesome, thanks, I’ll give that a spin!