Skip to content

Design Discussions Viewport Behaviour

chrisjstevo edited this page Jun 3, 2021 · 29 revisions

In order to create customised functionality for grids within the viewport either the client code has to be specialized to add the behaviour, or the server must be able to describe the behaviour and make it available to the client.

One mechanism to describe this on the server side could be:

      .addTable(
          TableDef(
            name = "instruments",
            keyField = "ric",
            columns = Columns.fromNames("ric".string(), "description".string(), "bbg".string(), "isin".string(), "currency".string(), "exchange".string(), "lotSize".int()),
            VisualLinks(),
            joinFields = "ric"
          ),
          (table, vs) => new SimulatedBigInstrumentsProvider(table),
          ViewPortDef(
             columns = ...,
             formatting = TextFormat("ric", LeftAlign) ++ TickUpDownFormat("price", RightAlign),
             menuOptions = InstrumentsRpcService.menuItems(),
             cellEditService = classOf[InstrumentsRpcService]
          )
      )

InstrumentsRpcService would then describe the non-standard behaviour we'd want to add to the grid. This could look like:

class InstrumentsRpcService extends RpcService with DataEntry { 

  def menuItems():MenuItems = {
    MenuItems(
       Menu("Edit", 
           MenuItem("Add Row", InstrumentsRpcService.onRowAdd ),
           MenuItem("Delete Row", InstrumentsRpcService.onRowDelete ),
       ),
       Menu("Send", 
           MenuItem("to VWAP", InstrumentsRpcService.sendToVwap ),
           MenuItem("to TWAP", InstrumentsRpcService.sendToTwap ),
       ),
   )
  }
}

When a new viewport is created by the client, we would offer additional meta to describe its available menu items to the client. This would then show menu's on right click if they are defined.

However, in order for this callback mechanism to the server to work, we'd need to be able to tell the UI to do certain actions when the rpc call completed, as a return value.

Take for example the scenario where we want to select a bunch of instruments from the instrument grid and then open a new model order entry grid for those instruments.

We would define the table and viewport descriptions as above, however we would have a MenuItem of:

def menuItems():MenuItems = {
    MenuItems(
       Menu("Create", 
           MenuItem("Create New Basket", InstrumentsRpcService.creatNewBasket ),
           ...
          ),

Create new basket could well have an implementation something like this on the server:

  def createNewBasket()(vpContext: ViewPortContex) = {
   
      val selectedInstruments = vpContext.currentViewport.selected

      val sessionTable = vpContext.tableContainer.createNewSessionTable(vpContext.tableDefs.get("orderEntry"))

      selectedInstruments.foreach( ric => sessiontable.tick( ric, Map("ric" -> ric ) )

  }

However on completion of that call we want to tell the client to open a new modal viewport grid onto a session table we've just created. In order to do that we'd need to return from the RPC service a set of defined client side actions, one of which has to be OpenModalViewport(tableName).

  def createNewBasket()(vpContext: ViewPortContex):ClientAction = {
   
      val selectedInstruments = vpContext.currentViewport.selected

      val sessionTable = vpContext.tableContainer.createNewSessionTable(vpContext.tableDefs.get("orderEntry"))

      selectedInstruments.foreach( ric => sessiontable.tick( ric, Map("ric" -> ric ) )

      OpenModalViewPort(sessionTable.name)
  }

The end result would be something like this.

Likewise if you added a call to submit all orders to market, the rpc call may return CloseViewPort(vpId) if everything is good, or you may want to leave the dialog showing if there was an exception.

Filtering Menu Actions based on Context

It's likely to be desirable to filter actions based on the context of the selected rows, for example if we had an action "Cancel Order" we would potentially want to specify a criteria on the menu action such that we could only cancel active orders.

One way to define this might be in the definition of the MenuAction...

MenuItems(
       Menu("Create", 
           MenuItem("Cancel Row", OrderService.cancelOrders, "\row -> row.STATUS == 'ACTIVE' " ),
           ...
          ),

This could be evaluated on the server, but that is likely to be wasteful. It would be better to be able to evaluate the criteria on the client.

Action / Context Menu Configuration for client

On receiving CREATE_VP_SUCCESS, client makes request to GET_VP_ACTIONS, passing viewPortId, response might look something like this

{
  //menu means its a folder within the right click menu, 
  //you can have folders within folders within folders, if you were insane...
  "menus" : Menu{
    "label" : "&Cancel.."
    "items" : [
  {
    "context": "grid",
    "label": "Cancel all orders",
    "action": "MyService.cancelOrders"
  },
  {
    "context": "selected-rows",
    "label": "Cancel selected orders",
    "condition": {
      "column": "status",
      "value": "active",
      "op": "eq"        
    },
    "action": "MyService.cancelSelectedOrders"
  },
  {
    "context": "row",
    "condition": {
      "column": "status",
      "value": "active",
      "op": "eq"    
    },
    "action": "MyService..."
  },
  ]},
  //this one will appear in root menu
  items : [{
    "click": "cell",
    "condition": {
      "column": "price",
      "value" : 0,
      "op": "gt"
    },
    "action": "RFQ"
  }]

]

We could have a convention for what parameters are sent for each context:

  • row: the row index
  • selected-rows: the selected row indices
  • cell - the row index plus column name plus value at time of click

etc

Note: one of the above is a click instruction rather than context menu - just a thought . Could equally have context menu entries at the cell level if it made sense. For the conditions, where context is row the value associated with the named column must match the condition. For cell level, the cell clicked must be the column named in the condition, as well as satisfying whatever op/value is specified.

First Cut...

{
  "name": "ROOT",
  "menus": [
    {
      "name": "Insert",
      "menus": [
        {
          "name": "Duplicate Row(s)",
          "filter": "",
          "rpcName": "DUPLICATE",
          "context": "selected-rows"
        }
      ]
    },
    {
      "name": "Edit",
      "menus": [
        {
          "name": "Edit Cell",
          "filter": "",
          "rpcName": "EDIT_CELL",
          "context": "cell"
        },
        {
          "name": "Edit Row",
          "filter": "",
          "rpcName": "EDIT_ROW",
          "context": "row"
        }
      ]
    },
    {
      "name": "Delete",
      "menus": [
        {
          "name": "Delete Row(s)",
          "filter": "",
          "rpcName": "DELETE_ROWS",
          "context": "selected-rows"
        },
        {
          "name": "Delete All Contents",
          "filter": "",
          "rpcName": "DELETE_ALL",
          "context": "grid"
        }
      ]
    },
    {
      "name": "Show Details",
      "filter": "",
      "rpcName": "SHOW_DETAILS",
      "context": "selected-rows"
    }
  ]
}