Web Core / XData Service Query Example v2

(Whoops, I guess because I marked the last one as 'solved' I can't add to it anymore?)

Alright, a bit more fiddling and I've replaced the WebCore side of the example with the following functions and everything seems to work just as I want now. So, to recap, I was looking to have a service endpoint from XData return a TStream that I could then use within WebCore as a dataset without having to know anything about the fields ahead of time. As I'm using FireDAC, the JSON coming through via the TStream already has everything needed, just a matter of parsing it.

My "Main Menu" example now has three functions in the client. These first two can be put somewhere like in a utility function unit or something that isn't specific to a form:

function GetQueryData(Endpoint: String; Dataset: TXDataWebDataSet):Integer;
var
  Conn:     TXDataWebConnection;
  Client:   TXDataWebClient;
  Response: TXDataClientResponse;

  JFDBS:       TJSObject;
  JManager:    TJSObject;
  JTableList:  TJSObject;
  JColumnList: TJSArray;
  JRowList:    TJSArray;

  StringFields:  Array of TStringField;
  IntegerFields: Array of TIntegerField;
  // Likely need a few more datatypes here

  i: Integer;
begin

  // Value to indicate the request was unsuccessful
  Result := -1;

  // Setup connection to XData Server
  Conn              := TXDataWebConnection.Create(nil);
  Conn.URL          := DM1.CarnivalCoreServer;          // could also be a parameter
  Conn.OnRequest    := PopulateJWT;                     // See below
  Client            := TXDataWebClient.Create(nil);
  Client.Connection := Conn;
  await(Conn.OpenAsync);

  // Make the Request 
  // Likely to have another version of function that includes more endpoint parameters
  try
    Response := await(Client.RawInvokeAsync(Endpoint, ['FireDAC']));
  except
    on Error: Exception do
    begin
      Client.Free;
      Conn.Free;
      console.log('...something is amiss...');
      exit;
    end;
  end;

  // Process the FireDAC-specific JSON that was returned
  JFDBS       := TJSObject(TJSObject(TJSJson.Parse(string(Response.Result)))['FDBS']);
  JManager    := TJSObject(JFDBS['Manager']);
  JTableList  := TJSObject(TJSArray(JManager['TableList'])[0]);
  JColumnList := TJSArray(JTableList['ColumnList']);
  JRowList    := TJSArray(JTableList['RowList']);
  // Don't really want 'Original' in field names, so let's remove it from JSON first
  // Probably a better one-liner, but this seems to work
  for i := 0 to JRowList.Length - 1 do
    JRowList.Elements[i] := TJSObject(JRowList.Elements[i])['Original'];

  // We're assuming Dataset parameter is newly created and empty.  
  // First, add all the fields from JSON
  // NOTE: Very likely more datatypes need to be added here
  for i := 0 to JColumnList.Length-1 do
  begin
    if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'AnsiString') then
    begin
      // NOTE: Different datatypes may need different values set (eg: Size for strings)
      SetLength(StringFields, Length(StringFields) + 1);
      StringFields[Length(StringFields)-1] := TStringField.Create(Dataset);
      StringFields[Length(StringFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
      StringFields[Length(StringFields)-1].Size      := Integer(TJSObject(JColumnList.Elements[i])['Size']);
      StringFields[Length(StringFields)-1].Dataset   := Dataset;
    end
    else if (String(TJSObject(JColumnList.Elements[i])['DataType']) = 'Int32') then
    begin
      SetLength(IntegerFields, Length(IntegerFields) + 1);
      IntegerFields[Length(IntegerFields)-1] := TIntegerField.Create(Dataset);
      IntegerFields[Length(IntegerFields)-1].FieldName := String(TJSObject(JColumnList.Elements[i])['Name']);
      IntegerFields[Length(IntegerFields)-1].Dataset   := Dataset;
    end
    else
    begin
      console.log('ERROR: Field ['+String(TJSObject(JColumnList.Elements[i])['Name'])+'] has an unexpected datatype ['+String(TJSObject(JColumnList.Elements[i])['DataType'])+']');
    end;
  end;

  // Add the data and return the dataset as opened
  Dataset.SetJSONData(JRowList);
  Dataset.Open;

  // Just for fun
  Result :=  Dataset.RecordCount;

  // No dataset stuff to free as the dataset was created by the caller and 
  // all the fields created were created with that dataset as the parent

  Client.Free;
  Conn.Free;
end;

procedure PopulateJWT(Args: TXDataWebConnectionRequest);
begin
  Args.Request.Headers.SetValue('Authorization', JWT-From-Somewhere);
end;

With those out of the way, the previous RefreshMainMenu function is then much simpler, primarily just processing the dataset and not having to think about how it was retrieved:

procedure RefreshMainMenu;
var
  i: Integer;
  MenuGroup: TTreeNode;
  MenuGroupName: String;
  MenuDataset: TXDataWebDataSet;
  MenuRecords: Integer;
begin
  MenuDataset := TXDataWebDataset.Create(nil);
  MenuRecords := await(GetQueryData('ISomethingService.EndpointName', MenuDataset));

  if (MenuRecords = -1) then
  begin
     console.log('...Menu refresh not working...');
     exit;
  end;

  MenuDataset.First;
  MenuGroupName := '';
  MainMenuIndex.Text := '';
  MainMenuSearch.Text := '';
  treeMainMenu.BeginUpdate;
  treeMainMenu.Items.Clear;
  i := 0;
  while not(MenuDataset.EOF) do
  begin
    if (MenuDataset.FieldByName('GRP').AsString <> MenuGroupName) then
    begin
      MenuGroupName := MenuDataset.FieldByName('GRP').AsString;
      MenuGroup := treeMainMenu.Items.Add(MenuGroupName);
      SetLength(MainMenuNodes, i+1);
      MainMenuNodes[i] := MenuGroup;
      i := i + 1;
    end;
    SetLength(MainMenuNodes, i+1);
    MainMenuNodes[i] := treeMainMenu.Items.AddChild(MenuGroup,MenuDataset.FieldByName('NAM').AsString);
    i := i + 1;
    MainMenuIndex.Add(RightStr('00000'+MenuDataset.FieldByName('REF').AsString,5)+':'+MenuDataset.FieldByName('NAM').AsString);
    MainMenusearch.Add(RightStr('00000'+MenuDataset.FieldByName('REF').AsString,5)+':'+MenuDataset.FieldByName('NAM').AsString+';'+MenuDataset.FieldByName('DSC').AsString);
    MenuDataset.Next;
  end;
  treeMainMenu.EndUpdate;
  MenuDataset.Close;
  MenuDataset.Free;
end;

So thanks everyone for your help and ideas!

5 Likes

Awesome @AndrewSimard! Very nice piece of code and I'm sure it will be useful for many other users. Thank you.

1 Like

Hi Andrew,

thx for this good example, nice peace of code.

br
Michael

2 Likes