Expanding node templates / accessing part.elements

Hello,

I want to add a little bit of interface to my nodes. I have a general question of what’s a good way to do this, and a specific question I was led to in my first attempt.

My data for one node looks like:
{ “title”:“hello”, “states”:[ {“desc”:“A1”, “complicated”:info1}, {“desc”:“A2”, “complicated”:info2}, {“desc”:“A3”, “complicated”:info3} ]
I want my nodes to essentially have two states, where > and < are buttons/elements with onClick events to toggle between the two:

   State 1                     State 2
-------------             ---------------------------------
|   Title   |             |      Title     |       A2     |
| A1      > |             | A1      >      |  info2a    W |
| A2      > |    or       | A2          <  |  info2b    X |
| A3      > |             | A3      >      |  info2c    Y |
-------------             |                |  info2d    Z |
                          ---------------------------------

I can imagine this is a common pattern.
Question 1. is there already have a sample that implements this, or a hook that I can use?, or could you please give some general design advice for this?

In the meantime, this is what I was thinking:
I have set up the node template with two separate Tables, each with new go.Binding(“itemArray”,“states”). One is vertical, and just contains the “desc” A1, A2, A3, … The second table is a horizontal table of tables, each of which shows the complicated info for one state (above, example for state A2 with complicated info info2a, info2b, …); all of these subtables are visible:false by default. When someone clicks on an “>”, I can use the onClick event to find out that it was the i-th state which was clicked, then iterate over the components of the node until I find the i-th table (that contains the detailed breakdown for state i) and set visible:true. At the same time, I set visible:false on the expander “>” and visible:true on the collapser “<”, and possibly do more.

I actually have a function for the onClick event which comes very close. But it sets visible:true/false on the wrong components in the node. This lead me to the question:
Q2. Is there a convenient way to examine, in JS, the elements in the node, to find out what they consist of?
Currently I access node.elements, but this is a generator not an array, so I go over node.elements.iterator to check each element – and these then have subelements, so I end up checking X.elements.iterator a few layers deep. But I’m basically blind – in the debugger, I cannot tell what elements.iterator.value is referring to at all. Are there API functions to access the elements of a node/panel/part conveniently? So I get a list of elements, with a dictionary of their information, like “type”:“TableRow” and “column”:1 and “text”:“info2b”, and so on?

  1. Not exactly, but you could consider either using the “PanelExpanderButton” or something similar to it that you could define. See GoJS Buttons -- Northwoods Software, http://gojs.net/latest/extensions/Buttons.js, and UML Class Nodes or IVR Tree. I guess you want the buttons to act like radio buttons? You might want to implement your own “PanelExpanderRadioButton”.

  2. Use Panel.elt to access the immediate elements of a panel by zero-based integer index. I’m not sure what you really need, though.

Hi Walter, thank you.

For Q1, PanelExpanderButton seems to hit the spot. How can I dynamically set the name of the panel to show/hide? I.e. it will be part of my itemTemplate, so is there a go.Binding(??,X) that I can apply? Static assigning of the panel name wouldn’t work (in the example, “LIST1” and “LIST2” are hard-coded).

For Q2, thanks, I’ll play around with things a bit more to get familiar.

I can take a look at how to implement this tomorrow. Well, later today.

Take a good look at: Minimal GoJS Sample

Hi Walter,

That looks decent, and your code is very clear. Thanks, it temporarily solves my problem.

In general, it’s necessary for me to be able to show several states, hence just re-assigning the data underlying the Details panel doesn’t make sense. Again the neat way of doing it seems to be to be able to parametrise the target of the expander. Is it possible – in setting the onClick event – to refer to one of the button’s own properties (e.g. “target”)? Or do you see another way of doing this?

The findObject function is handy, I learned that here.

F

Sorry, but I assumed you could only show at most one state’s details at a time, and you didn’t imply otherwise.

Well, if you can show more than one at a time, and if you would want all of the details for any state to stay within that state’s row, that would simplify things a lot – just put that “DETAILS” Panel inside the “ITEMS” TableRow Panel in column 3 or whatever.

But if you want all of the details for multiple states to show together in a single vertical list, I think you’ll need to programmatically assign the “DETAILS” Panel.itemArray to an Array that you construct dynamically. That also has the advantage that you can sort the detailed items in whatever order makes sense for your users.

Hi Walter,

I realised as soon as I read your answer that I hadn’t stated my problem appropriately. Sorry. But I do think your helpful answer is a step in the right direction for me.

It would be good to furthermore allow also the following possibility, see the below diagram. Do you have any thoughts on implementation? As I understand it, you suggest to put the Details-i table inside the row for State-i. In this case for layout purposes/user’s comprehension of the info I would rather show them off to the side.

     State 3
----------------------------------------------
|      Title     |      A2     |      A4     |
| A1      >      | info2a    U | info4a    G |
| A2          <  | info2b    V | info4b    H |
| A3      >      | info2c    W | info4c    I |
| A4          <  | info2d    X | info4d    J |
|                | info2e    Y | info4e    K |
|                | info2f    Z | info4f    L |
----------------------------------------------

As before, my implementation idea is still to have N details tables in the node template (generated using itemArray/itemTemplate, one table for each of the N states), but all of them have visible: false unless the corresponding arrow > is clicked that sets visible: true on the appropriate individual.

Would it be possible to do the following: give each details table new go.Binding("name","desc") and, for the buttons, do

$(go.Shape, "TriangleRight",
  new go.Binding("target", "desc"),
  {
    name: "ARROW",
    fill: "gray",
    click: function(e, shape) {
          e.handled = true;
          e.diagram.startTransaction();
          var details = shape.part.findObject(this.data.target);  // this.data.target ??
          ...

I.e. can the JS code for the click event access properties of the go.Shape which calls it, like its own name, fill, or – here – ‘target’? This could be a way of parametrising it, if it’s possible.

I think you could have a “Table” Panel whose Panel.itemArray was bound to the array of “states”. The Panel.itemTemplate would be a “TableColumn” Panel containing the same 2-column “Table” Panel that I used to show the details for a particular state. Yes, you would set visible: false on the “TableColumn” Panel, which you could programmatically toggle in your click event handler.

Yes, from the second argument to a click event handler you can traverse the whole visual tree to find whatever you need. But I do suggest giving the container panels (i.e. the one whose Panel.itemArray is set or bound) a name so that you can call findObject on the Node.

My solution is as follows.

// data for one node:
{ text:"Node 1"
, states:[
   { text:"A", vector:["a","b","c","d","e"] }
  ,{ text:"B", vector:["f","g","h","i","j"] }
  ,{ text:"C", vector:["k","l","m","n","o"] }

// want to display:
  ------------------------------
  |  Node 1  |  A  |  B  |  C  |
  | A   >/<  |  a  |  f  |  k  |
  | B   >/<  |  b  |  g  |  l  |
  | C   >/<  |  c  |  h  |  m  |
  |          |  d  |  i  |  n  |
  |          |  e  |  j  |  o  |
  ------------------------------
//always vis.//
         // Any of A/B/C only visible
         // if their expanders ">"s are clicked

// node template:
$(go.Node, "Auto",
  $(go.Shape, "Rectangle",
    $(go.Panel, "Horizontal",
      statesOverview, // always visible, with >/< expander button
      breakdownView)))  // contains A, B, C
statesOverview = $(go.Panel, "Vertical",
  $(go.TextBlock, new go.Binding("text", "text"), // title
    $(go.Table, new go.Binding("itemArray", "states"),
      { itemTemplate: $(go.Panel, "TableRow",
        $(go.TextBlock, new go.Binding("text", "text"), { column: 0 } ),
        $(go.TextBlock, { column: 1, text: ">", click: showState } ) } ) ) )
breakdownView = $(go.Panel, "Horizontal", // table of tables construction
  new go.Binding("itemArray", "states")
  { name: "breakdowns",
    itemTemplate: $(go.Panel, "Vertical",
      $(go.TextBlock, new go.Binding("text", "text"),
      $(go.Table, new go.Binding("itemArray", "vector"),
        { itemTemplate: $(go.TextBlock, new go.Binding("text", "")) } ),
      new go.Binding("name", "text"),
      { visible: false }
    ) } )

// click event handling
function showState( event, part ) {
  var row = part.panel
  var key = row.data.text // here, key = A or B or C
  var node = row.panel.part
  part.diagram.startTransaction("show state")
  node.findObject("breakdowns").findObject(key).visible = true
  part.text = "<"
  part.click = "hideState"
  part.diagram.commitTransaction("show state")
}
function hideState( event, part ) {
  var row = part.panel
  var key = row.data.text // here, key = A or B or C
  var node = row.panel.part
  part.diagram.startTransaction("show state")
  node.findObject("breakdowns").findObject(key).visible = false
  part.text = ">"
  part.click = "showState"
  part.diagram.commitTransaction("show state")
}

The key was the findObject function and using new go.Binding("name", "whatever"). I do think it would be even neater if there was a binding for setting the target of a PanelExpanderButton.

Thanks so much for your help Walter.