Search code examples
gojs

gojs nested setDataProperty


Originally my node data looked something like this:

{
  key: uuid,
  loc: "x y",
  color: color,
  param1: val1,
  param...:val...,
  paramn: valn
}

I decided I wanted to split my gojs details and node data into this:

{
  meta: {
    key: uuid,
    loc: "x y",
    color: color
  }.
  data: {
    param1: val1,
    param...:val...,
    paramn: valn
  }
}

but gave up on moving the key out since it looks like that needs to be in root and settled on this:

{
  key: uuid,
  meta: {
    loc: "x y",
    color: color
  }.
  data: {
    param1: val1,
    param...:val...,
    paramn: valn
  }
}

I updated the simple one-way binding with stuff like this:

// old way
new go.Binding('fill', 'color')

// new way
new go.Binding('fill', 'meta', meta => meta.color)

For the two-way binding I hit some snags adopting the fromLocation function as follows:

// This worked fine with a flat state:
// new go.Binding('location', 'loc', go.Point.parse).makeTwoWay(go.Point.stringify),
    
// I decided to move my stat into a sub object called meta with the following:
new go.Binding('location', 'meta', toLocation).makeTwoWay(fromLocation)

It wasn't clear to me that I needed to stringify the location, so I'm not; but because I have some legacy diagrams saved I'm still handling that for the time being

// This works just fine
const toLocation = (meta, node) => {
  // pulls the loc out of the meta, and parses into go.point
  let output = meta.loc;

  // TODO: Shouldn't need to check for a string since we're storing raw values now
  if (typeof output === 'string') {
    output = go.Point.parse(output);
  }
  console.log('toLocation:', { output, meta });

  return output;
};

I get really odd behavior in the fromLocation:

const fromLocation = (loc, data, model) => {
  // writes the loc as a string to the meta prop
  console.log('fromLocation IN:', { data: _.cloneDeep(data) });

  const meta = _.cloneDeep(data.meta);
  meta.loc = loc;

  console.log('model.nodeDataArray:', model.nodeDataArray);

  if (typeof data.meta !== 'undefined') {
    console.log('fromLocation has meta:', { meta: data.meta, loc });
  }

  this.diagramReferenceService.startTransaction('updating meta');

  model.setDataProperty(data, 'meta', meta);
  data.meta = meta;

  this.diagramReferenceService.commitTransaction('updating meta');

  console.log('fromLocation OUT:', { data: _.cloneDeep(data) });

};

when I look at the lodash clone the meta is populated, but something about the way this step is executing is setting the meta props to undefined, but it doesn't look like any of my other code is modifying it after this set point.


I adopted the code from here: https://gojs.net/extras/bindingSubProperties.html

as follows:

    const makeTwoWaySubBinding = (rootkey, targetname, sourcename, conversion, backconversion) => {
      console.log('makeTwoWaySubBinding:', { rootkey, targetname, sourcename, conversion, backconversion })
      const bind = new go.Binding(targetname, rootkey);
      bind.mode = go.Binding.TwoWay;

      bind.converter = (root, target) => {
        const value = root[sourcename];
        if (value === undefined) {
          return target[targetname];
        }
        return (typeof conversion === 'function') ? conversion(value, target) : value;
      };

      bind.backConverter = (value, data, model) => {
        const root = data[rootkey];
        if (model) {
          if (typeof backconversion === 'function') {
            model.setDataProperty(root, sourcename, backconversion(value, root, model));
          } else {
            model.setDataProperty(root, sourcename, value);
          }
        } else {
          root[sourcename] = (typeof backconversion === 'function') ? backconversion(value, root, model) : value;
          return root;
        }
      };

      return bind;
    };

their example stringified, so I went with it and invoked as follows:

makeTwoWaySubBinding('meta', 'location', 'loc', go.Point.parse, go.Point.stringify)

I'm having the same issue though


Solution

  • const makeLocationTwoWayBind = () => {
    
      const locationTwoWayBind = new go.Binding('location', 'meta');
      locationTwoWayBind.mode = go.Binding.TwoWay;
      locationTwoWayBind.converter = (meta, targetObj) => {
        // gets the location from the node data
        const loc = go.Point.parse(meta.loc);
        return loc;
      };
      locationTwoWayBind.backConverter = (loc, nodeData, model) => {
        // updates the location on the nodeData
        const meta = Object.assign(_.cloneDeep(nodeData.meta), { loc: go.Point.stringify(loc) });
        return meta;
      };
    
      return locationTwoWayBind;
    };
    

    So the issue was in part that I was still using loc Also, I'm not sure why the example code uses the model method setDataProperty it seems like the converters perform similar behaviors as simple returns


    small rant

    The `converter` and `backConverter` were -- IMO -- counterintuitively named.
    The converter takes the data from the model and applies to to the diagram,
    the back converter saves data to the model
    
    that became clear after looking at the input params,
    but gave me pause a number of times
    
    the model function names `toLocation` and `fromLocation`
    don't really help
    I would consider using something like marshall and unmarshall instead.
    
    marshall = backConverter = fromLocation
    unmarshall = converter = toLocatation
    

    anyway; having the converter simply return the parsed location was sufficient, the backConverter needed to return meta since meta was the key specified in the go.Binding constructor. I handled updating the loc param of meta in the backConverter then returned the updated meta

    so now I have what I was looking for:

    {
      key: uuid,
      meta: {
        loc: "x y", <--- locationTwoWayBind handles this
        color: color
      }.
      data: {
        param1: val1,
        param...:val...,
        paramn: valn
      }
    }