ExternalInterface Tutorial (with Invoke String Examples)

F-IN-BOX for Delphi / Builder C++ / VCL
jpierce
Posts: 23
Joined: Thu Jan 11, 2007 4:22 pm

ExternalInterface Tutorial (with Invoke String Examples)

Postby jpierce » Wed Sep 24, 2008 4:11 pm

The following is some internal documentation I wrote for my company. I thought it might help a lot because finding good docs for this topic is very difficult. F-IN-BOX: Feel free to copy this documentation if you like.


Calling a function in a Flex module

Using f-in-box in Delphi or javascript in a web browser, you can call a function inside a Flex module. The examples given will be for f-in-box in Delphi.

First you must register the function inside your Flex application.

Code: Select all

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="cc()">
  <mx:Script>
    <![CDATA[
      private function cc():void
      {       
        ExternalInterface.addCallback("externalFunc", externalFunc);
      }
     
      private function externalFunc(input:String):String
      {
        trace(input);
        return "got it!"
      }
    ]]>
  </mx:Script>

</mx:Application>


Invoke String

In Delphi, use the CallFunction method of your TFlashPlayerControl object to call the function. You must pass in the arguments as an XML invoke string.


Code: Select all

var
  request: WideString;
  response: WideString;
begin
  request := '<invoke name="externalFunc"><arguments><string>did you get it?</string></arguments></invoke>';
  response := Flash.CallFunction(request);
end;


The allowed types for the arguments in the invoke string are string, array, object and property. These names are case-sensitive. There is no integer type, so number must be used in the invoke string. But if you use an int variable in Flex to receive it, it will automatically be converted to an int. Boolean values may be passed as <true/> and <false/>. They are not wrapped in a <boolean> tag. <property> is used to indicate that an element is a property of an object or an index of an array. It has an id attribute that indicates the property name or the array index. <null/> indicates a null value.


Important: Do not put whitespace inside the <arguments> section. The only exception is putting it inside a <string> element. The whitespace will break the parser.

Here is a complicated example showing all the possible types. I have broken it up with whitespace for readability, but as I said above the parser does not like whitespace.

Code: Select all

<object>
  <property id="aString">
    <string>foo</string>
  </property>

  <property id="aNumber">
    <number>12345</number>
  </property>

  <property id="aTrueBoolean">
    <true/>
  </property>

  <property id="aFalseBoolean">
    <false/>
  </property>

  <property id="anArray">
    <array>
      <property id="0">
        <string>a</string>
      </property>

      <property id="1">
        <string>b</string>
      </property>

      <property id="2">
        <number>987</number>
      </property>

      <property id="3">
        <true/>
      </property>
    </array>
  </property>

  <property id="aSubObject">
    <object>
      <property id="field1">
        <string>foo</string>
      </property>

      <property id="field2">
        <number>42</number>
      </property>
    </object>
  </property>
</object>



This is equivalent to the ActionScript declaration:

Code: Select all

var o:Object = {
  aString:"foo",
  aNumber:12345,
  aTrueBoolean:true,
  aFalseBoolean:false,
  anArray:["a", "b", 987, true],
  aSubObject:{field1:"foo", field2:42}
};


Keep in mind that since this is XML, characters such as < or & must be escaped in both the invoke string and the return value. Good encode and decode functions can be found in the HTTPApp unit. You may also embed unescaped text in CDATA sections such as in the following example (whitespace added for readability, but make sure you don't do that in the actual code):

Code: Select all

<invoke name="externalFunc">
  <arguments>
    <string><![CDATA[<html><body>some text<p></body></html>]]></string>
  </arguments>
</invoke>


Return Value

By default, the response will also be in XML. There is also a javascript return type that returns data in the JSON format. Since I have never found a useful parser for JSON, I am only giving examples for XML.


For the first example, the response would be:

Code: Select all

<string>got it!</string>


You can also pass back arrays and simple objects without doing anything special.

Code: Select all

private function externalFunc(input:String):Object
{
  trace(input);

  var o:Object = {
    aString:"foo",
    aNumber:12345,
    aTrueBoolean:true,
    aFalseBoolean:false,
    anArray:["a", "b", 987, true],
    aSubObject:{field1:"foo", field2:42}
  };

  return o;
}


This will be returned to Delphi as shown in the earlier invoke string. To load the result into an TXpObjModel (freeware XML parser available at here), you need to do the following:

Code: Select all

const
  LE_BOM = #$FEFF;  // little-endian beginning of message character
...
var
  response: WideString;
  xml: TXpObjModel;
...
  response := LE_BOM + Flash.CallFunction(request);
  xml.LoadMemory(response[1], Length(response) * SizeOf(WideChar));


You can then use the xpath functions of the XpObjModel object to extract data:

Code: Select all

xml.DocumentElement.SelectInteger('/object/property[@id="anArray"]/array/property[@id="2"]/number')


Note: When you return an object, the order of the properties may not be the same as how you returned them. Arrays do seem to keep their order. Keep this in mind if you use plain text parsing.

Note: More complicated objects will only be returned as <object/>. I haven't determined if this is just because there are too many properties, or because some properties cannot be represented in XML.


Calling a function from a Flex module

Flex modules can call functions external to them, such as javascript functions in a browser or callbacks in f-in-box in Delphi. The basic method is the same as above.

In your Flex application, you connect an OnFlashCall event handler to your TFlashPlayerControl's object. You return a response by calling SetReturnValue:

Code: Select all

procedure TMainForm.FlashCall(ASender: TObject; const request: WideString);
begin
  ShowMessage(request);
  TFlashPlayerControl(Sender).SetReturnValue(
    '<object><property id="field1"><string>bar</string></property></object>');
end;



You call this function in your Flex application with the following code:

Code: Select all

var o:Object = ExternalInterface.call("delphiFunc", {field1:"foo", field2:123});


Using the two pieces of code above, the following would be displayed by the Delphi application (with added whitespace):

Code: Select all

<invoke name="delphiFunc" returntype="xml">
  <arguments>
    <object>
      <property id="field1">
        <string>foo</string>
      </property>

      <property id="field2">
        <number>123</number>
      </property>
    </object>
  </arguments>
</invoke>


The Flex application would receive a response that is equivalent to:

Code: Select all

  {field1:"foo", field2:123}



Thus you can easily pass arrays and more complex objects back to Flex this way.



Other methods of passing data into a Flex module

One limitation of ExternalInterface is that all data must be passed in text. To get around this, you can use the TFlashPlayerControl event handler for OnLoadExternalResource.


On use of this is to allow the Flex app to load files off disk, an operation that is normally prevented by the security sandbox:

Code: Select all

procedure TMainForm.FlashLoadExternalResource(ASender: TObject; const URL: WideString; Stream: TStream);
var
  fs: TFileStream;
  fileName: String;
begin
  fileName := ExtractFilePath(Application.ExeName) + StringReplace(URL, '/', '\', [rfReplaceAll]);

  if FileExists(fileName) then
  begin
    fs := TFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
    Stream.CopyFrom(fs, fs.Size);
    fs.Free;
  end;
end;


With this code set, the following code would load an image out of the images subdirectory of the Delphi program:

Code: Select all

<mx:Image source="images/icon.png"/>


But this could be used more cleverly to allow a query to the Delphi application return any binary data to Flex, which it can then access as a ByteArray:

Code: Select all

  <mx:Script>
    <![CDATA[
      private function loadData(user:String):void
      {
        var loader:URLLoader = new URLLoader;
        loader.dataFormat = URLLoaderDataFormat.BINARY;
        loader.addEventListener(Event.COMPLETE, loadComplete);
        loader.load(new URLRequest("getData?userID=" + user));
      }
     
      private function loadComplete(e:Event):void
      {
        trace("load complete");
        var ba:ByteArray = ByteArray(e.currentTarget.data);
        ba.endian = Endian.LITTLE_ENDIAN;
        dataResults.text = "uid: " + ba.readShort() + "\n" +
            "status: " + ba.readShort() + "\n" +
            "gid: " + ba.readShort() + "\n" +
            "hasdbaccess: " + ba.readInt();
      }
    ]]>
  </mx:Script> 
  <mx:TextInput id="userID" text="jpierce"/>
  <mx:Button label="Go!" click="loadData(userID.text)"/>
  <mx:TextArea id="dataResults" height="100"/>


Code: Select all

// the following is assigned to the FlashPlayerControl's OnLoadExternalResource event
procedure TMainForm.FlashLoadExternalResource(ASender: TObject; const URL: WideString; Stream: TStream);
var
  params: TStringList;
  connection: TADOConnection;
  query: TADOQuery;
  value4Byte: Integer;
  value2Byte: Smallint;
begin
  params := TStringList.Create;
  try
    params.Delimiter := '?';
    params.DelimitedText := URL;
    if (params.Count > 1) and (params[0] = 'getData') then
    begin
      params.Delimiter := '&';
      params.DelimitedText := params[1];
      if params.IndexOfName('userID') <> -1 then
      begin
        connection := TADOConnection.Create(nil);
        query := TADOQuery.Create(nil);
        try
          query.Connection := connection;
          connection.ConnectionString := 'Provider=SQLOLEDB.1;' +
              'Integrated Security=SSPI;Persist Security Info=False;' +
              'Initial Catalog=master;Data Source=mydbserver';
          query.SQL.Text := 'select uid, status, gid, hasdbaccess from ' +
              'sysusers where name=:username';
          query.Parameters.ParamValues['username'] := params.Values['userID'];
          query.Open;

          value2Byte := query.FieldByName('uid').AsInteger;
          Stream.Write(value2Byte, SizeOf(value2Byte));
          value2Byte := query.FieldByName('status').AsInteger;
          Stream.Write(value2Byte, SizeOf(value2Byte));
          value2Byte := query.FieldByName('gid').AsInteger;
          Stream.Write(value2Byte, SizeOf(value2Byte));

          value4Byte := query.FieldByName('hasdbaccess').AsInteger;
          Stream.Write(value4Byte, SizeOf(value4Byte));
        finally
          query.Free;
          connection.Free;
        end;
      end;
    end;
  finally
    params.Free;
  end;
end;
Last edited by jpierce on Tue Sep 30, 2008 12:57 am, edited 2 times in total.

Softanics
Site Admin
Posts: 1402
Joined: Sat Sep 18, 2004 3:03 am
Location: Russia, St. Petersburg
Contact:

Postby Softanics » Wed Sep 24, 2008 4:30 pm

This is really fantastic! Thank you very much!
Best regards, Artem A. Razin,
F-IN-BOX support
Ask your question here: http://www.f-in-box.com/support.html

jpierce
Posts: 23
Joined: Thu Jan 11, 2007 4:22 pm

Postby jpierce » Wed Sep 24, 2008 5:21 pm

Thanks! I especially wanted to share the last bit, as it lets you pass binary data from your delphi app to your flex app. You could even connect it with some ExternalInterface calls so that you can use Delphi to trigger the Flex app to load some binary data.

The one thing I still really wish I had was some way for the Delphi app to request binary data from the Flex app.

andreaarg
Posts: 14
Joined: Tue Aug 26, 2008 11:53 pm

Postby andreaarg » Mon Sep 29, 2008 12:26 am

Thanks for the information..
Could you explain a little more the part about binary data?
Specifically, I don't understand how the next code works...

Code: Select all

loader.load(new URLRequest("getData?userID=jsmith"));


What do the Delphi application must do to understand and reply that petition?

Thanks in advance.

jpierce
Posts: 23
Joined: Thu Jan 11, 2007 4:22 pm

Postby jpierce » Tue Sep 30, 2008 12:56 am

I edited the original post and put in a more fleshed out example of using OnLoadExternalResource to return binary data from Delphi. It's still kind of a trivial example since I'm trying to create an example people can follow. But hopefully it's enough to show how its done. Note that you can use the read*() functions in Flex, or you can access the data ByteArray as an array of bytes (e.g. data[0], data[1], etc.)

Let me know if it's still unclear.

andreaarg
Posts: 14
Joined: Tue Aug 26, 2008 11:53 pm

Postby andreaarg » Wed Oct 01, 2008 11:16 pm

Excellent, really clear.
Thanks a lot.

andreaarg
Posts: 14
Joined: Tue Aug 26, 2008 11:53 pm

Postby andreaarg » Thu Oct 23, 2008 11:07 pm

Hello. So far this information has been really useful.
I have one question: My Flex application needs information that is provided by the Delphi application. The delphi application gets the information from a Firebird database and passes it to Flex in XML format.

Which is the best way for the Flex Application to ask for this XML information to Delphi? FlashCall (setting the return value) or LoadExternalResource (writing the xml to a stream)?

Thanks in advance.
Andrea.

jpierce
Posts: 23
Joined: Thu Jan 11, 2007 4:22 pm

Postby jpierce » Fri Oct 24, 2008 6:36 pm

Funny you should ask, as right now I'm working on code that uses finbox in a Delphi app with an embedded Firebird database!

Well, here's the major consideration with LoadExternalResource - it's asynchronous. You can't get data immediately but have to wait for the URLLoader's complete event. If you make an ExternalInterface call, you can have the data immediately.

Of course, that causes other problems, too, in that your query could take longer than the magical 15 seconds and cause the Flash Player to put a stop to it.

Another downside to ExternalInterface is that you have to build the whole string in memory of what you want to send back. For large amounts of data, that might really chew up some memory.

I've written some code to help out a lot with this. It's not fully cleaned up, so error handling might not be the best and there are very few comments. But it provides a single way to do the querying via either ExternalInterface or LoadExternalResource.

First of all, there is a Delphi unit:

Code: Select all

unit ExternalDataLoader;

interface

uses
  SysUtils, Classes,
  IB_Header, IB_Components; // http://www.ibobjects.com/

procedure SendQuery(SQL: String; Stream: TStream; DB: TIB_Connection);

implementation

const
  QUERY_SUCCESS = 0;
  QUERY_FAILURE = 1;

  FIELD_INT = 0;
  FIELD_NUMBER = 1;
  FIELD_STRING = 2;
  FIELD_DATE = 3;
  FIELD_BINARY = 4;

  FLASH_DATE_BASE = 25569; // to translate to flash Date.time values

procedure WriteInteger(Value: Integer; Stream: TStream);
begin
  Stream.WriteBuffer(Value, SizeOf(Value));
end;

procedure WriteString(Value: WideString; Stream: TStream);
var
  len: Word;
  str: UTF8String;
begin
  str := UTF8Encode(Value);
  len := Length(str);
  Stream.WriteBuffer(len, SizeOf(len));
  Stream.WriteBuffer(str[1], len);
end;

procedure WriteNumber(Value: Double; Stream: TStream);
begin
  Stream.WriteBuffer(Value, SizeOf(Value));
end;

function SendFieldData(Query: TIB_Cursor; Stream: TStream): Boolean;
var
  i: Integer;
  f: TIB_Column;
  fields: TIB_Row;
begin
  Result := False;

  fields := Query.Fields;

  WriteInteger(query.FieldCount, Stream);

  for i := 0 to fields.ColumnCount - 1 do
  begin
    f := fields[i];

    if f.IsNullable then
      Result := True;

    WriteString(f.FieldName, Stream);

    case f.SQLType of
      SQL_VARYING, SQL_VARYING_,
      SQL_TEXT, SQL_TEXT_:
        WriteInteger(FIELD_STRING, Stream);

      SQL_BLOB, SQL_BLOB_:
        WriteInteger(FIELD_BINARY, Stream);

      SQL_FLOAT, SQL_FLOAT_,
      SQL_DOUBLE, SQL_DOUBLE_,
      SQL_INT64, SQL_INT64_:  // treat int64 like Number since Flex has no int64
        WriteInteger(FIELD_NUMBER, Stream);

      SQL_SHORT, SQL_SHORT_,
      SQL_LONG, SQL_LONG_:
        WriteInteger(FIELD_INT, Stream);

      SQL_TYPE_TIME, SQL_TYPE_TIME_,
      SQL_TYPE_DATE, SQL_TYPE_DATE_,
      SQL_TIMESTAMP, SQL_TIMESTAMP_:
        WriteInteger(FIELD_DATE, Stream);
    end;

    WriteInteger(Integer(f.IsNullable), Stream);
  end;
end;

procedure SendRowData(Query: TIB_Cursor; Stream: TStream; HasNulls: Boolean);
var
  nullMask: Integer;
  i: Integer;
  fields: TIB_Row;
  f: TIB_Column;
begin
  fields := Query.Fields;

  WriteInteger(Query.RecordCount, Stream);
  while not Query.Eof do
  begin
    if HasNulls then
    begin
      nullMask := 0;
      for i := 0 to fields.ColumnCount - 1 do
        if fields[i].IsNull then
          nullMask := nullMask or (1 shl i);

      WriteInteger(nullMask, Stream);
    end;

    for i := 0 to fields.ColumnCount - 1 do
    begin
      f := fields[i];

      if not f.IsNull then
        case f.SQLType of
          SQL_VARYING, SQL_VARYING_,
          SQL_TEXT, SQL_TEXT_,
          SQL_BLOB, SQL_BLOB_:  // blob will work as string even with #0s in it
            WriteString(f.AsString, Stream);

          SQL_FLOAT, SQL_FLOAT_,
          SQL_DOUBLE, SQL_DOUBLE_,
          SQL_INT64, SQL_INT64_:  // treat int64 like Number since Flex has no int64
            WriteNumber(f.AsDouble, Stream);

          SQL_SHORT, SQL_SHORT_,
          SQL_LONG, SQL_LONG_:
            WriteInteger(f.AsInteger, Stream);

          SQL_TYPE_TIME, SQL_TYPE_TIME_,
          SQL_TYPE_DATE, SQL_TYPE_DATE_,
          SQL_TIMESTAMP, SQL_TIMESTAMP_:
            WriteNumber(Round((f.AsDouble - FLASH_DATE_BASE) * MSecsPerDay), Stream);
        end;
    end;

    Query.Next;
  end;
end;

procedure SendQuery(SQL: String; Stream: TStream; DB: TIB_Connection);
var
  query: TIB_Cursor;
  hasNulls: Boolean;
begin
  query := TIB_Cursor.Create(nil);
  try
    try
      query.IB_Connection := DB;
      query.SQL.Text := sql;

      if Pos('select', LowerCase(sql)) = 1 then
      begin
        query.Open;
        try
          WriteInteger(QUERY_SUCCESS, Stream);
          hasNulls := SendFieldData(Query, Stream);
          SendRowData(Query, Stream, hasNulls);
        finally
          query.Close;
        end;
      end
      else
        query.ExecSQL;
    except
    end;
  finally
    query.Free;
  end;
end;


end.


Here's an example of how you would hook up your finbox code to it:

Code: Select all

uses
  OmniXML,  // free from http://www.omnixml.com/
  IB_Components, IB_Header, // http://www.ibobjects.com/
  ExternalDataLoader, // my custom unit
  EncdDecd, HTTPApp;  // delphi units

// rest of your form code goes here

function SelectString(Node: IXMLNode; const XPath: String; DefaultValue:String = ''): String;
var
  n: IXMLNode;
begin
  n := Node.SelectSingleNode(XPath);
  if n = nil then
    Result := DefaultValue
  else
    Result := n.Text;
end;

const
  EXTERNAL_INTERFACE_QUERY_FUNCTION = 'query';

procedure TMainForm.FlashCall(ASender: TObject; const request: WideString);
var
  sql: String;
  db: TIB_Connection;
  inStream: TMemoryStream;
  outStream: TStringStream;
  xml: IXMLDocument;
  functionName: String;
begin
  xml := CreateXMLDoc;

  if xml.LoadXML(request) then
    functionName := LowerCase(SelectString(xml, '/invoke/@name'));

  if functionName = EXTERNAL_INTERFACE_QUERY_FUNCTION then
  begin
    outStream := TStringStream.Create('<string>');
    inStream := TMemoryStream.Create;
    try
      sql := HTMLDecode(SelectString(xml, '/invoke/arguments/string'));
      db := ConnectToDB(Database);
      if db <> nil then
      try
        OutputDebugString(PChar(sql));
        SendQuery(sql, inStream, db);

        outStream.Position := outStream.Size;
        inStream.Position := 0;
        EncodeStream(inStream, outStream);
        outStream.Position := outStream.Size;
        outStream.WriteBuffer('</string>', Length('</string>'));

        Flash.SetReturnValue(outStream.DataString);
      finally
        db.Free;
      end;
    finally
      inStream.Free;
      outStream.Free;
    end;
  end;
end;

procedure TMainForm.FlashLoadExternalResource(ASender: TObject;
    const URL: WideString; Stream: TStream);
var
  fs: TFileStream;
  fileName: String;
  sql: String;
  db: TIB_Connection;
  decodedURL: String;
begin
  decodedURL := HTTPDecode(URL);
  if Pos(EXTERNAL_INTERFACE_QUERY_FUNCTION + '?', decodedURL) = 1 then
  begin
    db := ConnectToDB(Database);
    if db <> nil then
    try
      sql := Copy(decodedURL, Length(EXTERNAL_INTERFACE_QUERY_FUNCTION + '?') + 1,
          Length(decodedURL) - Length(EXTERNAL_INTERFACE_QUERY_FUNCTION + '?'));
      SendQuery(sql, Stream, db);
    finally
      db.Free;
    end;
  end;
end;


Just hook those handlers up to OnFlashCall and OnLoadExternalResource.

Finally, here's the Flex package.

Code: Select all

package nes.fInBox
{
  import flash.events.Event;
  import flash.external.ExternalInterface;
  import flash.net.URLLoader;
  import flash.utils.ByteArray;
  import flash.utils.Endian;
 
  import mx.utils.Base64Decoder;

  public class ExternalDataLoader extends URLLoader
  {
    public static function loadData(sql:String, output:Object = null,
        completeHandler:Function = null, forceAsynchronous:Boolean = false):Object
    {
      if (output == null)
        output = {loaded:false};

      if (forceAsynchronous || completeHandler != null)
        return loadDataAsynchronous(sql, output, completeHandler)
      else
      {
        loadDataSynchronous(sql, output);
        return output;
      }
    }
   
    private static const EXTERNAL_INTERFACE_QUERY_FUNCTION:String = 'query';
   
    private static function loadDataAsynchronous(sql:String, output:Object = null, handler:Function = null):URLLoader
    {
      return new ExternalDataLoaderHelper(sql, output, handler, externalDataLoaderHelperHandler);
    }

    private static function loadDataSynchronous(sql:String, output:Object):void
    {
      var result:String = ExternalInterface.call(EXTERNAL_INTERFACE_QUERY_FUNCTION, sql);
      var dec:Base64Decoder = new Base64Decoder;
      dec.decode(result);
     
      parseDataDirect(dec.drain(), output);
    }   

    private static const QUERY_SUCCESS:int = 0;
    private static const QUERY_FAILURE:int = 1;
    private static const FIELD_INT:int = 0;
    private static const FIELD_NUMBER:int = 1;
    private static const FIELD_STRING:int = 2;
    private static const FIELD_DATE:int = 3;
    private static const FIELD_BINARY:int = 4;
       
    private static function externalDataLoaderHelperHandler(e:Event):void
    {
      var loader:ExternalDataLoaderHelper = ExternalDataLoaderHelper(e.currentTarget);     
      parseDataDirect(loader.data, loader.output);
      if (loader.handler != null)
        loader.handler(loader.output);
    }

    private static function parseDataDirect(ba:ByteArray, output:Object):void
    {
      ba.endian = Endian.LITTLE_ENDIAN;
     
      if (ba.readInt() == QUERY_FAILURE)
      {
        // nothing to really handle the error yet
      }
      else
      {
        var fieldCount:int = ba.readInt();
        var fields:Array = [];
        var fieldIndexes:Object = {};
        fields.length = fieldCount;
        var f:int;
        var hasNulls:Boolean;
       
        // get the field data
        for (f = 0; f < fieldCount; f++)
        {
          var field:Object = {};
          field.index = f;
          field.originalName = ba.readUTF();
          field.name = (field.originalName as String).replace(new RegExp('"', "g"), "").toLowerCase();
          field.type = ba.readInt();
          field.nullable = ba.readInt() == 1 ? true : false;
          fields[f] = field;
          fieldIndexes[field.name] = f;
          hasNulls = hasNulls && field.nullable;
        }
       
        var rowCount:int = ba.readInt();
        var rows:Array = [];
        rows.length = rowCount;
       
        for (var r:int = 0; r < rowCount; r++)
        {
          var row:Array = [];
          row.length = fieldCount;
          rows[r] = row;
          var nullMask:int = ba.readInt();
         
          for (f = 0; f < fieldCount; f++)
          {
            if (nullMask & (1 << f))
              row[f] = null
            else
            {
              switch (fields[f].type)
              {
                case FIELD_INT:
                  row[f] = ba.readInt();
                  break;
                case FIELD_NUMBER:
                  row[f] = ba.readDouble();
                  break;
                case FIELD_STRING:
                  row[f] = ba.readUTF();
                  break;
                case FIELD_DATE:
                  row[f] = new Date(ba.readInt());
                  break;
                case FIELD_BINARY:
                  var blob:ByteArray = new ByteArray;
                  var blobLen:int = ba.readInt();
                  ba.readBytes(blob, 0, blobLen);
                  row[f] = blob;
                  break;
              }
            }
          }
        }
      }
     
      output.fieldIndexes = fieldIndexes;
      output.fields = fields;
      output.rows = rows;
      output.loaded = true;
    }
  }  // end class
}  // end package


import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.events.Event;
import flash.net.URLRequest;

class ExternalDataLoaderHelper extends URLLoader
{
  public var output:Object;
  public var handler:Function;
  public var sql:String;

  public function ExternalDataLoaderHelper(sql:String, output:Object, handler:Function, complete:Function)
  {
    super();
   
    this.output = output;
    this.handler = handler;
    this.sql = sql;
   
    dataFormat = URLLoaderDataFormat.BINARY;
    addEventListener(Event.COMPLETE, complete);   
    load(new URLRequest("query?" + escape(sql)));
  }
}


The only thing you really need to use is the static function ExternalDataLoader.loadData. This is what you call to get it to load the data from Delphi.

If you include a handler function, it will do the call asynchronously using LoadExternalResource. The handler function is just a function with an Object parameter. This will be populated with the results from the query. In this case loadData will return the URLLoader that is loading the data. You can use this to hook further events up like progress or ioError.

If you do not provide a handler, it will assume you want to use ExternalInterface. If you pass in an object, this will populated with the results from the query. If not, the function will create the results object. In either case, the return value will be the results object.

You can also force it to use LoadExternalResource even if you don't provide an event handler by using the forceAsynchronous parameter. This would be handy if you didn't care about the results of a query and wanted it executed asynchronously.

The results object gets four properties: fields, fieldIndexes, rows and loaded.
  • fields - an array of Objects with properties index, originalName (as it came from the DB), name (with double quotes removed and converted to lowercase), type (type of field data), nullable
  • fieldIndexes - an object that serves as a hash for the index for each field. For example: fieldIndexes.last_name might return 3.
  • rows - a two dimensional array of the data. The first dimension is the row number. The second dimension is the field index as given in the fields and fieldIndexes properties.
  • loaded - set to true when the data is fully parsed on loaded. Could be checked if you have multiple query result objects all hitting the same handler function and you need to check when all of them have been loaded.

Examples:

Code: Select all

var results:Object = ExternalDataLoader.loadData("select * from foo");  // uses ExternalInterface

var results:Object;
ExternalDataLoader.loadData("select * from foo");  // uses ExternalInterface

var results:Object;
ExternalDataLoader.loadData("select * from foo",  null, gotData);  // uses LoadExternalResource and calls the function gotData() with the populated result

// this goes after the call that uses ExternalInterface or in the handler if it's using LoadExternalResource
for each (var row:Array in result.rows)
  trace(row[fieldIndexes.first_name] + " " + row[fieldIndexes.last_name]);


Whew. I hope I didn't miss anything. Hope I didn't also mess anything up as I was fixing things as I posted this and noticed them. Let me know if you find this helpful or at least interesting!


Return to “Delphi / Builder / VCL Edition”

Who is online

Users browsing this forum: No registered users and 15 guests

cron