Home > Delphi, JavaScript, Object Pascal, OP4JS, Smart Mobile Studio > require.js for Smart Pascal

require.js for Smart Pascal

October 26, 2016 Leave a comment Go to comments

I guess it had to happen. And why not? If you don’t know what require.js is then you can read up on it here: http://requirejs.org/ and also check out their repo on github. I’ll show you how to get this going for Smart Mobile Studio.

Require.js

Ever looked at a node.js program? Notice how they always start with require? It’s just all over the place. Well fret ye not, it’s basically node’s equivalent of pascal’s uses. Except, it also deals with code modularization, resolving dependencies and file-loading at the same time.

I don’t know what’s up with these JS developers. Brilliant coders and I love them for it, but they have this tendency to just stuff everything under the sun including the kitchen sink into a single package. This is what happens when they replace C and Pascal at universities around the world 🙂

require.js can be divided into 4 aspects:

  • Defining dependencies that must be in place before a piece of code executes
  • Loading JavaScript files en-mass and getting a nice callback when the files are ready
  • Loading of code modules, ala DLL files in a clever way
  • Support for plugins, there is a whole bunch available (check the website)

Loading JavaScript files en-mass

To be honest the VJL already have this. It’s been a part of our RTL for ages now. If you look at the unit SmartCL.FileUtils.pas you will find a class called TW3Storage sporting routines for loading not just scripts -but images, text-files, sound files, XML and even CSS. So in your face require.js! Ha!

It has the following interface:

  TW3Storage = static class
  public
    class procedure LoadXML(aFilename:String;
          const OnComplete:TW3XMLDataReadyEvent);

    class procedure LoadFile(aFilename:String;
          const OnComplete:TW3TextDataReadyEvent);overload;

    class procedure LoadFile(aFilename:String;
          const OnComplete:TW3StreamDataReadyEvent);overload;

    class function LoadCSS(const aRel,aHref:String;
         const OnComplete:TProcedureRef):THandle;overload;
    class function LoadCSS(const aRel,aHref:String):THandle;overload;

    class Procedure LoadScript(aFilename:String;
          const OnComplete:TProcedureRef);overload;
    class procedure LoadScript(aFilename:String);overload;

    class function LoadImage(aFilename:String;
          const OnComplete:TProcedureRef):THandle;overload;
    class function LoadImage(aFilename:String):THandle;overload;

    (* This function is for batch-loading an array of images.
       The callback will be invoked when all images are successfully loaded *)
    class Procedure BatchLoadImages(aFileNames:TStrArray;
          const OnComplete:TProcedureRef);
  End;

So we have little need for require.js when it comes to loading files. There is actually very little require.js (on the surface) has to offer the Smart RTL. Having said that – require.js is not just about loading files. It also deals with module declarations and dependencies when loading these – which is what a Delphi programmer would call a DLL file.

So I think it’s wise to include it for the future, because pure JS libraries is something we will be adding to the codegen later. I wont set a date for it, but I have already added quite a bit to the codegen.

As a bonus, the keyword “require” is already marked as a reserved keyword for node.js, so the syntax highlighter in Smart Mobile Studio (im testing this on version 2.2.1.4542 which is older than the current release) will highlight the keyword as an intrinsic procedure in SmartCL based projects as well. Neat and fits like a glove!

requirejs

Require is a reserved word in node.js

Right. Loading files is well and dandy, but what about the modules? I havent really bothered with that just yet. It’s there, and it shouldnt be to hard for a crafty Smart coder to complete it, but I want it to remain dormant until we have time to make it an integral part of Smart. No point in adding something super-cool and just expose it in the IDE. And we have our work cut out for us as it is (!) So let’s finish the backlog before we start with the DLL standard.

Also, Smart is package based just like Delphi and Lazarus, so the whole JavaScript “module” system is a bit of a mess really. It works, but elegant is not a word I’ll use to describe some of the JavaScript solutions out there.

You want it now? Ok, here you go!

Yes, this time you can actually get it now. I have written the wrapper in such a way that you can just download require.js, stuff it in a library, then add a unit to your RTL folder. I know I have irritated you with all these previews. Since this unit have no special dependencies you can assemble it in 10 minutes or less.

Click here to download both the test project and the library files if you don’t want to follow the tutorial.

First, download require.js and put that in the  $root\libraries folder. like this ([D] = Directory, [F] = file):

    • [D] Libraries
      • [D] Require.js
        • [F] require.js
requirejs_minified

You want the minified version so click that

For simplicity just rename the JavaScript file “require.js”, then create a text file in the same folder with the download url and the version number. That way you can keep track of the version when you update in the future.

Now let’s write some pascal to make this puppy conform to our way of doing things! But always remember, this file works with the document object model (DOM) and must never be used in a node.js application. Node has its own version of require(). So this belongs in SmartCL namespace and visual applications only (!).

Note: Normally I would urge you to save this file to the RTL folder, but that is quite risky. The RTL folder is under the control of our automatic update program (see license) and as such it can be deleted or replaced without further notice.
To avoid any changes you make being erased, place it side-by-side with the pascal file in $Libraries\require.js\SmartCL.Require.pas. Just saying.

Note: This file is copyright, but if you own Smart Mobile Studio you have the right to use it in your projects. If not, visit www.smartmobilestudio.com for licensing options.

{ **************************************************************************** }
{                                                                              }
{ Smart Mobile Studio - Runtime Library                                        }
{                                                                              }
{ Copyright (c) The Smart Company AS. All rights reserved.                     }
{                                                                              }
{ **************************************************************************** }
unit SmartCL.Require;

interface

uses W3C.DOM, System.Types, system.reader;

type

EW3RequireJS = class(EW3Exception);

TW3RequireError = class external (JDomError)
public
  property columnNumber: integer;
  property lineNumber: integer;
  property fileName: string;
  property message: string;
  property name: string;
  property stack: TStrArray;
  property requireType: string;
  property requireModules: TStrArray;
end;

TW3RequireErrHandler = procedure (err: TW3RequireError);

TW3RequireJSConfig = class external "requirejs.config"
  property enforceDefine: boolean;
  property baseUrl: string;
  property paths[name: string]: variant;
  property waitSeconds: integer;
end;

TW3RequireJS = class external "requirejs"
  property config: TW3RequireJSConfig;
  property onError: TW3RequireErrHandler;
end;

function Require: TW3RequireJS; overload;
procedure Require(Files: TStrArray); overload;
procedure Require(Files: TStrArray; const Success: TProcedureRef); overload;
procedure Require(Files: TStrArray; const Success: TProcedureRef;
  const Failure: TW3RequireErrHandler); overload;

implementation

{$R "require.js"}

function Require: TW3RequireJS;
begin
  try
    asm
    @result = require;
    end;
  except
    on e: exception do
    raise EW3RequireJS.Create({$I %FUNCTION%}, nil, e.message);
  end;
end;

procedure Require(Files: Array of string);
begin
  try
    asm
    require(@files);
    end;
  except
    on e: exception do
    raise EW3RequireJS.Create({$I %FUNCTION%}, nil, e.message);
  end;
end;

procedure Require(Files: TStrArray; const Success: TProcedureRef);
begin
  try
    asm
      require(@Files, @Success);
    end;
  except
    on e: exception do
    raise EW3RequireJS.Create({$I %FUNCTION%}, nil, e.message);
  end;
end;

procedure Require(Files: TStrArray; const Success: TProcedureRef;
  const Failure: TW3RequireErrHandler);
begin
  try
    asm
    require(@Files, @Success, @Failure);
    end;
  except
    on e: exception do
    raise EW3RequireJS.Create({$I %FUNCTION%}, nil, e.message);
  end;
end;

initialization
begin
  // When you compile a smart program, it will copy all files imported with
  // the $R compiler define, these will be stored in the $AppName/Res folder
  // and are loaded automatically.
  // Since that is the place you want to store other scripts as well, we
  // create an alias for the location. So $scripts will always point to
  // that path. You can add as many aliases you wish
  require.config.enforceDefine := false;
  require.config.paths['$scripts'] := '/res/';
end;

end.

Now save this as described above and give it a testdrive!
Start a new visual project, save the project before you start coding (important!)
Add a button, save once more (I know, I know.. this is being fixed now. I hate that bug so much). Double click the button in the designer, then add this:

procedure TForm1.W3Button1Click(Sender: TObject);
begin
  require(["https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"],
      procedure ()
      begin
        showmessage("JQuery loaded");
      end);
end;

The above code will load jQuery (latest build) from google’s public API server. To make sure that everything went ok, click the “devtools” button in the Smart display browser and investigate:

Require() works like a charm :)

Require.js works like a charm 🙂

Note: JQuery is here just used as an example. You have absolutely no need for it under Smart, because the way visual controls work in the VJL a control always knows it’s childrens handles. So searching (hence the “query” in jquery) for tags and groups of elements have little meaning for object pascal in the browser. It already knows its children and have direct access to them. So no querying required.

Well — enjoy!

Advertisements
  1. October 26, 2016 at 10:32 am

    Jon,
    I can easily reproduce your demo using Promise approach in smart.
    I’ve modified the ECMA.Promise.pas and created this demo: http://rawgit.com/smartpascal/smartms/master/games/projPromise/www/preview.html

    [CODE]
    unit uPromise;

    interface

    uses
    SmartCL.System;

    type
    JDeferred = class;
    TCallback = procedure(Value: Variant);
    TCallbackFn = procedure(d: variant; f: variant);
    TJDeferred_object_new_fn_ = function (d: TCallback): variant;
    TJDeferred_object_new_fn = procedure (resolve: TCallback; reject: TCallback);
    TPromiseCallback = function(Value: Variant): Variant;
    TEventHandler = function(event: variant): Variant;

    JPromise = class external “Promise”
    constructor create(fn: TJDeferred_object_new_fn_{ = nil}); overload;
    constructor create(resolve: TJDeferred_object_new_fn_; reject: TJDeferred_object_new_fn_); overload;
    constructor create(fn: TJDeferred_object_new_fn); overload;
    function always(alwaysCallbacks: array of variant): JPromise;
    function done(doneCallbacks: array of variant): JPromise; overload;
    function done(doneCallbacks: variant): JPromise; overload;
    function fail(failCallbacks: array of variant): JPromise;
    function progress(progressCallbacks: array of variant): JPromise;
    function state(): string;
    function &then(doneCallbacks: variant; failCallbacks: variant = undefined; progressCallbacks: variant = undefined): JPromise;
    function &then(onFulfilled: TPromiseCallback = nil): JPromise; overload;
    function &then(onFulfilled: TPromiseCallback; onRejected: TPromiseCallback): JPromise; overload;
    function catch(rejectCallback: Variant = nil): JPromise; overload;
    function catch(rejectCallback: TPromiseCallback): JPromise; overload;
    class function promise(target: Variant): JPromise;
    end;

    type
    JDeferred = class external “Promise”(JPromise)
    function notify(args: array of variant): JDeferred;
    function notifyWith(context: variant; args: array of variant): JDeferred;
    function reject(args: array of variant): JDeferred; overload;
    function reject(args: variant): JDeferred; overload;
    function reject(args: TEventHandler): JDeferred; overload;
    function rejectWith(context: variant; args: array of variant): JDeferred;
    function resolve(args: array of variant): JDeferred; overload;
    class function resolve(value: variant = nil): JPromise; overload;
    function resolveWith(context: variant; args: array of variant): JDeferred;
    function all(iterable: Array of Variant): JPromise;
    function race(iterable: Array of Variant): JPromise;
    end;
    { global external functions }
    function Promise : JDeferred; external ‘Promise’ property;
    function Error(message: variant): variant; external ‘Error’;

    implementation

    end.
    [/CODE]

    (*… and require.js is not required! 🙂 *)

    function getURI(url: string): variant;
    begin
    // Create new promise with the Promise() constructor;
    // This has as its argument a function
    // with two parameters, resolve and reject
    Result := JPromise.create(
    procedure(resolve: TCallback; reject: TCallback)
    // Standard XHR to load an image
    var request: JXMLHttpRequest;
    begin
    request := new JXMLHttpRequest();
    request.open(‘GET’, url);

    // When the request loads, check whether it was successful
    request.onload := lambda
    begin
    // This is called even on 404 etc
    // so check the status
    if (request.status = 200) then
    begin
    // If successful, resolve the promise by passing back the request response
    resolve(request.response);
    end
    else
    begin
    // Otherwise reject with the status text
    // which will hopefully be a meaningful error
    reject(Error(“File didn’t load successfully; error code: ” + request.statusText));
    end;
    end;
    end;
    // Handle network errors
    request.onerror := lambda
    // Also deal with the case when the entire request fails to begin with
    // This is probably a network error, so reject the promise with an appropriate message
    reject(Error(‘There was a network error.’));
    end;
    // Send the request
    request.send();
    end);
    end;

    function TForm1.doSomething(): variant;
    begin
    showmessage(“JQuery loaded”);
    end;

    procedure TForm1.btnPromiseClick(Sender: TObject);
    begin
    getURI( ‘https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js’ )
    .then(lambda(): variant
    Result := doSomething; // FileExists!
    end)
    .catch(lambda(error: variant)
    WriteLn(‘:(‘+ error); // :(Error: File didn’t load successfully; error code: File not found
    end);

    end;

    • October 26, 2016 at 10:45 am

      If you read the article again you will notice that loading files is not really the point. The point is that requirejs supports modules with dependency resolvement. The unit is there as an investment for the future, not as a showcase of loading files (which we have supported from day 1).
      But hey, promise is also a nice feature so good work on that!

    • October 26, 2016 at 10:47 am

      Also: can you please stop using nude/semi nude-pics on your demos? Seriously.
      You write good code, but the presentation of that code in some of your demos is seriously not acceptable.
      You have to understand that our audience will not associate such demos as serious. Use some standard pictures instead and that solves it.

  2. November 22, 2016 at 3:12 am

    warleyalex is horny c0der.

    • November 23, 2016 at 12:04 am

      He is a great coder, but his choice of graphics could perhaps be improved with regards to demos 🙂

  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: