Home > Delphi, JavaScript, Language research, Object Pascal, Smart Mobile Studio > Smart Pascal: Download streams

Smart Pascal: Download streams

January 20, 2017 Leave a comment Go to comments

Real, binary streams has been a part of the Smart Pascal RTL for quite some time now. As a Delphi developer you probably take that for granted, but truth be told – no other JavaScript framework even comes close to our implementation. So this is unique to Smart Pascal, believe it or not.

The same can be said about the ability to allocate, move and work with memory buffers. Sure you can write similar code by hand in pure JavaScript, but the amount of code you have to write will quickly remind you why object orientation is so important.

Binary data counts

So you got streams, what of it? I hear you say. But you are missing the point here. If there is one thing JavaScript sucks at, it’s dealing with binary data. It has no concept really of bytes versus 32 bit integers, or 64bit integers. There is no such thing as a pointer in JavaScript. So while we have enjoyed pointers, memory allocations, being able to manipulate memory directly and use streams to abstract from our binary data for decades in Delphi — all of this is brand new under JavaScript.

672274_cd11_2

And binary data counts. The moment you want to write code that does something on any substancial level – the capacity for dealing with binary data in a uniform way is imperative. If you are into HTML5 game coding you will sooner or later get in contact with map editors (or level editors) that work best in binary format. If you plan on making a sound app that runs in the cloud, again being able to read binary files (just like we do in Delphi) is really, really important. Just stop and think for a few seconds how poor Delphi and C++ builder would be without streams.

Making data available

One developer asked me an important question earlier: how do you get data out? And he meant that quite literally. What if my Smart app is the producer of binary data? What if I use SMS to create the fancy map editor, or the waveform generator code or whatever – what then?

Indeed that is a good question, but thankfully an easy one.

Most JavaScript objects, or data objects in general, inside a browser can be exported. What this means is that the browser can tag a spesific object with an ID, and then make the data available as a normal link.

For instance, if you have a block of memory like an uint8Array and you want that data exported, you would call url.createObjectURL() and it will create an URL you can use to get that data. Let’s have a look at the code you need first:

function BinaryStreamToURLObject(Stream: TStream):String;
var
  mBlob:  THandle;
begin
  if stream<>NIL then
  begin
    stream.position:=0;
    var mTemp := TDatatype.BytesToTypedArray(Stream.read(stream.size));
    asm
      var encdec = window.URL || window.webkitURL;
      @mBlob = new Blob([@mTemp],{ type: "application/octet-binary" } );
      @result = encdec.createObjectURL(@mBlob);
      console.log(@result);
    end;
  end;
end;

procedure ForceDownloadOf(FileName: string; Stream: TStream);
var
  LARef:  TControlHandle;
begin
  if Stream <> nil then
  begin
    if Stream.Size > 0 then
    begin
      // Create node
      asm
        @LARef = document.createElement('a');
      end;

      // Setup values
      LARef.style := "display: none";
      LARef.href := BinaryStreamToURLObject(Stream);
      LARef.download := Filename;

      // Add to DOM
      asm
        document.body.appendChild(@LARef);
      end;

      // Wait for the obj to appear in the DOM
      LARef.readyExecute( procedure ()
        begin
          // Invoke click on link
          LARef.click();
        end);

    end;
  end;
end;

Note #1: Notice how I use a TControlHandle in the example above. Why? Because this handle has a helper class that gives us some perks, like readyExecute(), which fires when the element is safely in the DOM and is ready to be used.

Note #2: Since the built-in browser in Smart Mobile Studio doesnt have download functionality, nothing will happen when you run this inside the IDE. So click on the “Open in browser” and run your app there to see it.

The first function takes a stream and converts it into a blob object. The second function creates an anchor object, and then calls the click() method on that anchor. Essentially kick-starting the download. It is the exact same as you clicking on a download link, except we do it purely through code.

Let’s go through the steps

  • Grab all the data from the stream
  • Convert from TByteArray to a typed browser array
  • Fetch the browser’s URL object
  • Call createObjectURL, passing the data
  • Return the internal URL for the data, which is now kept safe
  • Create an anchor link object
  • Make sure the anchor is invisible
  • Set the URL to our blob above
  • Add the anchor to the DOM
  • Call the Click() method on the anchor

Voila! Not to hard was it 🙂

So now you can just have a button and in the onClick event you just call ForceDownload() and bob’s your uncle 🙂

Here is the internal link I got after saving a TW3Dataset to a stream and pushing it through the steps above: blob:http%3A//192.168.38.102%3A8090/9a351a97-5f6d-4a43-b23b-c81b77972e21

This link is relative to the content, so it will only work for as long as your Smart app is in memory (actually, only while the blob is managed, you can remove the blob as well).

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: