Sunteți pe pagina 1din 240

Using Elevate Web Builder 2 Web and Mobile Apps

2nd Edition

By Erick Engelke
erickengelke@gmail.com
Kitchener, Canada
Copyright © 2016 Erick Engelke

All rights reserved. No parts of this book may be reproduced, stored in a retrieval system
or transmitted in any form or by any means without prior written permission from the
author, except in brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the
information presented. However, the information in this book is sold without warranty,
either express or implied. Neither the author, the publisher or its affiliates, dealers and
distributors will be held liable for any damages caused or alleged to be caused directly or
indirectly by this book.

The author has endeavored to provide trademark information about all the companies and
products mentioned in this book by the use of capitals. However, the author and publisher
cannot guaranty the accuracy of this information.

Published August 2016


ISBN-13: ISBN-10:

Using Elevate Web Builder


Web and Mobile Apps
2nd Edition
Erick Engelke This book is dedicated to my wife, Rosemary.

I am very grateful for the keen proofreading and eternal patience of Tim Young of Elevate.
In 1995 there was a new product called Delphi which revolutionized Windows
development for many programmers. It introduced the Object Pascal language and the
Visual Component Library (VCL) which greatly simplified writing Windows applications,
and database applications in particular.
The idea was called RAD for Rapid Application Development and it benefitted small
shops and individual programmers because they could quickly produce applications which
competed with big firms, and it helped big firms because they could do more… faster.

In the years since, Delphi has been reworked to support native Linux, Android, IOS and
Macintosh applications. Its open source cousin: Free Pascal with Lazarus have supported
these and other platforms too. Their motto is ‘write once, compile anywhere’.

Several groups have translated Delphi’s basic architecture to Web applications which
generate JavaScript as the run time language. The two I have used are Smart Mobile Studio
SMS and Elevate Web Builder or EWB. They are not identical to Delphi, but still so
similar one feels eerily at home..

This book focuses on EWB. I think it marks an epoch in Web development that ushers in an
age of reasonable tools for fast and reliable production of Web pages.

If you have a Delphi background, you will be instantly familiar with much of EWB. But
even newcomers will find it inviting. By basing the product on the ideas of Delphi, the
Elevate developers have inherited a lot of well-tuned principles.
Pascal is a wonderful language. It has the power of C and thus has been used to write
whole operating systems, but unlike C and its derivatives (C++, JavaScript, PHP), it has
very strong type checking. I believe the loose typechecking of these other languages leads
to many bugs and run-time errors because the execution does not match the programmer’s
intent.

With Pascal, the compiler knows exactly how you have defined a variable or constant or
object, and it uses it only in that manner. This not only makes your execution more
predictable, it means most programming errors are found at compile time and not when
your users go to execute the code. It also means the compiler can use efficient strategies,
and that leads to fast execution.

There is an excellent reference to the general Object Pascal language written by Marco
Cantu. It makes a great read and a reference text, and it is applicable to EWB, Free Pascal,
Delphi and others.

EWB is a product of Elevate Software in the state of New York, USA. They started out in
1998 with database technologies which they sell world-wide. In 2011 recently they
branched into web development with EWB.

I found they have excellent customer service with product support being provided by the
actual developers as well as many satisfied customers.
They test their products rigorously using automated tests, so you are not likely to discover a
bug.
EWB is written in Delphi and bears a strong resemblance to the Delphi Integrated
Development Environment.
There is also a command line version of the compiler for those who chose not to use the
IDE.

The EWB IDE includes:


• Code editor
• Object Inspector for the component library
• Project Manager to keep track of source code
• Compiler
• Optional code obfuscator / compressor
• Web server and database server
• Online help

It currently lacks a debugger, however by default the code generated is attractive


JavaScript which can be debugged by your favorite web browser.

When you are ready to ship your completed application, you can chose to compress the
source code. This converts it to minimalist JavaScript functions, variable names and
spacing to compress the size of the output files, and effectively obfuscates it from easy
reading or modification.

For a typical project, compressed and obfuscated HTML and JS files typically hover
around 1 MB total, which is the size of a typical Jpeg file and can easily be loaded by
phones and other network devices on cellular, Wifi and other Internet options.

I will focus on the most popular applications, which are Form based applications. You can
create other pages without using the libraries, but I feel that is best done as an advanced
project, if at all. The libraries benefit you with the wisdom and experience of several years
of development and refinement. And the cost (in size and performance) is low.
The reader is assumed to know some programming language like Pascal, C, PHP, C++,
JavaScript or similar.

This is not a definitive introduction. There are many texts which spend hundreds or
thousands of pages doing justice to that topic. Instead, this is intended to get a typical
programmer up-to-speed in EWB’s implementation of Object Pascal.

Pascal is a case-insensitive language. You can use any combination of capitalization at any
time, so: myObject is the same as MyObject. Just try to follow some convention or your
code will look ugly.
EWB uses Unicode for all elements. It is capable of creating applications for any Unicode
language. However, EWB cannot support some Eastern languages in the Code Editor.

For comments, EWB supports C++like // single line comments and { } for multiline
comments.

There are a few variable types:


• Integer – 52 bit signed integer
• Boolean – True or False
• Double – 64 bit floating point, maximum precision of 16 digits
• Char (Character)
• String
• DateTime – milliseconds since epoch midnight Jan 1, 1970 UTC
• Array of …
• Object
• Variant – raw native JavaScript type, not typed, can be cast to other types Strings are
immutable, you cannot change characters by saying s[3] = ‘3’ for a variable s : string.

Delphi allows dynamic and static range arrays. EWB only supports dynamic arrays. They
are always zero based, and the size (length) of the array must be set in code, just like
dynamic arrays in Delphi.

In the declaration you specify a type but not a range.


Var myTimes : array of DateTime;
To set the length of an array, use SetLength.
SetLength( myTimes, 10 );
To get the length of an array, use GetLength
ShowMessage( GetLength( myTimes ));
Once you have defined the length of the array, feel free to reference the 0 based elements.
myTimes[3] := now;
Later we will see how you can implement a form of associate arrays, but they are
implemented with TStrings and not arrays:
Eg option[‘name’] := ‘Harry’;
The standard operators are:
And, Not, Or , Xor - Boolean and arithmetic
* / div - + mod shl shr - arithmetic

a mod b is like a % b in C++.


Shl an shr are bitwise shift left and right.
Div is integer divide, whereas / is floating point divide.

There is also a + string operator which concatenates strings.


Rather than specify the order of operations, I will give you advice that works with EVERY
language, so you will never make a mistake again:
Use parentheses to tell the compiler the order you wish!
Object Pascal is an object-oriented language (OOP) gains its benefits from Objects.

If you do not come from an OOP backgroup, objects are like C structures but on steroids.
Not only do they contain data, they do so much more. They contain methods, which are
functions and procedures that implement functionality for the object.

Objects offer:
Encapsulation: The object is self-aware. It can contain data that only the objects methods
can access, and it can present that data in special ways. For example, it can act like an
array, but when you access an element, one of the methods is called to perform an action
such as update a cell in an on-screen table. So the upstream program is simplified because
it just appears to access a read-only, read/write or write-only attribute, but in reality it is
calling code.

Inheritance
Classes descend from other classes and inherit their strengths, while adding or replacing
functionality. This creates a class hierarchy with no limit on its depth.

Before we get much farther, let’s give some examples:


Type
TMainForm = class( TForm )
// visual form, knows how to display itself
TUser = class( TPersistent )
// will be user instances, can read/write itself from
storage
TFileName = class( TObject )
// we will define things like name, date, subdir…
Every visual and non-visual component in the library is based on objects: buttons, menus,
text boxes, textual captions, timers, etc.

EWB only supports single inheritance, meaning classes have a single parent, which can
also be a descendants of still other classes. This differs from some languages which permit
classes to inherit from one or more direct classes.

Much has been written about the argument of whether single inheritance is better or worse.
I’ll simple leave it as a fact that many great programs have been written with single-
inheritance languages and that it is simpler to understand and implement.

If no inheritance is specified, the class will inherit from TObject. Classes can have four
types of members:

• Private class members – accessible only to the class itself


• Protected class members
• Public class members
• Published class members

For starting developers, you will use three class types:

The protected section of a form is not used for anything in the IDE - the IDE manipulates a
special section that is internally called the designer public section. You'll notice that the
IDE puts things in the section at the top with no label - that's the designer public section. It
has the same scope behavior as public.

Private will be for variables and methods you create which only your class can use. By
default, put all variables and methods into private.

Public is the designation for all variables and methods you wish to expose to other classes
and procedures. Move variables and methods to Public when you realize they need to be
accessed externally.

Object methods can be simple functions and procedures, they can also be declared as
virtual, abstract and override.
Virtual methods can be overridden in descendant classes.

Abstract methods indicate the virtual method is NOT implemented in the base class, but
must be implemented by descendant classes. Your application will not compile until you
implement the functionality – which in turn reduces the problem of not implementing all the
requirements.

Inherited methods can augment the functionality in a base class, and can even call the base
class function.
Overloaded methods have multiple declarations. Such as add( x, y : integer) :
integer;
add( x, y : double ):double; overload; Note that EWB, unlike several
other Object Pascal variants, does not require you to use the overload keyword.

Constructors and Destructors are special methods that handle the process of creating and
destroying instances. In Delphi the constructors and destructors allocate and free memory –
which is not necessary in our JavaScript environment. However, they are also useful for
initializing variables and cleaning up afterwards.
Typically, you will not create files like C’s Header files which are separate from the
source code (.H files in the C example). Instead, a declaration section at the top of the
Pascal file does a similar thing.

Files look like:

unit Main;
interface
// types, variables, objects and methods used
// and implemented in this file

implementation
// the rest of the file is private to this file
end.

Pascal is a single-pass language meaning the compiler only reads through the source once,
whereas C is typically a two to four pass language. As a result, Pascal compiles much
faster, but it must be organized more carefully.

If you define, say, TClassDog, and it descends from TClassMammal, then you must define
TClassMammal before you define TClassDOG.
Functions and procedures may be passed variables, but these variables only have scope
inside the method called.

These methods can also reference variables and methods specified in the current class.
Pascal has the love-it-or-hate-it WITH keyword, where you can specify: WITH
InstanceDog do
….

so you do not have to specify the variable name on each line. There are many arguments for
and against this keyword. I’ll leave it to you, but remind you that WITH overrides the
normal scope of any local or class
variables/properties/methods.

Most modern programming languages let you cast from one variable type to another.

Source Type Valid Target Types Integer Integer Boolean


Double
DateTime
Enumeration
Double Double
String String Char
Char Char
String
DateTime DateTime
Integer
Boolean Boolean Integer
Enumeration Enumeration Integer
Class Instance Any same class or ancestor class type

We will use an example later where we iterate through visual components on the screen
and only react to TButtons by using is to query the class type. is does not simply look at the
current class type, it works for all inherited types too.

var
i : integer;
c : TComponent;
but : TBbutton;

begin
for i := 0 to Component.Count -1 do begin c = Components[i];
if ( c is TButton ) then begin

but := TButton( c );
but.Caption := ‘a button’; end;
end;

Good programming deals with exceptions you never expected, and those that you did
anticipate too.
In Object Pascal, the keywords are try, except, on exception, and raise.
For example, if the user tries to enter negative money, you should raise an exception.
If ( a < 0 ) then
Raise Exception.Create(‘do not use negative money’);
The component library and the run-time raise exceptions whenever necessary. Use
try..except to handle exceptions.
try a := StrToInt( edit1.Text );
except
ShowMessage(‘Could not convert ‘+edit1.Text+‘ to a number’);
end;
Or you can access the exception class variable to print out the general exception
information.

try
a := StrToInt( edit1.Text );
except
on E: Exception do
ShowMessage(‘Error:’+E.message); end;

In the exception handler you can re raise the exception for the next stacked exception
handler.
Object Pascal also implements a finalize section to implement code that always executes
whether an exception is raised or not.

try

finally
.// this code is always called
end;

Visual applications have a default handler if you do not handle the exception. It normally
prints a generic message with the exception text. You can define a TApplication.OnError
event handler which can do other things, such as give your phone support number.

EWB is not an island to itself. You may also wish to call external JavaScript libraries or
access variables in the Document Object Model (DOM), etc. Remember that JavaScript is
case sensitive, so you must be careful with casing of references. However, only the
developer of external interfaces must be concerned with case sensitivity of JavaScript, so
application code which references it from EWB Pascal is free to use any capitalization you
choose. EWB will emit the JavaScript using the case of the external interface definition
and not the case of the references to it.

The most common example of external interfaces is to access the Window’s URL. Here we
see how to switch to Google’s home page.
uses webdom;
s := ‘http://google.com’; window.location.replace( s );
Like Delphi, EWB uses a message queue to sequence operations. Async is used to schedule
future code execution. We have a whole chapter on this later.

One of the most used object classes is TStrings and its relative TStringList which both
encapsulate lists of strings.
After you create() a TStringList, you can add strings with member functions add(), delete().
You can access individual lines as though it were an array of strings. And you can deal
with the entire list as though it were a single string using the .Text attribute; You can sort()
and find() strings.

If you assign strings with a NameValueSeparator (defaults to =), you can use it as though it
were an associative array.
Eg.

var
sl : tstringlist;
name : string;
age : integer;

begin
sl := tstringlist.Create;
sl.add(‘Name=Erick’);
sl.add(‘Password=Friday’); sl.Values[ ‘Age’] := ‘49’;
name := sl.Values[‘Name’]
age := IntToStr( sl.Values[‘Age’]);

end;

The visual form designer is comprised of the form, the tabbed units editor, and the
components one can select.

The object inspector show properties and events related to a selected component. You can
select something visual, like a button, something nonvisual, or even the form itself to adjust
its properties and events.
A messages window at the bottom shows messages such as the compiled size, or errors if a
compiler error is being reported to you.
If you select Project | Options | Application, you can set the title where appears on the
browser’s header. This step is necessary to have a professional looking page.

We’ll start with a simple application and look at how it works.


Start EWB and press the New Project button, or File | New… | Project.
Select Visual Project with Form Support.
When asked, pick TForm as the ancestor Form.

So we have a blank form. As a matter of practice, save the files right away and run the
form by pressing F9 or Run | Run. It will wish to save the form and the project, put them in
a new subdirectory to keep things organized.

EWB will then show the empty form. Not very interesting yet, just a blank form. Press the
close X button above the blank page, this will return to edit mode.

From the Visual Objects collection on the upper right hand of the screen, pick the T icon
and then tap on the empty form to place it anywhere on the form. This is a text label, it
displays text. Being the first TLabel on your form, it will be called Label1. TEdit is the
class name, Label1 is the instance name.

Then move your mouse over the other icons until you find TButton, which is a button Place
it on the form too: Button1.

Double click on the button you just placed. This will open the code editor on the button’s
OnClick event handler, the method called when one clicks on a button.

Add the following code:


procedure TForm1.Button1Click(Sender: TObject); begin
Label1.Caption := 'hello'; end;
Note, you can toggle back and forth between the code editor and the visual form designer
by pressing F12.

This is a program. Press F9 to Run it. Both the label and button are visible, but they say:
Caption, which is the default text for the Caption property. But when you press the button, it
changes the Label1’s Caption property to ‘hello’, and through encapsulation, that updates
the screen automatically.

You’ve written your first program.


Now let’s make it more interactive.
Add a TEdit, which will be Edit1. It accepts user input.
procedure TForm1.Button1Click(Sender: TObject); var
i : integer;

begin
i := Edit1.Text;
Label1.Caption := i + 1;

end;

This will not compile when you press F9, because i is an Integer type and Edit1.Text
(which is the user input) is a string and not all strings can be turned into numbers. And it
would also complain because integers cannot be converted to strings in the next line

To fix this, change the code to:


procedure TForm1.Button1Click(Sender: TObject); var
i : integer;

begin
i := StrToInt(Edit1.Text);
Label1.Caption := IntToStr(i+1);

Now it compiles and runs. Enter a number and we will show the following integer.
But you could still enter a text string like George instead of a number, and you will get an
error message: Invalid Integer Value and a line number like 619.

Let’s see the actual code. Open the output subdirectory from your source subdirectory and
you will find both an HTML and a JS (JavaScript) file. The error refers to the StrToInt()
function which is implemented not in Pascal but in JavaScript.

function strtoint($0)

{
if ($0 != "" && $0[0] == "0" && ($0[1] == "x" || $0[1] ==
"X"))
var $1 = parseInt($0,16); else
var $1 = parseInt($0,10); if (!isNaN($1))
return $1;
else
throw "Invalid integer value";
};

Yes, your code is converted to JavaScript and executed by the JavaScript engine in your
web browser.
A nice way to deal with this is using Try/Except exception blocks.
procedure TForm1.Button1Click(Sender: TObject); var
i : integer;

begin
try
i := StrToInt(Edit1.Text);
Label1.Caption := IntToStr(i+1);

except
Label1.Caption := 'That was not a number';

When an error arises, the Try/Exception block jumps to the Exception code and executes it.
If there are no exceptions, the Exception block never executes.
The complete source code to our form is:
unit Unit1;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebLabels, WebBtns,
WebEdits;
type

TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
implementation
procedure TForm1.Button1Click(Sender: TObject); var
i : integer;

begin
try
i := StrToInt(Edit1.Text);
Label1.Caption := IntToStr(i+1);
except
Label1.Caption := 'That was not a number'; end.

Let’s clean it up a bit. Going back to the Visual Form Designer, look to the left of the screen
and you will find the Object Inspector. Click once on the Label1 and the Object Inspector
will show its default values. Caption is set to Caption, change this to enter a number. This
changes the startup value of the Caption.

Click once on the button1 and it its Caption to Go. Now your application looks better. You
can drag the visual controls around the screen to make the position nicely.

There is only one other piece of code to write, and it is mostly hidden and usually
automatically written, it’s the project Application source.
Go to Project | View Source and you will find something like:
project Project1;
contains Unit1;
uses WebForms, WebCtrls;

begin
Application.Title := '';
Application.LoadProgress := False;
Application.CreateForm(TForm1); Application.Run;

End

We will explore this code later, but for now the important thing is that it creates
TForm1/Form1 (our form had a name), and then it runs the application, which is all the
open forms, of which there was simply Form1.

We will change the application again to show how live it is. Go to the System tab on the
Visual Form Designer and place a TTimer on the form. The TTimer is a non-visual
component meaning it doesn’t display anything. Double click on it and enter the following
code it its Timer1Timer event handler.

procedure TForm1.Timer1Timer(Sender: TObject); begin


Label1.Caption := DateTimeToStr( now ); end;
This timer event handler is called once every second and it updates the display text to say
the current time. Press F9 to run.

Using the Object Inspector you can change the Interval parameter from 1000 ms to 10000
for every 10 seconds. The beauty of EWB is that you write clean simple functions instead
of messy JavaScript which is error prone.
Our last change shows a useful tool for displaying answers and can help tremendously in
debugging: using ShowMessage() which pops up a message on the users screen

In our previous example, the time would overwrite the user’s results, and this is not ideal.
So change the code for the Button1 to:
procedure TForm1.Button1Click(Sender: TObject); var
i : integer;

begin
try
i := StrToInt(Edit1.Text);
ShowMessage( IntToStr(i+1 ), ‘debug’);

except
ShowMessage( 'That was not a number',’debug’); end;
end;

Run your app again. Now input generates a Modal message box that blocks all input until
the user presses Ok. Unlike in Windows or even alert() in JavaScript, this box does not halt
program flow, the program keeps executing but input is blocked. This behavior is due to the
Message Queue which we will examine later.
There are some things to note. EWB applications are SPA or Single Page Applications,
like Gmail, Facebook, etc. This means you load a page once and do everything without
loading subsequent HTML/JavaScript programs from the network. This makes the
experience fast and responsive to users. It feels like a Windows program. And now web
apps are as easy to write as Delphi Windows programs.

Your application can access the network, and we will do plenty of that in time, but
typically it will do AJAX (Asynchronous JavaScript And XML) calls to a server and
update itself as needed in the background.
Only the simplest apps have a single form, real apps usually have multiple forms that either
appear simultaneously or in some sequence.

With EWB you can have as many open forms as you please (and your JavaScript run-times
allow), easily in the hundreds if not more.

Each form type is written in a separate source file, however, you may create several
instances of the same form type, in which case all instances would be based on the same
file.

Simply take your single-form app as we had in the previous chapter and add more forms by
pressing File | New | Form and then selecting the base class for your Form. Make this one
of type TDialog..
Press F9 to run, and you will see there are two forms created, one will be over the top of
the other. You can grab the dialog box by its top and drag it off of your old Form1.

Our problem is that Form2 is being create and displayed along with Form1 by the
Application’s source. So fix it by editing Project | View Source

project Project1;
contains Unit1, Unit2; uses WebForms, WebCtrls;

begin
Application.Title := '';
Application.LoadProgress := False;
Application.CreateForm(TForm1);

// comment out Application.CreateForm(TForm2);


Application.Run;
end.

Run the program and we only see the one form. Place a TLabel on Form2. It’s time to get
into the good habit of always changing your components to useful names, so go to the object
inspector and change its name property to: lbUserAnswer which stands for Label User
Answer.

Then change the Form1’s button click handler to procedure


TForm1.Button1Click(Sender: TObject); var

i : integer;
begin
if not Assigned( Form2 ) then
Form2 := TForm2.Create( Application );

try
i := StrToInt(Edit1.Text);
Form2.lbUserAnser.Caption := IntToStr( i + 1 ); Form2.Show;

except
ShowMessage( 'That was not a number'); end;
end;

and after the Implementation keyword add: uses unit2;

which indicates the module references things in the Interface section of the Unit2 file which
has our second form. Press F9 and your program will display its output in the new output
window.

The Form2 variable is defined as type TForm2 in Unit2, which we included in the uses
statement.

We use if not Assigned( Form2) because by default the object/TForm1 is not assigned any
memory – well, it was assigned, but we commented that out in the Project Source, so it
must be created manually.

Form2 := TForm2.Create(Application) creates the object and sets all the parameters to
their defaults, including all the parameters of all visual and nonvisual components of the
form.
Form2.Show shows the form, because merely creating and modifying a form isn’t enough,
you have to show it too.

The main points of this exercise are that:

- EWB generates a single web page (SWP) no matter how many forms are visible and how
many buttons one presses
- Forms are each designed in a separate unit
- Forms are auto-created in the Application source, you must comment them out if you do
not want them created and displayed at start time.
- You must instantiate each form variable with
TFormN.Create(Application).
- The only global variables we know about (so far) are Application, Form1 and Form2.
The rest are objects within these object classes. The next few chapters introduce you to
classes available in the Visual Form Designer. The online help is a complete and updated
reference; my goal here is to describe the controls and list their most noteworthy
parameters.

Among the classes you can drop in a form, the most common are from the Standard group.
They are almost all text-based, though some have optional icons.

The label is usually text, numbers, and other things which are being presented. These are
cornerstones of your visual form. You can update the Caption at any time to update the
display.

Like most controls, it is possible to edit the many object attributes. Some commonly
changed ones are:

• AutoSize – typically leave as true, otherwise you must adjust the size if you reassign the
Caption
• Caption – the text to display
• Font parameters, which include Color, GenericFamily and Size
• Background, which includes Fill… Color to highlight a text field
• Format … Wrap to turn on line wrapping
• Tag – which can hold an arbitrary number, useful for holding any data number you want
• OnClick() event handler.. yes labels can receive events, and OnDblClick() double click
handler.
• OnSize() is called when the size changes for any reason. This is a special label which has
a balloon holding the text and an optional icon property which specify one of a few
standard icons. You may choose to make it invisible by set Visible property to False at
design time, then set Visible := True to make it appear. Setting the Animations property you
can make it pop up nicely.

Yet another label extension. This one displays an alert at the top of the window.
AllowClose determines whether an X close button appears to close the error message or
not. Orientation determines whether a left sided or right sided X is used to close the error
message. It is common to create the TAlertLabel at design time but only make its Visible
property true when necessary, such as when the network request fails or a password is
incorrect.

The TButton is another workhorse.


• Its OnClick() handler is called when the user presses the button
• Its OnMouseEnter() / OnMouseExit() can be used to detect a passing

mouse for helping the user with a balloon text or some other notice.
• Icon can specify an icon
The TIconButton specifies a button with an icon (glyph) and no text or border.

• Like most controls, you can specify a different cursor for when the mouse pointer is over
the control
• Sometimes you may want to fire events as long as a button is pressed, like in a video
game. Set the RepeatClick to True and

RepeatClickInterval to a value expressed in milliseconds,


Use TDialogButton with a TDialogForm to set the modal result of a form.

• If you set ModalCancel to True, this button will be called when the user presses Esc.
• If you set ModalEnter to True, this button will be called when the user pressed
Enter/Return.
• You must set ModalResult to the value which will be returned from the modal dialog box.

TCheckBox encapsulates the CheckBox functionality.


• Set the Caption to the text string you wish to display beside the checkbox
• SelectionState can have three states that can be read or written o ssSelected – an X
appears
o ssUnselected – no X appears
o and ssIndeterminate – a dash appears to indicate we don’t know
• To act on a change to the state there are several options
o onClick – whenever it is clicked
o onChange – only when the value changes
o onMouseDown/onMouseUp
• setting the SelectionState in software will also invoke events, this can be confusing
because you might have assumed only user activity would fire the events.
• There is a ReadOnly property which means the TCheckBox can be used for output if you
wish, such as upon completion of a task.
TRadioButton work together in a group like the push buttons on a car stereo. o
SelectionState is similar to TCheckBox, except ssInderterminate does not show anything
o When using with a database, ValueSelected is the value returned when the particular
TRadioButton is selected.
o All TRadioButtons on the same page are in the default group. o Use with TGroupPanel
to specify multiple groups of TCheckBoxes

TEdit is the Edit box.


• Specify ReadOnly = True if you wish it to be output only, no writing
• Value contains the entry of the user, or the preset value
• MaxLength is the maximum number of characters allowed in a field.

Use this!
• InputType can be set to various values. It does not restrict the input but
on many devices it determines what type of virtual keyboard is
displayed.
o tiNumber
o tiEmail
o tiNone
o tiURL
• It is possible to modify keystrokes entered by intercepting the
OnKeyPress or OnKeyDown. For example, you could make a sound if a
user entered a letter in a number field.
• It is common to hook the OnKeyUp() event handler to process
keystrokes after the Edit has placed them into the Value buffer.

This control is used just like TEdit, but it displays dots instead of characters.
MultiLineEdit is a self-explanatory name.
• The text entry is stored as a TStrings property called Lines. You can specify Lines[0],
Lines[1], etc. up to Lines.Count -1
• You can also read or set the whole string with Lines.Text
• MaxLength is the number of characters allowed
• ReadOnly is useful for transforming this to a text output box
• ScrollBars can be defined as sbHorizontal, sbVertical, sbNone or sbBoth. Scroll bars are
only displayed if necessary.

The TCalendar shows a complete calendar.

• The default View is cvMonth, you can also set it to cvCentury, cvDecade, and cvYear
• Read or write the date by editing the Text field
• Dates are stored in US American format: month/day/year, you may have to convert to your
locality’s format
• See also:TDateEditComboBox

Use the TButtonComboBox to select among several button values.


• The button values are stored in a TStrings called Items with the current value specified by
ItemIndex, which can be 0 to Items.Count -1, or -1 if nothing is selected

• Text contains the current value, which is either empty string or one of Items
• Often handle OnCLick() or OnChange() events
TListBox is a box containing a list of strings.

• The strings are stored in Items, indexed by ItemIndex, or -1 if nothing is selected


• OnClick and OnChange are useful events to handle

The TDateEditComboBox is an Edit box which features a drop down calendar if the user
clicks on it.
• The date is stored in American format month/date/year
• Read or write the Text property
• Your program or the user can specify a time following the date and it will be viewable in
the Text property, but the built-in calendar only specifies a new date
• The application can read or write SelectedDate which is the DateTime selected by the
user but returns DateTime(0) if the date is invalid OR the user specified a time on that date.
SelectedDate is in the local time zone

This is an Edit/ComboBox which is useful for situations where a user needs to be able to
enter in a value or select it from a separate dialog, such as a lookup dialog that has a data-
bound grid in it.

• Set AutoComplete = True to have the browser suggest values


Use the TFileComboBox to specify a filename for uploading or downloading.
• Many browsers call the operating system’s File handling dialogs
• Text is the filename with path.

The TGrid is a wonderful component that creates a spreadsheet-like display.


• RowSelect – means select one whole row at a time
• ShowLines gives a classy lined look
• ColumnHeaders = True shows a top column shaded as a topic heading
• MultiSelect allows multiple rows to be selected at once (it requires

RowSelect := True)
• Selected items are in a Selected array
• TGridCells are arranged in TGridColumns and TGridRows
• We will show several examples later
The graphics controls are… well... graphical.

TImage encapsulates an image such as a Jpeg or Png format file.


• Specify the image filename in URL property
• Visible can be toggled to view or make invisible the thame

Displays a little icon from the standard collection


This version of the icon adds the StartAnimating and StopAnimating methods.
TPaint specifies a surface on which you can draw as needed. We will have an example
later.

The TSlideShow presents a nice slide show of your images


• takes a collection of Jpeg or Png files in a ImageURLs TStrings.
• Set Loop := True to recycle the slides
• DisplayTime and FadeTime determine how long images are displayed

and fade expressed in milliseconds


There is presently only one indicator.
The TProgressBar is a graphical indicator of how far progress has been made toward some
goal.

• MinValue <= Position <= MaxValue


• The default values are MinValue = 0, MaxValue = 100
The multimedia controls play audio or videos. There are presently no recording controls
because JavaScript alone does not support it. PhoneGap, however, does support audio and
video recording if you are doing mobile apps.

• DefaultRatePlayback 1 for 100%, less than 1 is slower, greater than 1 is faster


• AutoPlay – whether to play immediately
• Loop – whether to continuous replay the audio
• Muted – is audio muted
• SourceURL – url for the audio

• PosterImageURL – image to display for the clip when not playing


• SourceURL – URL for the clip
• DefaultRatePlayback – 1 = 100%, < 1 is slower, >1 is faster
• Loop – continuous replay
• Muted – is audio muted

Containers hold other visual controls. Sometimes they have visual boarders, but not
always.

A THeaderPanel is the header that goes at the top of a form or a panel. Typically you
would place a TLabel into the THeader which would say the name of the virtual window.
It is automatically included in a TPanel.

This is a basic panel with no scrollbars, no header.


The TPanel component is a panel control with a border and a caption bar with
minimize/restore and close buttons.
• Set CaptionBar.Caption to the name of the Window
The TPage is a tabbed control
To achieve a tabbed control, place a TPagePanel on the form, then press the + button in the
upper right hand corner for each new TPage within the control.

• Set the Tab.Caption property to the name of the panel


The TScrollPanel is used to add scroll bars to a control if they are needed, and ScrollBars
is set to sbHorizontal, sbVeritcal, or sbBoth.

THTMLForm exposes an HTML ‘form’ within the displayed page.


• Set URL to the page to view
• Method can be fmPost, fmGet, fmDelete fmHead or fmPut.
• A later example will show how to use the THMLForm to upload files.

TSizer is used to allow the user to adjust the size of controls. See the chapter on layouts.

A TSizeGrip modifies controls so a user can resize them, simply drop the TSizeGrip onto a
control and you can drag the lower right hand corner larger or smaller, up to constraints in
the control’s constraints, or the size of the bounding control.
There is only one menu class, TMenu.
• Use OnClick event which is fired when someone presses a menu option
• Index is the currently selected item.
• Use the + symbol to add menu options.
• Set Visible to show or hide the menu

TToolBar is a generic class which can hold multiple TToolBarButtons.

The TDataSetToolBar shows a perfect example of a TToolBar in use, it has buttons for
common actions like adding, deleting, modifying records and more. The TLink control has
a clickable caption which leads a browser to a different page.

• Set URL to the intended URL


Use TBrowser to have an iframe with your content. We will have several examples.

The TScript component allows you to include a JavaScript file in your application
dynamically. You must still specify an external interface from the EWB compiler. We use
this extensively in our JavaScript chapter.

Implement a Google map into your application. Specify Locations on the map. Set Options
such as MapType for roads or other options. In our Cordova/PhoneGap example we
desmonstrate how to use the TMap.

TTimer is the interval timer, the onTimer event is called periodically..


• Interval is the time between event firings in milliseconds
• Enabled means the timer will fire when the interval happens
• Timing is not guaranteed, the event may fire sometime after the

specified number of milliseconds if the system is busy doing other things.


We all know computers have various screen sizes and resolutions, but add mobile devices
to the mix, and you may have a tiny display or the massive display of a graphics
workstation for you page.

You can design your web form using the WYSIWYG (what you see is what you get) visual
form designer.

The alignment panel contains


tools to assist you with beautifying your WYSIWYG form.
The WYSIWYG feature is great, no doubt, but do you design a separate page for mobile,
and then which one in particular?

EWB supports dynamic layout and sizing of controls known in the industry as Responsive
Web Design. It seems a little bit intimidating at first, but it is powerful and relatively
simple once you get started. After only a few projects, you will get the hang of the layouts
and many of us use it in every new project.

Every visual control has:


• Constraints.Max.Wdith, Max.Height, Min.Width and Min.Height these specify the
maximum size the object can grow to fill, and the minimum size to which it can shrink. The
IDE and the run-time will both respect these constraints.

• LayoutOrder – the order in which objects are added among layout groups.
• Layout parameters:
o Consumption
o Overflow
o Position
o Reset (Boolean)
o Stretch
• Margins (left, right, top, bottom)
• Padding (left, right, top, bottom)

Objects are laid out in order specified by LayoutOrder. First 0, then 1, etc. and they are
placed into the remaining rectangle of space in the Form or container.

Position tells from where to start drawing the object. Often you will start at the TopLeft and
proceed either to the right or down. Sometimes buttons start at the bottom right and grow
leftward or up.

Consumption specifies if the direction the object grows (left, right, etc.) and eats into the
remaining rectangle.
Overflow says what to do when you run out of space in the current direction, like go Right,
go Down, etc.
Reset says to reset to the beginning since the last Reset (or the start).

Stretch tells whether to optionally grow in a direction, or not at all. If an object stretches,
its minimum and maximum constraints are observed. If it doesn’t stretch, they are ignored
because it stays the designed size.

Margin says how close an object should be to other objects, and padding is the space
inside the object reserved for spacing.
An example is needed to see how these factors all play together. This example is found on
the EWB web site under Responsive Design.

We’ve highlighted the direction, changes in direction, etc.


LayoutOrder 0 grows (consumption) loBottom, so downward.

Thus LayoutOrder 1 is below it. And it grows to the right, but has a RESET=True, so the
rectangle goes to the last reset position which is at the top of the remaining rectangle.

LayoutOrder 2 is a tricky one: it stretches to the right (consumption), but grows toward the
bottom, so the remaining rectangle is below it.

LayoutOrder 3 naturally falls below LayoutOrder 3 because we grew to the bottom. Now
we grow to the right.
Where LayoutOrder 4 is placed depends on the remaining space. If there is room, we
continue growing to the right. But if you resize the rectangle smaller, #4 has to go below
#3, because #3 specified overflow would go below.

It is useful to stretch and squeeze the browser window. Also, many browsers support a
responsive design mode which simulates the size of common devices like cell phones,
tablets, etc.

Using responsive design, your pages can be attractive and functional on all devices. But
you will have to test and apply the rules as we have done here to make sure it grows and
shrinks the way you want.

When doing responsive design, you often want to have the MultiLineEdit box grow as
needed. This can easily be accomplished in code by setting the OnKeyUp handler as
follows;

procedure TForm1.MultiLineEdit1KeyUp(Sender: TObject; Key:


Integer; ShiftKey, CtrlKey, AltKey: Boolean);
var

c : integer;
begin
c := MultiLineEdit1.Lines.Count;
MultiLineEdit1.Height :=
(c+1) * (3*MultiLineEdit1.Font.Size) div 2; end;

Don’t let the math scare you, it’s just c (the number of lines of text) incremented by one so
there is always a bit of extra room, multiplied by a bit more than the font height.
Sometimes you want the form or panel to be resizable on the desktop.
Start by setting your form’s layout to TopLeft placement and stretch BottomRight. Then
place a TScrollPanel on your form , set its layout to TopLeft placement with strech
BottomRight, and finally set the Scroll property to ssBoth.

If you only want part of the form to scroll, use a TBasicPanel to delimit the area that will
not scroll, using Layout parameters to occupy that space. Then use the above steps with
TScrollPanel on the remaining space to create a scrollable area.

You can impose Contstraints that limit the minimum and maximum size of your scollable
area.
EWB supports {$DEFINE xxx} and {$IFDEF xxx}/{$ENDIF} much like Delphi.

You can use this to mark of a section of code that you want to temporarily disable. Often
you will have a problem with a function, so {$ifdef DEBUG} it out, and {$ifdef DEBUG}
out the definition in the TYPE section too.

Another great use is to comment out commens with embedded { and } characters, like
JSON or sample C/PHP/JavaScript code. You cannot normally comment out these things
because every end brace } frustratingly turns off comments.

I use {$ifdef xxx} between different versions of my code. Modern browsers allow you to
store some data locally in the local and session storage. The space is not enormous, but it
is big enough to store useful strings.

Local storage is conveniently accessed through the LocalStorage TPersistent class with
functions like Set, Items (which gets data), and Clear which all operate on Key names.
Local storage persists for a long time, possibly years.

Session storage is access through the SessionStorageƒ TPersistent class with the same
functions as LocalStorage. Session storage generally is deleted when you exit or restart
your browser (not just closing the window or tab).

Be aware that these data, although not visible to other Web applications, are not considered
secure. The user or local applications can read the data, so do not store anything sensitive
like Social Security Numbers, passwords, etc.

Session storage is particularly useful for Web site that have pages which load and change
frequently but EWB usually does not usually involve a lot of different web pages.
However, you may wish to store which windows are open, so if the user hits reload then
you can do something intelligent to recover.
Here is the source code:

unit storage1;
interface
uses WebCore, WebComps, WebUI, WebForms, WebCtrls, WebEdits,
WebLabels, WebBtns;
type

TForm1 = class(TForm)
edSession: TEdit;
edLocal: TEdit;
Label1: TLabel;
Label2: TLabel;
btSave: TButton;
btRetrieve: TButton;
Label3: TLabel;
Label4: TLabel;
btClear: TButton;
procedure btSaveClick(Sender: TObject);
procedure btRetrieveClick(Sender: TObject);
procedure btClearClick(Sender: TObject);

private
{ Private declarations } public
{ Public declarations } end;

var
Form1: TForm1;
implementation

procedure TForm1.btSaveClick(Sender: TObject); begin


SessionStorage.Set('text', edSession.Text);
LocalStorage.Set('test2', edLocal.Text); end;

procedure TForm1.btRetrieveClick(Sender: TObject); begin


edSession.Text := SessionStorage.items['text'];
edLocal.Text := LocalStorage.items['test2']; end;

procedure TForm1.btClearClick(Sender: TObject); begin


SessionStorage.Clear('text');
LocalStorage.Clear('test2');
btRetrieveClick( Sender ); // update onscreen copies end;

end.

As we alluded to earlier, you can compile to JavaScript and debug that. And we will
discuss techniques built into EWB and also some not yet included in EWB, such as adding
breakpoints, using JavaScript variable watches, and more advanced DEWBUnit Unit
Testing is introduced.

The first step is to ensure you have not enabled compression/encryption on your project.
This way the generated HTML/JavaScript is human readable.

When trying to figure out raised exceptions, a method I found helpful for debugging
exceptions is to place a try / except block around code that has problems. Then declare a
local variable var ProgressNum : integer. In various places around your function, assign
ProgressNum to various different values (0,1,2,3…). And in your exception handler have

Except on Exception: E do
ShowMessage(‘ERROR: ‘+E.message +
IntToStr( ProgressNum ));

End;

This code fragment will pinpoint the last good spot in your program before the exception.
Note that EWB exceptions are based on JavaScript exceptions, which differ from Delphi
Pascal exceptions. For example any number div 0 in JavaScript does not generate a divide
by zero error, instead the returned result is infinity. If you depend on a divide by zero
exception, you will have to detect a zero and raise the exception yourself.
Another trick is to use ShowMessage() to show variable contents and how far you have
progressed. This has a problem under EWB in that the message boxes are all displayed
concurrently, so you will have a bunch of message boxes present.

It is possible to send debugging messages to the JavaScript debugging console. We


demonstrate this functionality later (see the index for Console.Log)

Native JavaScript is typically riddled with bugs due to the fact the language is interpreted
rather than compiled and not strongly typed. For these reasons, almost every web browser
supports debugging features for JavaScript programmers. We’ll learn how to use them.
First, you must download my debugcode.js JavaScript file and add it to your project
(Project | Options | External Files | … ) Then add the following few lines to your interface
section.

External TConsole emit console = class Public procedure log(


s :string);
End;

external function eval( s : string ):object; external


console : TConsole;

Start by adding a breakpoint to any function where you are having problems. Just add the
line:
eval(‘debugger;’);

On most browsers, F12 enables the debug screen. If it doesn’t on yours, either find it on the
menu or switch to an easier-to-use debugger like Chrome or Firefox. F12 and debug
output/breakpoints do not work on the built-in browser of the EWB IDE.

Click on the “console” button on your browser’s developer pane, it should show no error
messages except the messages you outputted with debugoutput(). If you see errors here
there is something that should be fixed. You can output text to the console window with
console.log(‘stuf’), and this is a handy way to debug – output everything that happens until
something stops.

When debugger command executes, it will bring up the debugger.

Step over the debugger statement and return to the calling function, which is typically
what you will want to debug. You can usually see the contents of any variables and classes
by clicking on their names inside the source code, and many browsers let you set a Watch
variable, just like Delphi’s debugger.
Many EWB programs have been written without the use of JavaScript debugging, but once
you see these tricks, you can speed up development by improving your efficiency
debugging your code.

Another way to set breakpoints is to edit the JavaScript output, search for some word in
your source code (constants, variable and class names are good candidates), and insert the
command debugger; where the trouble is, then run the application.

Unit Testing is the systematic creation of tests to validate software, it’s an important step in
making professional quality software.

The idea is simple, put your logic into subroutines or non-GUI class methods, then create a
sequence of tests and run them against your code and test the actual outputs against
expected outputs.
For example, look at the following two subroutines using DEWBUnit,

procedure TestProcedure( assert : TEWBU_test ); var


one : integer;
begin
one := 1;
assert.equal( one, 1 , 'one is 1'); end;

QEWBUnit’s assert.equal() method checks that the first two parameters are equal. If they
are, all is well, otherwise it logs the error that one has some other value.

Consider a less fortunate situation:

procedure TForm1.TestMethod( assert : TEWBU_test); var


one : integer;
animal : string;
begin
assert.timelimit(4);
assert.expect( 3 );
one := 1;
animal := 'cow';

Button1.Caption := 'ha';
assert.equal( one, 1 , 'one = 1');
assert.equal( animal,'dog', 'animal = dog');

assert.okay('happiness found');
assert.fail('homework');
raise exception.Create('divide by zero');
end;

QEWBUnit is told the time limit for this routine and all its results is 4ms (assert.timelimit)
and that the expected number of assertions is 3 (assert.expect).

Assert.equal passes for one = 1, but fails for animal = dog. Assert.ok logs ‘happiness
found’, and also logs the failure ‘homework’.

Finally, QEWBUnit logs the exception created and not handled by your code. All you need
to add this is copy QEWBUnit.* to your project and add the few lines:

procedure TForm1.Button1Click(Sender: TObject);


begin
hide;
qewbunitform := TQEWBUnitPage.Create( Nil );
qewbunitform.Test( 'test of a procedure', TestProcedure );
qewbunitform.Test( 'test of a class method', TestMethod );
qewbunitform.Test( 'test of a procedure AGAIN',
TestProcedure);
qewbunitform.Start;

end;

The code creates an instance of the Tester, then queues tests on a procedure, then on the
TestMethod method, and again on the initial procedure.

The Start function runs the tests in the queue. In the results below, I have checked the
“Show only failures” checkbox, and the results show only test2 failed, and which failures it
had.
Among the results are the number of milliseconds of each stage. This value is not
absolutely correct as the QEWBUnit code does consume some time doing its chores. In
general, you will find pure JavaScript typically take 50ms or less, whereas GUI operations
are often over 100ms to complete. Network operations can take 10ms to seconds or even
minutes.

Note the Assert parameter is different for each function called. And your function may
return before the whole operation is completed due to the asynchronous model – such as if
you have networking or GUI calls which happen asynchronously.

The results are green for success, red for failure, and orange for an incorrect number of
results when you set a count with Assert.expect().

In a production application you would define important functions and typical results, then
write the function to implement the results.
Eg.
procedure TForm1.TestMethod( assert : TEWBU_test); var

t : DateTime;
yesterday : DateTime;
lastweek : DateTime;
lastyear : DateTime;
begin assert.timelimit(100); // no way should it take 100 ms
t := now;
assert.equal( fn(t), ‘right now’ , 'test for right now');
yesterday := DateTime( Integer( t ) – 1000*24*60*60 );
assert.equal( fn(yesterday),’yesterday’, 'test yesterday');
lastweek := DateTime( Integer(t ) – 1000*8*24*60*60 ));
assert.equal( fn(lastweek),’last week’, ‘test last week’);
lastyear := DateTime( Integer(t ) – 1000*370*24*60*60 ));
assert.equal( fn(lastyear),’last year’, ‘test last year’);
end;

Now that you have write the function fn(x:DateTime). Typically you would leave the tests
available to test every component before every new product release; this will ensure small
changes do not break working code.

There are DUnit testers for a number of platforms. I looked at using a pure JavaScript
version, but instead got frustrated and decided to write DEWBUnit in pure EWB code to
integrate better this environment.
There are many reasons you might want to use Painting on the form, some include:

- Bar/pie/line graphs
- Schematics
- Simple maps
- Modifications to images (which we do in the FileSave example)

When you place a TPaint on your form, you create a surface called a canvas on which you
can draw using:

- Text with various fonts


- Lines (curved and straight)
- Boxes
- Circles/elipses/arcs

There are a constants to keep in mind:


- Font
- Color
- Line width
- Position

For example, to draw a box, set the canvas FillColor to a color of your choosing and call
FillRect( x, y, width, height), and it and all other things will be drawn in that color until
you specify a new color.
Call FillText( caption, x, y) to place text on the canvas using the current color.
Several line oriented functions follow a path which you can later fill or leave unfilled.

For example, when we are going to draw a pie segment, we start a path at the center, move
to and create an arc,, then ClosePath back to the centre, and finally we set a color and tell it
to fill.

BeginPath; // start afresh // move to center


MoveTo(CHART_COORD_X,CHART_COORD_Y);

// create an arc
Arc(CHART_COORD_X,CHART_COORD_Y
,CHART_RADIUS,StartAngle,EndAngle);
// finish the pie wedge ClosePath;

// get the color and fill it FillColor := clRed;


Fill;

The following example shows how to create pie and bar graphs from the same data.

Place a TPaint surface on your form, make it quite large or the following code will clip
meaning it goes beyond the size of the TPaint and is therefore not displayed.

Add two TButtons, Button1 and Button2 in the upper left hand corner, these will each draw
a graph.
unit pie;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebPaint, WebBtns;
type

TForm1 = class(TForm)
Paint1: TPaint;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject); procedure
Button2Click(Sender: TObject);

private
{ Private declarations }
procedure ClearChart;
public

{ Public declarations }
procedure DrawPie;
procedure DrawBars;

end;
var
Form1: TForm1;
implementation

const
CHART_COORD_X = 200; CHART_COORD_Y = 200;

CHART_RADIUS = 150; CHART_STEP = 50;

CHART_LABEL_X = 700;
CHART_LABEL_Y = 100;
CHART_LABEL_HEIGHT = 30;

// Routine to clear the canvas, ie. draw a white box over it


procedure TForm1.ClearChart;
begin

with Paint1.Canvas do begin


FillColor:=clWhite;
FillRect(0,0, Width, Height );
end;

end;

procedure TForm1.DrawPie;
var
I: Integer;
Data: array of Integer=[12,23,34,45];
Colors: array of
TColor=
[clElevateLightRed,clElevateLightBlue,clElevateLightOrang
e,clElevateLightGreen];
Labels: array of String=['North','South','East','West'];
LabelX: Integer= 400;
LabelY: Integer= 100;
Total: Integer=0;
Angles: array of Double;
StartAngle: Double;
EndAngle: Double;
Begin
ClearChart;
// Total will be the sum total of all numbers in pie for
I:=0 to Length(Data)-1 do
Inc(Total,Data[I]);
// create a list of angles SetLength(Angles,Length(Data));
for I:=0 to Length(Data)-1 do
Angles[I]:=((Data[I]/Total)*PI*2);
StartAngle:=(-PI/2);

with Paint1.Canvas do
begin
StrokeStyle:=dsColor;
Font.Name:='Segoe UI';
Font.Size:=14;
Font.Color:=clElevateLightBlack;
TextBaseLine:=blTop;
StrokeColor:=clTransparent;
LineWidth:=1;
// cycle through the numbers drawing the pie for I:=0 to
Length(Data)-1 do
begin
EndAngle:=(StartAngle+Angles[I]);
with Paint1.Canvas do
begin
BeginPath; // start afresh

// move to center
MoveTo(CHART_COORD_X,CHART_COORD_Y);
// create an arc
Arc(CHART_COORD_X,CHART_COORD_Y
,CHART_RADIUS,StartAngle,EndAngle);
// finish the pie wedge ClosePath;

// get the color and fill it FillColor:=Colors[I];


Fill;

// draw the little box


FillRect(LabelX,(LabelY+(CHART_LABEL_HEIGHT*I)),20,20);

// draw our black text


FillColor:=clElevateLightBlack;
FillText(Labels[I],(LabelX+CHART_LABEL_HEIGHT),

(LabelY+(CHART_LABEL_HEIGHT*I))+2); end;
StartAngle:=EndAngle;

end;
Paint1.Canvas.BeginPath; // reset subpath for canvas end;

procedure TForm1.DrawBars;
var
I : Integer;
x, y: double;
Data: array of Integer=[12,23,34,45];
Colors: array of
TColor=
[clElevateLightRed,clElevateLightBlue,clElevateLightOrang
e,clElevateLightGreen];
Labels: array of String=['North','South','East','West'];
LabelX: Integer= 400;
LabelY: Integer= 100;
MaxHeight: Integer=0;

begin
ClearChart;
// get the height for scaling
for I:=0 to Length(Data)-1 do
MaxHeight := max( MaxHeight, Data[I] );

with Paint1.Canvas do
begin
StrokeStyle:=dsColor;
Font.Name:='Segoe UI';
Font.Size:=14;
Font.Color:=clElevateLightBlack;
TextBaseLine:=blTop;
StrokeColor:=clTransparent;
LineWidth:=1;
end;

for I:=0 to Length(Data)-1 do


begin
y := ( Data[I] * CHART_RADIUS ) / MaxHeight;
x := CHART_COORD_X - CHART_RADIUS + I * CHART_STEP;
with Paint1.Canvas do begin
FillColor:=Colors[I];
FillRect(x,CHART_COORD_Y,CHART_STEP,-y);
FillRect(LabelX,(LabelY+(CHART_LABEL_HEIGHT*I)),20,20);
FillColor:=clElevateLightBlack;
FillText(Labels[I],(LabelX+CHART_LABEL_HEIGHT),

(LabelY+(CHART_LABEL_HEIGHT*I))+2);
end;
end;

Paint1.Canvas.BeginPath; // reset subpath for canvas


procedure TForm1.Button1Click(Sender: TObject); begin

DrawPie;
end;
procedure TForm1.Button2Click(Sender: TObject); begin
DrawBars; end;
end.

Unit: WebUI
Alpha
CompositeOperation
FillColor
FillGradient
Global Alpha (Transparancy) of canvas, default 1.0

How are source pixels combined with destination pixels during drawing operation, default
coSourceOver

Fill color when FillStyle set to dsColor


Gradient to use when FillStyle set to dsGradient

FillPattern
FillStyle
LineCapStyle

LineJoinStyle
LineWidth MiterLimit

ShadowBlur
ShadowColor ShadowOffsetX ShadowOffsetY StrokeColor
StrokeGradient StrokePattern StrokeStyle

TextAlign
TextBaseLine
Pattern to use when FillStyle set to dsPattern One of: dsColor, dsGradient, dsPattern

How are lines terminated when drawn, default csButt, other options are csRound and
csSquare How are lines joined when they intersect, default jsMitre, others jsLevel,
jsRound
Line width for drawing (stroking) on the canvas Upper limit when LineJoineStyle is set to
jsMitre, default 10

How much blur, default is 0, which is crisp What color is the shadow if ShadowBlur is not
zero Horizontal offset of shadow, default 0
Veritcal offset of shadow, default 0
Line color when StrokeStyle is set to dsColor Line gradient when StrokeStyle set to
dsGradient Line pattern when StrokeStyle set to dsPattern

Line drawing stype: One of dsColor, dsGradient, dsPattern


How is text aligned when drawing, default: taLeftJustify, other values taRightJustify,
taCenter

Vertical alignment of text, default: blAphabetic, other values blBottom, blHanging,


blIdographic, blMiddle, blTop
Arc
ArcTo

BeginPath
Add an arc along the current subpath.
Add a straight line and an arc to the current subpath Discard any existing subpath and begin
new subpath with no defined starting point.
BezierCurveTo Add a cubic Bezier curve to the path.
ClearRect Draw a transparent clBlack rectangle, does not affect current path Clip
ClosePath

Compute the intersection of the inside of the current path with the current clipping region
and use the smaller region as the new clipping region.

ConvertToDataURL

Close the path by adding a line back to the first point, then start a new
Path

Convert the contents of the Canvas to a URL, specify either Image/PNG,


or image/Jpeg

DrawImage Fill
Draw an image onto the canvas

fill the current path of the canvas with the color, gradient, or pattern specified by the
FillStyle, FillColor, FillGradient, and FillPattern

FillRect
FillText
IsPointInPath
LineTo
MeasureText
MoveTo
QuadraticCurveTo
Rect
Rotate
Save

Fill the specified rectangle with the color, gradient, or pattern specified by the FillStyle,
FillColor,
FillGradient, and FillPattern properties.

Draw the specified text at the specified point with the font specified
by the Font property, and the color, gradient, or pattern specified by
the FillStyle, FillColor, FillGradient, and FillPattern properties.

Does the point fall within or on the edge of the current path?
Add a line segment to the current path starting at the current point.
Measure the length of the specified text if drawn with the current font settings

Move the current point to the specified location and begin a new
Subpath

Add a quadratic Bezier curve to the current path, and the new position will be the ending
point.

Add a rectangle to the current path of the canvas. The added rectangle has its own subpath
not connected to any other subpaths in the current path. After this
method is called, the current point on the canvas is set to the point specified by the first two
parameters

Change the transformation matrix such that all


subsequent drawing is rotated by the specified
number of radians.
Push the current properties of the canvas to a stack for drawing some time later. Use
Restore to recover the settings.

Scale
SetTransform
Stroke
StrokeRect
StrokeText
Transform Translate
Change the transformation matrix to scale the future X and Y drawing by these factors.
Directly set the
transformation matrix.

Stroke (draw) the current path of the canvas with the color, gradient, or pattern specified
by the StrokeStyle, StrokeColor, StrokeGradient, or StrokePattern properties. The
LineCapStyle, LineJoinStyle, MiterLimit and LineWidth properties control how lines
of the current path are drawn.

Stroke (draw) the specified rectangle with the color, gradient, or pattern specified by the
StrokeStyle, StrokeColor, StrokeGradient, or StrokePattern properties. The LineCapStyle,
LineJoinStyle, MiterLimit and LineWidth
properties control how the lines of the rectangle are drawn.

Draw the outline of the specified text at the specified point with the font specified by the
Font property, and the color, gradient, or
pattern specified by the StrokeStyle,
StrokeColor, StrokeGradient, or StrokePattern properties. The LineCapStyle,
LineJoinStyle, MiterLimit and LineWidth properties control how the lines that make up
the outline of the font characters are drawn. See online notes about condensed text if the
specified text does not fit within the specified width.

Directly set the transformation matrix.

Move the following commands to the horizontal and vertical offsets specified, ie. slide the
image in a given direction.
I recommend ‘The JavaScript Bible’ which has details on using these thinly disguised
JavaScript functions.
Sometimes you must know a little bit about JavaScript or HTML5 to do what you want.
Such is the case with resizing images or manipulating them.
Here we have a little program with a ButtonComboBox whose five items are set to 100
percent, 50 percent, 33 percent, 25 percent and 200 percent. Its onChange property is set to
ButtonComboBox1Change;

We add a TImage Image1 used to load an image. We set its URL property to any picture of
our choosing. Its OnLoad property is set to Image1Load which resizes the image to its fully
loaded size and makes it invisitble.

And we place a TPaint Paint1 onto the form.


unit resize1;
interface
uses WebCore, WebDom, WebUI, WebForms, WebCtrls, WebEdits,
WebBrwsr, WebPaint;
type

TForm1 = class(TForm)
ButtonComboBox1: TButtonComboBox;
Image1: TImage;
Paint1: TPaint;
procedure ButtonComboBox1Change(Sender: TObject); procedure
Image1Load(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
external function eval( s: string) : object; implementation

procedure TForm1.ButtonComboBox1Change(Sender: TObject); var


percent : integer;
i : integer;
s : string ;
newwidth, newheight : double;

context : THTMLCanvasRenderingContext2D;
paint, img : THTMLElement;
begin

s := ButtonComboBox1.Text; i := Pos(' ', s );


if i > 0 then begin

s := Copy( s, 1, i - 1); percent := StrToInt( s ); end else


percent := 100;
newwidth := (Image1.ActualWidth * percent) / 100; newheight
:= ( Image1.ActualHeight * percent ) / 100;
Paint1.Width := Round(newheight); Paint1.Height :=
Round(newheight);
Image1.clientID := 'image'; Paint1.clientID := 'paint';
img :=
THTMLElement(window.document.getELementById('image')); img
:= THTMLElement(img.childNodes[0]);

paint := THTMLElement(
window.document.getElementById('paint'));
context := THTMLCanvasElement( Paint ).getContext('2d');

context.drawImage(img, 1,1, newwidth, newheight); end;

procedure TForm1.Image1Load(Sender: TObject); begin


Image1.Width := Image1.ActualWidth;
Image1.Height := Image1.ActualHeight;
Image1.Visible := False;
end;
end.

EWB does not use HTML IDs, so we set the clientID that in turn sets the HTML ID to
something recognizable, in this case ‘image’ and ‘paint’.
The variable Paint is set to the HTML element JavaScript finds with the ID paint.

The variable is set to the HTML element JavaScript with the ID image, but EWB uses the
container of the Paint1 object which is a DIV, so we have to get its one and only child,
which is the true paint.
From there, it’s a simple matter of getting a HTML5 2d drawing context, and then drawing
the image on using whatever size we want.
Providing attractive displays is not enough, we often need the ability to transfer files
between the web client and server, and the other direction too.

The ability to upload files is one of the important functions of web pages.

The power of EWB can lead you to forget that it is limited by the JavaScript restrictions,
one of which is that file access is severally crippled to prevent web sites from stealing
files from your hard drive.

It is possible to upload files to a web server using techniques similar to other web page
editors.

First, drop a Containers | THTMLForm onto your form. This will be the HTML form that is
uploaded to the web server. Only controls inside this form are uploaded.

Place a TFileComboBox into the THTMLForm, and also a Edit and a TButtton. If these are
not inside the THTMLForm the whole thing will fail.

Place a Web | TBrowser elsewhere on the form. It will hold the output from the web server.
Set HTMLForm1’s Output to Browser1 which you just placed on the form.

Set the HTMLForm1.URL to http://localhost/formsubmit which is EWB’s sample web page


that just echoes back what you send it.
Set the Button1’s OnClick method to:
procedure TForm1.Button1Click(Sender: TObject); begin
HTMLForm1.Submit;
end;

Now when you run your web page, you can select a file to upload, enter a text string in
Edit1, and press Button1 to upload the file. The WebBrowser1 will display the output from
the web page.
By default, HTMLForm1 will POST the data as a MultiPartForm, which is appropriate for
binary data. You can change this in the Encoding parameter to feTextPlain or
feURLEncoded, and in Method to any valid HTTP method.

To download a data file you act as though you are going to replace the current web page
with a new one. This causes the browser to do an HTTP/HTTPS GET of the specified
URL.

uses webdom; …
url := ‘report.php’;
s := url + ‘?report=transaction.csv’
window.location.replace( s );

Then the HTTP/HTTPS server script indicates through MIME (Multimedia Internet Mail
Extensions) that the file is not HTML but rather some other format, and that its filename is
whatever, and that the file should be downloaded and not displayed. If you do not indicate
that the file should be saved with a filename, it will instead open up in the appropriate
application (eg. PDF viewer, Excel, Word, etc.).

report.php :

header('Status: 200');
header('Content-type: text/csv');
$u = "answers.csv";
header("Content-disposition: attachment; filename=$u;");

print “a, 1, 3, 5\n”;

If you wish, you can download the file in a Web | TBrowser, which will load the new file
into an HTML IFRAME simply by setting the TBrowser’s URL to the appropriate web
address. This has the benefit that you can use POST to download the file, and POST
arguments are hidden from web server logs unlike GET arguments.
EWB uses a totally event driven model where nothing ‘blocks’.

There is no sleep() function, no waiting for network events like a return to our
HTTP/HTTPS request, no waiting for user input. Instead we use a TTimer to schedule a
delay, a network request invokes HTTP/HTTPS request handler when the request fails or
success, keystrokes and other input generate event methods for affected components.

This is a change from Delphi, where ShowMessage() or MessageBox() would block until
the user accepted the results… we cannot block, we can only complete event handlers.
(The exceptions to this non-blocking are described under window.alert() and similar
functions of the JavaScript chapter.)
There is also no concept of a TThread or multithreading in normal EWB (though we
present a multithreading solution in a later chapter). But there are workarounds; we can
schedule several short programming segments to occur in sequence. This is accomplished
with the async keyword.

For example, consider this code


procedure TForm1.Button1Click(Sender: TObject); var
i, j, k : integer;

begin
for i := 1 to 10000 do begin
Edit1.Text := IntToStr( i );
for j := 1 to 1000 do

k := j;
end;
end;

It is a for loop that would update Edit1’s Text to the numbers 0 through 10,000. And since
there is an internal for j := loop, it would delay between each update. The for j loop is not
intended to provide a specific delay, it’s just an example of something keeping the cpu
busy.
Try it. It doesn’t work the way you thought. The only things that happen are that there is a
five second delay while it calculates, and then the number 10000 is displayed.

EWB executes all code, then schedules an update to the display when all changes are
made.

In this example, if you want to show intermediate results, the trick is to use async to
schedule future code to execute. Async saves the state of all parameters necessary to
complete the call sometime in the future.

procedure TForm1.UpdateOneEdit1( v : integer ); var


j , k : integer;
begin
Edit1.Text := IntToStr( v );
for j := 1 to 1000 do
k := j ;
if ( v < 1000 ) then async UpdateOneEdit1( v + 1 ); end;
procedure TForm1.Button2Click(Sender: TObject); var
i, j, k : integer;

begin
async UpdateOneEdit1( 0 );
end;

The UpdateOneEdit() chains to a future UpdateOneEdit() by calling the async function.


This code will update the counter for every number and is slowed down by the counter.
Note, the counter speed will vary by CPU, some will be faster or slower than others.
The ShowProgress and HideProgress can be used to display a busy message during slow
execution. ShowProgress presents a modern animation that informs users about the delay
and disables the user input until you call HideProgress.

procedure TForm1.UpdateOneEdit1( v : integer ); var


j , k : integer;
begin
Edit1.Text := IntToStr( v );

for j := 1 to 1000 do
k := j ;
if ( v < 1000 ) then async UpdateOneEdit1( v + 1 )
else HideProgress;
end;

procedure TForm1.Button2Click(Sender: TObject); begin


ShowProgress( 'updating');
async UpdateOneEdit1( 0 );
end;

In normal operations like generating a web request, you will call ShowProgress() and then
the request function without using async. The request will start the HTTP/HTTPS
connection then return to the event handler.

When the HTTP/HTTPS result is returned, your request handler is called, so you process
the data and then call HideProgress.
Tag is a property on every component that has no declared use, it’s usable by you for any
purpose you want.

For example, when you write a calendar app, you can use Tag to hold the datetime or an
event number of the calendar event you are displaying in a TLabel. Then when the user
clicks on the TLabel, you know exactly when it will happen.

Another popular use is to set a TEdit’s Tag := 0 in the OnShow event, and set it to 1 in an
OnChange event. Then you would know if the user changed any details, and which details
since the edit was available.
You can set the Tag to an ordinal in an array of objects or a TObjs or TStringList, so the
Tag will give an index to which object or string it references.

You are limited only by your imagination and the 52 bits of the signed Integer type.
Every environment seems to have a different format for storing datetimes. In EWB we use
the 52 bit integer to hold the number of milliseconds since the Epoch of January 1, 1970
UTC (universal time coordinates).

Very likely UTC is not your current timezone, so you often want to present local time to the
user, and read local time from them, but often servers store UTC time if they are expected
to serve a world-wide market.

There are many functions to convert between DateTime and other formats, usually integers
or strings:

YearOf( x : DateTime , optionalUTC :boolean ) returns the year as an integer. If you do not
specify the optionalUTC parameter, it defaults to false, meaning local time.

MonthOf… returns the month


DayOf … returns the day of the month

DateToStr( x : DateTime , optionalUTC : boolean ) DateTimeToStr( x : DateTime,


optionalUTC : Boolean ); TimeToStr( x : DateTime, optionalUTC : Boolean );
StrToDateTime( x : DateTime , optionalUTC :Boolean);

The DateTime and String conversion funcitons depend on the TFormatSettings


ShortDateFormat
M The month number with no leading zero

MM The month number with a leading zero if


the month number is less than 10
d The day number with no leading zero
dd The day number with a leading zero if
the day number is less than 10
yy The last two digits of the year number with a leading zero
yyyy The full four digits of the year number
and the ShortTimeFormat is defined with

h The hour number (12-hour clock) with no leading zero hh The hour number (12-hour
clock) with a leading zero if the hour number is less than 10
H The hour number (24-hour clock) with no leading zero

HH The hour number (24-hour clock) with a leading zero if the hour number is less than 10
m The minute number with no leading zero
mm The minute number with a leading zero if the
minute number is less than 10
s The second number with no leading zero
ss The second number with a leading zero if
the second number is less than 10
tt The AM/PM designation for a 12-hour clock literal

DateTimeToISOStr() returns a standard ISO string which is computer parseable and yet
human readable. It is based on UTC time.
The ability to do animations can give your application a nice look. You can animate any
control, including containers as well as individual TButtons, etc.

The animations will occur whenever there is a change to the size or visibility of a control.
You do not have to schedule a TTimer to do the animation, animations are built-in.

The following minimalist program shows how to animate a TButton and TListBox. Every
time you press the TButton, the TButon and TListBox will grow 50% by slowly expanding.

unit main;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebLabels,
WebLists, WebBtns;
type

TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
implementation
procedure EaseIn( Ctrl : TControl ); var
h : integer;

begin
ctrl.BeginUpdate;
h := Ctrl.Height;
with Ctrl.Animations.Height do begin

Duration := 1000;
Style := asQuadEaseIn;
end;
Ctrl.Height := h +(h div 2);
ctrl.EndUpdate;
end;

procedure TForm1.Button1Click(Sender : TObject); begin


EaseIn( ListBox1 );
EaseIn( Button1 );
end;
end.

Nomally you would click on the Object Inspector and set the Animations as constants
rather than entering them in code.

The basic animations are:


- Height
- Left
- Opacity
- Top
- Visible
- Width

Each attribute has


- Duration in milliseconds, best set to 500 or so
- Style

Style can be one of: asBackEaseIn


asBackEaseInOut
asBackEaseOut

asBackEaseOutIn Easing equation function for a back easing in: accelerating from zero
velocity.
Easing equation function for a back easing in/out: acceleration until halfway, then
deceleration. Easing equation function for a back easing out: decelerating from zero
velocity.
Easing equation function for a back easing out/in: deceleration until halfway, then
acceleration. asBounceEaseIn
asBounceEaseInOut
asBounceEaseOut
asBounceEaseOutIn
asCircEaseIn
asCircEaseInOut
asCircEaseOut
asCircEaseOutIn
asCubicEaseIn
asCubicEaseInOut
asCubicEaseOut
asCubicEaseOutIn

asElasticEaseIn Easing equation function for a bounce


(exponentially decaying parabolic bounce) easing in: accelerating from zero velocity.
Easing equation function for a bounce
(exponentially decaying parabolic bounce) easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a bounce
(exponentially decaying parabolic bounce) easing out: decelerating from zero velocity.
Easing equation function for a bounce
(exponentially decaying parabolic bounce) easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for a circular easing in: accelerating from zero velocity.
Easing equation function for a circular easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for an exponential easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for a circular easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a cubic easing in: accelerating from zero velocity.
Easing equation function for a cubic easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a cubic easing out: decelerating from zero velocity.
Easing equation function for a cubic easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for an elastic
(exponentially decaying sine wave) easing in: accelerating from zero velocity.
asElasticEaseInOut

asElasticEaseOut
asElasticEaseOutIn
asExpoEaseIn
asExpoEaseInOut
asExpoEaseOut
asExpoEaseOutIn
asLinear
asNone
asQuadEaseIn
asQuadEaseInOut
asQuadEaseOut
asQuadEaseOutIn
asQuartEaseIn

asQuartEaseInOut Easing equation function for an elastic


(exponentially decaying sine wave) easing in/out: acceleration until halfway, then
deceleration. Easing equation function for an elastic
(exponentially decaying sine wave) easing out: decelerating from zero velocity.
Easing equation function for an elastic
(exponentially decaying sine wave) easing out/in: deceleration until halfway, then
acceleration. Easing equation function for an exponential easing in: accelerating from zero
velocity. Easing equation function for an exponential easing in/out: acceleration until
halfway, then deceleration.
Easing equation function for an exponential easing out: decelerating from zero velocity.
Easing equation function for an exponential easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for a simple linear tweening, with no easing.
No animation style.

Easing equation function for a quadratic easing in: accelerating from zero velocity.
Easing equation function for a quadratic easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a quadratic easing out: decelerating from zero velocity.
Easing equation function for a quadratic easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for a quartic easing in: accelerating from zero velocity.
Easing equation function for a quartic easing in/out: acceleration until halfway, then
deceleration.
asQuartEaseOut

asQuartEaseOutIn
asQuintEaseIn
asQuintEaseInOut
asQuintEaseOut
asQuintEaseOutIn
asSineEaseIn
asSineEaseInOut
asSineEaseOut

asSineEaseOutIn Easing equation function for a quartic easing out: decelerating from zero
velocity.
Easing equation function for a quartic easing out/in: deceleration until halfway, then
acceleration.
Easing equation function for a quintic easing in: accelerating from zero velocity.
Easing equation function for a quintic easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a quintic easing out: decelerating from zero velocity.
Easing equation function for a quintic easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a sinusoidal easing in: accelerating from zero velocity.
Easing equation function for a sinusoidal easing in/out: acceleration until halfway, then
deceleration.
Easing equation function for a sinusoidal easing out: decelerating from zero velocity.
Easing equation function for a sinusoidal easing in/out: deceleration until halfway, then
acceleration.

There are a lot of choices. Most developers follow some theme rather than a random attack
of the GUI on the poor user. Use them tastefully. As we will see later, TGrid is very useful
in database applications. But sometimes you will use TGrid on its own, just as a
convenient way to display and maybe update data.

One noteworthy application is in generating an online receipt for a transaction. The grid
looks good, and it can have a variable number of rows and columns.
For our TGrid example, place a TButton on the form and a TGrid. The rest we can do in
code.
procedure TForm1.Button1Click(Sender: TObject); begin

with Grid1 do begin


if ColumnCount < 2 then begin
NewColumn;
NewColumn;
AppendRow;
AppendRow;

end;
Columns[1].ControlType := ctEdit; Rows.Row[0].Value[0] :=
'Name'; Rows.Row[0].Value[1] := 'Erick';
Rows.Row[1].Value[0] := 'Org'; Rows.Row[1].Value[1] :=
'Engineering';

end; end;

We first check the column count, otherwise we’d keep appending columns every time you
press the button.

We set column[1] as editable so a user can change its values. Not so for column[0], it is
readonly.
A popular question that appears on the message board asks how to make color versions of
TButtons or TGrids.

The long answer is that you can create class decendant controls. That is truly the best
possible result, as these controls can then be added to the component palette and employed
in all your subsequent projects.

The source code to the component library is included with your EWB license, so you can
refer to many example components there.

But doing that takes a lot of coding and clicking and perfecting. Suppose you want a quick-
and-dirty solution which solves your particular problem without all the effort.

The first thing to do is pick the parent control on which you wish to base your new control.
Consider TButton, we’ll add a color to it so there is a new property called color of type
TColor.

unit tcolorbutton1;
interface
uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebLabels;
type

TForm1 = class(TForm)
Button1: TButton;
Label1: TLabel;
procedure Form1Show(Sender: TObject); procedure
Button1Click(Sender: TObject);

private
{ Private declarations }
butRed, butGreen, butBlue : TColorButton;
function MakeColorButton( caption : string;
color : TColor ; myleft : integer ):TColorButton; public
{ Public declarations }
end;

TColorButton = class(TButton)
protected
fFillColor : TColor;
procedure InitializeProperties; override; procedure
UpdateInterfaceState; override; function getFillColor :
TColor;
procedure setFillColor(const col : TColor );

published
property FillColor : TColor read getFillColor write
setFillColor;
end;
var
Form1: TForm1;
implementation

procedure TColorButton.UpdateInterfaceState; begin


inherited UpdateInterfaceState;
Element.Background.Fill.Color := fFillColor; end;

procedure TColorButton.InitializeProperties;
begin
inherited InitializeProperties;
fFillColor := clRed; // default is some vibrant color end;

function TColorButton.getFillColor : TColor; begin


result := fFillColor;
end;

procedure TColorButton.setFillColor( const col : TColor );


begin
fFillColor := col;
Element.Background.Fill.Color := col;

end;
// user application code

function TForm1.MakeColorButton( caption : string ;color :


TColor ; myleft : integer ):TColorButton;
begin
result := TColorButton.Create( self );
result.Parent := self;
result.Left := myleft;
result.Top := Button1.Top;
result.FillColor := color;
result.Caption := caption;
result.OnClick := Button1Click;

end;
procedure TForm1.Form1Show(Sender: TObject);
begin
butRed := MakeColorButton( 'red', clRed, Button1.Width + 10
+

Button1.Left );
butGreen := MakeColorButton( 'green', clGreen,
2*(Button1.Width + 10 ) + Button1.Left );
butBlue := MakeColorButton( 'blue', clBlue, 3*(Button1.Width
+
10) + Button1.Left );
end;

procedure TForm1.Button1Click(Sender: TObject); begin


ShowMessage( 'button says ' + TButton(Sender).Caption); end;
end.

Looking at TForm, we declare three buttons (butRed, butGreen and butBlue) and a
MakeColorButton() function which creates the buttons parallel to a normal TButton we
dropped on the form.

private
{ Private declarations }
butRed, butGreen, butBlue : TColorButton; function
MakeColorButton( caption : string;

color : TColor ; myleft : integer ):TColorButton;

MakeColorButton just makes a regular button, but adds a FillColor which is new.

function TForm1.MakeColorButton( caption : string ; color :


TColor ; myleft : integer ):TColorButton;
begin
result := TColorButton.Create( self );
result.Parent := self;
result.Left := myleft;
result.Top := Button1.Top;
result.FillColor := color;
result.Caption := caption;
result.OnClick := Button1Click;
end;

The form’s OnShow handler creates the three buttons.


procedure TForm1.Form1Show(Sender: TObject);
begin
butRed := MakeColorButton( 'red',

clRed, Button1.Width + 10 + Button1.Left ); butGreen :=


MakeColorButton( 'green',
clGreen, 2*(Button1.Width + 10 ) + Button1.Left ); butBlue
:= MakeColorButton( 'blue',
clBlue, 3*(Button1.Width + 10) + Button1.Left ); end;

So now we just need a class that does everything TButton does, but adds a color
background. We declare it as a descendant of TButton class, and we add an fFillColor
value and two helper function getFillColor and setFillColor which get and set the
published property FillColor.

TColorButton = class( TButton )


protected
fFillColor : TColor;
procedure InitializeProperties; override; procedure
UpdateInterfaceState; override; function getFillColor :
TColor;
procedure setFillColor(const col : TColor );

published
property FillColor : TColor read getFillColor write
setFillColor;
end;

getFillColor and setFillColor just access the fFillColor data.


function TColorButton.getFillColor : TColor; begin
result := fFillColor;
end;

procedure TColorButton.setFillColor( const col : TColor );


begin
fFillColor := col;
Element.Background.Fill.Color := col;
end;

We also define two protected override functions. The system calls these overridden
functions instead of the default functions of TButton of the same name.

procedure InitializeProperties; override; procedure


UpdateInterfaceState; override;
InitializeProperties simply calls the inherited (TButton’s) InitializeProperties, then sets a
default color in case the user forgets.

procedure TColorButton.InitializeProperties;
begin
inherited InitializeProperties;
fFillColor := clRed; // default is some vibrant color end;

Finally, here’s the magic function:

procedure TColorButton.UpdateInterfaceState; begin


inherited UpdateInterfaceState;
Element.Background.Fill.Color := fFillColor; end;

It calls the usual UpdateInterface of the TButton, but then reasserts the Background Fill
Color as whatever we’ve defined.
That’s it. Of course, you have to know that UpdateInterfaceState is called to get the
component to update itself. But now you know.
Creating color, bold, italics, underline and strikeout options on grids is quite easy.
The result looks like this
The trick is simply to create an OnCellUpdate event handler which adjusts the font
characteristics.
First, add a TGrid to your form, then use the OnShow event to add data to your Grid.
unit colorgrid1;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebGrids;
type

TForm1 = class(TForm)
Grid1: TGrid;
procedure Form1Show(Sender: TObject);

private
{ Private declarations }
procedure OnCellUpdate( Col : TGridColumn ;

ACell: TGridCell );
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
procedure TForm1.OnCellUpdate( Col : TGridColumn ;
ACell: TGridCell );
var
textcell : TGridTextCell;
newcol : TColor;
fo : TFont;

begin
textcell := TGridTextCell( ACell );
fo := ACell.Font;
case textcell.index of

0 : begin
fo.Color := clRed;
fo.style.italic := True;
fo.style.underline := True;

end;
1 : fo.Color := clBlue;
end;
end;
procedure TForm1.Form1Show(Sender: TObject); begin
with Grid1 do begin

if ColumnCount < 2 then begin


NewColumn;
NewColumn;
AppendRow;
AppendRow;

end;
Columns[1].ControlType := ctEdit; Rows.Row[0].Value[0] :=
'Name'; Rows.Row[0].Value[1] := 'Erick';
Rows.Row[1].Value[0] := 'Org';
Rows.Row[1].Value[1] := 'Engineering';

Columns[1].OnCellUpdate := OnCellUpdate; end;


end;

First, add a TGrid to your form, then use the OnShow event to add data to your Grid.
procedure TForm1.Form1Show(Sender: TObject); begin
with Grid1 do begin

if ColumnCount < 2 then begin


NewColumn;
NewColumn;
AppendRow;
AppendRow;

end;
Columns[1].ControlType := ctEdit; Rows.Row[0].Value[0] :=
'Name'; Rows.Row[0].Value[1] := 'Erick';
Rows.Row[1].Value[0] := 'Org';
Rows.Row[1].Value[1] := 'Engineering';

Columns[1].OnCellUpdate := OnCellUpdate; end;


end;

Notice that we hard code the values above. In practice you can either set them to variables
or use the database to query the value.
The trick to getting color is simply to create an OnCellUpdate event handler which adjusts
the font characteristics.

procedure TForm1.OnCellUpdate( Col : TGridColumn ;

ACell: TGridCell );
var
textcell : TGridTextCell;
newcol : TColor;
fo : TFont;

begin
textcell := TGridTextCell( ACell );
fo := ACell.Font;
case textcell.index of

0 : begin
fo.Color := clRed;
fo.style.italic := True;
fo.style.underline := True;

end;
1 : fo.Color := clBlue;
end;
end;

TextCell.index contains the row number of the value, so this example bases color and font
information on the particular row. And remember, in the OnShow event we only applied
the OnCellUpdate handler to Column #1. In practice you could apply it to many columns.
Finally, you may want to base your decision of particular colors on other factors. For
example, if the data is N/A, or < 0, or whatever, you may want to flag data problems with
red.

if textcell.Data = 'N/A' then fo.Color := clRed;

Persistence is the topic of storing data and recovering it later. Serialization is the ability to
take an object, flatten it out to some intermediate format for sending over a network or
storing in a database. Deserialization is the inverse, taking the intermediate format and
converting it back into a usable object (though possibly in a different environment and/or
programming language).

In a nutshell, the problem is this:

1. We have an object holding some data, say a customer record, or an accounting record, or
an inventory item sitting on the server database.
2. We want to get the data from that object into a usable format on the Web client written
with EWB.’
3. To get to that client, the data must traverse the Internet in some intermediate format as a
series of bytes.

EWB knows how to convert data from structures into two common formats: JSON
(JavaScript Object Notation). We will see just how easy it is with the first example below.

unit readerwriter1a;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebEdits, WebBtns;
type

TClient = class ( TPersistent ) private


fClientID : integer;
fFirstName : string;
fSurname : string;
fAddress : string;
fCity : string;
fStateProvince : string; fCountry : string;
fPostalZip : string;

published
property ClientID : integer read fClientID write fClientID;

property FirstName : string read fFirstName write


fFirstName;
property Surname : string read fSurname write fSurname;
property Address : string read fAddress write fAddress;
property City : string read fCity write fCity;
property StateProvince : string read fStateProvince write
fStateProvince;
property Country : string read fCountry write fCountry;
property PostalZip : string read fPostalZip write
fPostalZip;
end;

TForm1 = class(TForm)
MultiLineEdit1: TMultiLineEdit;
Button1: TButton;
procedure Form1Show(Sender: TObject); procedure
Button1Click(Sender: TObject); procedure Form1Create(Sender:
TObject);

private
{ Private declarations }
client : TClient;
Reader : TReader;
Writer : TWriter;

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation
procedure TForm1.Form1Create(Sender: TObject); begin

client := TClient.Create;
Reader := TReader.Create;
Writer := TWriter.Create;

end;

procedure TForm1.Form1Show(Sender: TObject); begin


Writer.Initialize;
client.Save( Writer );
MultiLineEdit1.Lines.Text := Writer.Output;
end;

procedure TForm1.Button1Click(Sender: TObject); begin


// save contents
Reader.Initialize( MultiLineEdit1.Lines.Text );
client.Load( Reader );

// now write them back out again Form1Show( Sender );


end;
end.

The data structure TClient is a bit special in that we have defined published properties
such as FirstName, with read and write properties that save the data into private data
properties. For convention sake, we name the private properties f…

When we start our application, the onCreate method creates an empty TClient variable and
a TReader and TWriter. We will use later

procedure TForm1.Form1Create(Sender: TObject); begin


client := TClient.Create;
Reader := TReader.Create;
Writer := TWriter.Create;
end;

In the OnShow event hander we display the JSON of the TClient structure in a few short
lines:

procedure TForm1.Form1Show(Sender: TObject); begin


Writer.Initialize;
client.Save( Writer );
MultiLineEdit1.Lines.Text := Writer.Output;

Then the user is free to edit the JSON and press Button1 which reloads the TClient from
the MultiLineEdit1 text box.

procedure TForm1.Button1Click(Sender: TObject); begin


// save contents
Reader.Initialize( MultiLineEdit1.Lines.Text );
client.Load( Reader );

// now write them back out again Form1Show( Sender );


end;

By calling Form1Show(), the procedure rereads the JSON notation and updates it on the
display.
The output of this simple program looks like this:
You can see, edit and update the JSON and thus the TClient.
Now add an fBirthday to the private part of TClient, and Birthday to the published part.
Private


fBirthday : DateTime
Published

property Birthday : DateTime read fBirthday write
fBirthday;

and change OnCreate to initialize the birthday field to today:

procedure TForm1.Form1Create(Sender: TObject); begin


client := TClient.Create;
client.Birthday := now;
Reader := TReader.Create;
Writer := TWriter.Create;
End;

When you rerun the program it displays the date as the number of milliseconds since
January 1, 1970 UTC. Eg:
"birthday": 1469207693068
If you prefer, you can change the default output to ISO date/time by changing

procedure TForm1.Form1Create(Sender: TObject); begin


client := TClient.Create;
client.Birthday := now;
Reader := TReader.Create(dtfISO8601);
Writer := TWriter.Create(dtfISO8601);

And the output changes to:

"birthday": "2016-07-22T17:24:37.585Z"
or something similar which is more human readable. But it may still be confusing because
the time is UTC zoned.

To the novice programmer it seems annoyingly redundant to specifiy private and published
properties. But there are several good reasons.

Published properties are used by TReader and TWriter. Having a private property such as
Phone number could exist without being copied to and fro in the JSON.

Another benefit is that you can have handlers which correct or interpret the data. For
example, many systems like to specify gender of someone. Depending on your location and
current views of gender identity, it may allow Male, Female and Unspecified, or maybe
other designations. But people often just enter M or F or U. So what if the system
automatically fixed the data to the long accepted formats.

TClient = class ( TPersistent ) Private

..
fSex : string;
procedure LocalWriteSex( s : string );

Published


property Sex : string read fSex write
LocalWriteSex;
end;

procedure TClient.LocalWriteSex( s : string ); begin


case LowerCase(s) of
'm', 'male' : fSex := 'male';
'f', 'femail' : fSex := 'female';
else fSex := 'unspecified'; end;
end;

And update the OnCreate form to set the Gender.

procedure TForm1.Form1Create(Sender: TObject); begin


client := TClient.Create;
client.Birthday := now;
client.Sex := 'u';
Reader := TReader.Create(dtfISO8601);
Writer := TWriter.Create(dtfISO8601); end;

Sometimes you know you will have arrays of items and will want to often code them as
JSON arrays – eg. party guests from your TClients. The easiest way to accomplish this
uses TCollections. But to do so, you must have TClient inherit from TColllectionItem
(which is a descendant of TPersistent) instead of TPersistent.

If you took the above examples and substituted TCollectionItem for TPersistent, everything
works identically.
Consider the case where we have people and their cousins, who are also people.

We will build a program which dumps a person and their cousins to persistent storage…
actually just to a MultiLineEdit box. And we will have a button which rereads that
MultiLineEdit box and reloads it into our structure. You can tell it works if you change the
spacing or the entries, and they revert back to EWB’s spacing after you hit the button.

unit persistarray1;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebEdits, WebBtns;
type

TPerson = class (TCollectionItem )


private
fname :string;
fcousins : TPeople;

published
property name : string read fname write fname; property
cousins : TPeople read fcousins write fcousins;

end;
TPeople = class( TCollection )
private

fPerson : TPerson;
published
property person : TPerson read fPerson write fperson; end;

TForm1 = class(TForm)
MultiLineEdit1: TMultiLineEdit;
Button1: TButton;
procedure Form1Create(Sender: TObject); procedure
Form1Show(Sender: TObject); procedure Button1Click(Sender:
TObject);

private
{ Private declarations }
me : TPerson;
reader : TReader;
writer : TWriter;

public
{ Public declarations }
end;

var
Form1: TForm1;
implementation
procedure TForm1.Form1Create(Sender: TObject); var
cousin : TPerson;

begin
me := TPerson.Create;
me.name := 'Erick';
me.cousins := TPeople.Create( TPerson );

cousin := TPerson( me.cousins.Add ); cousin.name := 'Paul';

cousin := TPerson( me.cousins.Add ); cousin.name :=


'Marsha';
procedure TForm1.Form1Show(Sender: TObject); begin

writer.Initialize;
me.Save( writer );
MultiLineEdit1.Lines.Text := writer.Output; end;
procedure TForm1.Button1Click(Sender: TObject); begin
me.Free; // totally destroy previous copy to start fresh
me := Nil;

me := TPerson.Create;
reader.Initialize( MultiLineEdit1.Lines.Text ); me.Load(
reader );
Form1Show( Sender );

end;
end.

In our OnCreate routine we initialize me to be Erick and set two of my cousins. We also
create a TReader and TWriter for use later.
procedure TForm1.Form1Create(Sender: TObject); var
cousin : TPerson;

begin
me := TPerson.Create;
me.name := 'Erick';
me.cousins := TPeople.Create( TPerson );

cousin := TPerson( me.cousins.Add ); cousin.name := 'Paul';


cousin := TPerson( me.cousins.Add ); cousin.name :=
'Marsha';
In our OnShow event handler we dump out the data, just like before.
procedure TForm1.Form1Show(Sender: TObject); begin
writer.Initialize;
me.Save( writer );
MultiLineEdit1.Lines.Text := writer.Output; end;
It displays the results:

{
"name": "Erick", "cousins": { } }

As you can see, cousins is empty.


It turns out we need to extend TPeople to know how to write out the cousins property.
We add to TPeople:
protected
procedure SaveProperties( AWriter : TWriter ); override and
the code

procedure TPeople.SaveProperties( AWriter : TWriter ); var


person : TPerson;
i : integer;
begin
// handle things we don't do ourselves
inherited SaveProperties( AWriter );

// now save the people list


AWriter.PropertyName('items'); AWriter.BeginArray( Count > 0
); for i := 0 to Count - 1 do begin

if i > 0 then AWriter.Separator; person := TPerson( items[i]


); person.Save( AWriter );

end;
AWriter.EndArray( Count > 0 ); end;

So we simply write out the property name (which we call items), start an array, cycle
through the array and write each member, then end the array.

In our situation, TPeople has only one field/array we want to persist, the items field. But if
there were other useful fields, we could use the inherited function to persist them. So for
future expansion we included the inherited function at the start.

If we rerun the program it now lists:


{
"name": "Erick",
"cousins": { "items": [ {

"name": "Paul" }, { "name": "Marsha" } ] } }


This is exactly what we want.

The process of reading in the data is a little bit more complicated. When you look at the
above JSON, cousins is a member of TPerson returning a TPeople, and to TPeople we are
adding an array called items. So we need to extend TPerson and TPeople with some more
overrides.

First we add the cousins property to TPerson. We use SameText() to do a caseinsensitive


comparision of the PropertyName. We see they want to load cousins, so we need to create
a cousins : TPeople structure. We skip over the used PropertyName and PropertySeparator,
and load the object which is our cousins structure.

And if the PropertyName is not one of the ones we are specifically looking for, we are a
good citizen and pass it along to the default handler.

function TPerson.LoadProperty( AReader : TReader ):boolean;


var

PropertyName : string; begin


result := False;

PropertyName := AReader.GetPropertyName;

if SameText( PropertyName , 'cousins' ) then begin // we


need to create a home for the cousins first cousins :=
TPeople.Create( TPerson );
result := True; // we are handling it
AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

cousins.LoadObject( AReader ); // ArrayElements( AReader );


end else
result := inherited LoadProperty(AReader ); end;

We need to do a similar section on TPeople to read the items list.

function TPeople.LoadProperty( AReader : TReader ):boolean;


var
PropertyName : string; begin
result := False;

PropertyName := AReader.GetPropertyName;

if SameText( PropertyName , 'items' ) then begin // we need


to create a home for the cousins first result := True; // we
are handling it
AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

LoadArray( AReader );
end else
result := inherited LoadProperty(AReader );

Here the difference is we use LoadArray() because items is an array.


Finally, we need to add the code to load the ArrayElement (TPerson) for each of the items.

function TPeople.LoadArrayElement( AReader : TReader


):boolean;
var

tempperson : TPerson;
propertyname : string;
begin

tempperson := TPerson( Add );


tempperson.LoadObject( AReader);
Result := True;

end;

The complete code is below. I’ve used {$ifdef} / {$endif} to comment out the code to
show the initial version and the complete version so you can see what needed to be added.

unit persistarray1;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebEdits, WebBtns;
type
{$DEFINE DEMO}

TPerson = class (TCollectionItem )


private
fname :string;
fcousins : TPeople;

published
property name : string read fname write fname; property
cousins : TPeople read fcousins write fcousins;

{$IFDEF DEMO}
protected
function LoadProperty( AReader : TReader ):boolean;

override;
{$ENDIF}
TPeople = class( TCollection )
private

fPerson : TPerson;
published
property person : TPerson read fPerson write fperson;
{$IFDEF DEMO}
protected
procedure SaveProperties( AWriter : TWriter ); override;
function LoadProperty( AReader : TReader ):boolean;
override;
function LoadArrayElement( AReader : TReader ):boolean;
override;
{$ENDIF}
end;

TForm1 = class(TForm)
MultiLineEdit1: TMultiLineEdit;
Button1: TButton;
procedure Form1Create(Sender: TObject); procedure
Form1Show(Sender: TObject); procedure Button1Click(Sender:
TObject);

private
{ Private declarations }
me : TPerson;
reader : TReader;
writer : TWriter;

public
{ Public declarations }
end;
var
Form1: TForm1;
implementation

{$IFDEF DEMO}
procedure TPeople.SaveProperties( AWriter : TWriter ); var

person : TPerson;
i : integer;
begin

// handle things we don't do ourselves


inherited SaveProperties( AWriter );
// now save the people list
AWriter.PropertyName('items'); AWriter.BeginArray( Count > 0
); for i := 0 to Count - 1 do begin

if i > 0 then AWriter.Separator; person := TPerson( items[i]


); person.Save( AWriter );

end;
AWriter.EndArray( Count > 0 ); end;

function TPeople.LoadArrayElement( AReader : TReader


):boolean; var
tempperson : TPerson;
propertyname : string;
begin
tempperson := TPerson( Add );
tempperson.LoadObject( AReader);
Result := True;
end;

function TPerson.LoadProperty( AReader : TReader ):boolean;


var

PropertyName : string;
begin
result := False;

PropertyName := AReader.GetPropertyName;

if SameText( PropertyName , 'cousins' ) then begin // we


need to create a home for the cousins first cousins :=
TPeople.Create( TPerson );
result := True; // we are handling it
AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

cousins.LoadObject( AReader ); // ArrayElements( AReader );


end else
result := inherited LoadProperty(AReader );
end;

function TPeople.LoadProperty( AReader : TReader ):boolean;


var

PropertyName : string; begin


result := False;

PropertyName := AReader.GetPropertyName;

if SameText( PropertyName , 'items' ) then begin // we need


to create a home for the cousins first result := True; // we
are handling it
AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

LoadArray( AReader );
end else
result := inherited LoadProperty(AReader ); end;
{$ENDIF}

procedure TForm1.Form1Create(Sender: TObject); var


cousin : TPerson;

begin
me := TPerson.Create;
me.name := 'Erick';
me.cousins := TPeople.Create( TPerson );

cousin := TPerson( me.cousins.Add ); cousin.name := 'Paul';


cousin := TPerson( me.cousins.Add ); cousin.name :=
'Marsha';
reader := TReader.Create; writer := TWriter.Create; end;
procedure TForm1.Form1Show(Sender: TObject); begin
writer.Initialize;
me.Save( writer );
MultiLineEdit1.Lines.Text := writer.Output; end;
procedure TForm1.Button1Click(Sender: TObject); begin

me.Free; // totally destroy previous copy to start fresh


me := Nil;

me := TPerson.Create;
reader.Initialize( MultiLineEdit1.Lines.Text ); me.Load(
reader );
Form1Show( Sender );

end;
end.

If you are planning to use persistence to deal with MySQL, you will want to exchange data
in MySQL’s time format, and to most people’s delight, the local timezone is used rather
than UTC.

I’ve used {$IFDEF DEMO} to mark the small bit of code you need to add to a class or to
its ancestor class.

Much of the code for for this example is stolen from the previous examples. Again, we
display the serialized version, and when you press the button, it deserializes it back to the
object.

unit persistime1;
interface
uses WebCore, WebUI, WebForms, WebCtrls, WebEdits, WebBtns;
type {$DEFINE DEMO}

TPerson = class (TCollectionItem )


private
fname :string;
fbirthday : DateTime;

published
property name : string read fname write fname; property
birthday : DateTime

read fbirthday write fbirthday;


{$IFDEF DEMO}
protected
procedure SaveProperty(AWriter: TWriter; const AName:
String); override;
function LoadProperty( AReader : TReader ):boolean;
override;
{$ENDIF}
end;

TForm1 = class(TForm)
MultiLineEdit1: TMultiLineEdit;
Button1: TButton;
procedure Form1Create(Sender: TObject); procedure
Form1Show(Sender: TObject); procedure Button1Click(Sender:
TObject);

private
{ Private declarations }
me : TPerson;
reader : TReader;
writer : TWriter;

public
{ Public declarations }
end;

var
Form1: TForm1;
implementation

{$IFDEF DEMO} procedure TPerson.SaveProperty(AWriter:


TWriter; const AName: String);
var

TempType: Integer;
TempInstance: TObject;
dt : DateTime;
s : string;
TempShortTimeFormat : string;
TempShortDateFormat : string;

begin
TempType:=PropertyType(AName);
if (TempType <> TYPE_INFO_UNKNOWN) then

begin
if TempType = TYPE_INFO_DATETIME then begin
AWriter.PropertyName( AName );
dt := DateTime(GetProperty(AName));
TempShortTimeFormat :=
FormatSettings.ShortTimeFormat; // save it
TempShortDateFormat :=
FormatSettings.ShortDateFormat; // save it
FormatSettings.ShortTimeFormat := 'H:mm';
FormatSettings.ShortDateFormat := 'yyyy/M/d';
s := DateTimeToStr(dt);
FormatSettings.ShortTimeFormat :=
TempShortTimeFormat;
FormatSettings.ShortDateFormat :=
TempShortDateFormat;
AWriter.StringValue(s);
end
else inherited SaveProperty( AWriter, AName ); end;
end;

function TPerson.LoadProperty( AReader : TReader ):boolean;


var
PropertyName : string;
PropType : Integer;
s : string;
dt : DateTime;
TempShortTimeFormat : string;
TempShortDateFormat : string;
begin
result := False;
PropertyName := AReader.GetPropertyName;

PropType:= PropertyType(PropertyName); if PropType =


TYPE_INFO_DATETIME then begin AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

s := Areader.ReadString;

TempShortTimeFormat := FormatSettings.ShortTimeFormat; //
save it
TempShortDateFormat := FormatSettings.ShortDateFormat; //
save it
FormatSettings.ShortTimeFormat := 'H:mm';
FormatSettings.ShortDateFormat := 'yyyy/M/d';
dt := StrToDateTime(s);
FormatSettings.ShortTimeFormat := TempShortTimeFormat;
FormatSettings.ShortDateFormat := TempShortDateFormat;
SetProperty(PropertyName,dt );
end else
result := inherited LoadProperty(AReader ); end;
{$ENDIF}
procedure TForm1.Form1Create(Sender: TObject); var
cousin : TPerson;

begin
me := TPerson.Create;
me.name := 'Erick';
me.birthday := now;

reader := TReader.Create; writer := TWriter.Create; end;


procedure TForm1.Form1Show(Sender: TObject); begin
writer.Initialize;
me.Save( writer );
MultiLineEdit1.Lines.Text := writer.Output; end;

procedure TForm1.Button1Click(Sender: TObject);


begin
me.Free; // totally destroy previous copy to start fresh
me := Nil;

me := TPerson.Create;
reader.Initialize( MultiLineEdit1.Lines.Text ); me.Load(
reader );
Form1Show( Sender );

end;
end.

When we save a class, the class calls SaveProperty for each property passing a name, so
we use an overload method on SaveProperty. We call PropertyType() to get the property
type of the property, and if it is a DateTime we handle it ourselves. Otherwise we just call
the inherited function to handle all other types. We need to change the default date and time
formatting properties, so we temporarily save their formats and return them again when we
are done.

procedure TPerson.SaveProperty(AWriter: TWriter; const


AName: String);
var

TempType: Integer;
TempInstance: TObject;
dt : DateTime;
s : string;
TempShortTimeFormat : string;
TempShortDateFormat : string;

begin
TempType:=PropertyType(AName);
if (TempType <> TYPE_INFO_UNKNOWN) then

begin
if TempType = TYPE_INFO_DATETIME then begin
AWriter.PropertyName( AName );
dt := DateTime(GetProperty(AName));
TempShortTimeFormat :=
FormatSettings.ShortTimeFormat; // save it
TempShortDateFormat :=
FormatSettings.ShortDateFormat; // save it
FormatSettings.ShortTimeFormat := 'H:mm';
FormatSettings.ShortDateFormat := 'yyyy/M/d'; s :=
DateTimeToStr(dt); FormatSettings.ShortTimeFormat :=
TempShortTimeFormat;
FormatSettings.ShortDateFormat :=
TempShortDateFormat;
AWriter.StringValue(s);
end
else inherited SaveProperty( AWriter, AName ); end;
end;

The code to deserialize is also straightforward. We just check if the type is DateTime,
swap around the format strings, read in the value, and restore the format string.

function TPerson.LoadProperty( AReader : TReader ):boolean;


var
PropertyName : string;
PropType : Integer;
s : string;
dt : DateTime;
TempShortTimeFormat : string;
TempShortDateFormat : string;
begin
result := False;

PropertyName := AReader.GetPropertyName;
PropType:= PropertyType(PropertyName); if PropType =
TYPE_INFO_DATETIME then begin AReader.SkipPropertyName;
AReader.SkipPropertySeparator;

s := Areader.ReadString;
TempShortTimeFormat := FormatSettings.ShortTimeFormat; //
save it

TempShortDateFormat := FormatSettings.ShortDateFormat; //
save it
FormatSettings.ShortTimeFormat := 'H:mm';
FormatSettings.ShortDateFormat := 'yyyy/M/d'; dt :=
StrToDateTime(s);
FormatSettings.ShortTimeFormat := TempShortTimeFormat;
FormatSettings.ShortDateFormat := TempShortDateFormat;

SetProperty(PropertyName,dt );
end else
result := inherited LoadProperty(AReader ); end;

Creating database web applications is where EWB truly shines. This makes sense because
Elevate Software was traditionally a database technology company.

Since version 2.05, EWB supports multiple TDatabase components, how many, it’s
actually unlimited. Database is created automatically at application startup time for both
visual and non-visual projects. It keeps track of all datasets used by the application and
contains properties, methods and events for starting and commiting data transactions as
well as rolling them back.

TDataSet manages data between the application and the database for both visual and non-
visual projects. You typically have one TDataSet for each SQL table in use.

TDataSets can be dropped on the visual form at design time or created at runtime.
The important Columns property contains the column definitions for the dataset.

Database.LoadColumns loads the columns for the dataset directly from the database,
transparently marshalling the values through a JSON (JavaScript Object NotatioN) string
across a network connection for the user. TDataSet.LoadColumns similarly loads the
columns using a JSON format, but it is up to your application to provide that JSON string.
This provides a handy way of locally inserting values without having the baggage of a
whole database setup.
Rows of data are loaded from the server using Database.LoadRows, or
TDataset.LoadRows following the same server versus local
Once the TDataSet is loaded, you can navigate using the First, Prior, Next and Last
methods.
The TDataSet can also call Insert, Update and Delete to deal with various row operations.

Insert takes a parameter: Append:Boolean. If true, the TDataSet is extended at the end,
otherwise the new row is inserted at the current position. Remember to Save after your
insert.

Update prepares the current TDataSet row for an update. Call Save when done.
Delete deletes the current TDataSet row.
TDataset.Find is used to find a specific or nearest match. It can specify a case insensitivity
and a Locale insensitivity.
The nearest match requires an active sort on the database (Sorted = True).
If there is an active sort on the dataset, CaseInsensitive must match the SortCaseInsensitive
property.
TDataSet.Sort invokes the sorting if and only if Sorted is True and sort columns have been
specified for the dataset.
SortCaseInsensitive and SortLocaleInsensitive control how the sort is performed.

You only need call Sort after the sort directions are initially assigned for the columns.
Subsequent to the sort, any row operations will automatically maintain the correct order of
the active sort.

Our first database app will be a static local database on the Web client itself and is simple
enough to show the principles without getting into the challenges of network
communications and server configuration.

Create a new project. Drop a Database | TDataSet onto the form, it’s a nonvisual
component, so it can be placed anywhere. Name it People. It will be our list of people.

Double-click on People’s Columns property in the Object Inspector, this opens the
Columns editor. In the Columns Editor, press the + button to add a new column, it will be
called PeopleDataColumn1 by default. In the Object Inspector, change the column name to
PeopleID and the DataType to dtString. Repeat to add a column named PeopleName with
DataType dtString and Length 50.
Place two edit boxes on the form, name one edID and the edName. Set their DataSet
properties to People (to match our DataSet), and their DataColumns to PeopleID and
PeopleName respectively – do this by clicking the down arrow on the TEdit’s DataSet and
selecting people, and down arrow on DataColumn and selecting the appropriate column.
Add a Toolbars | TDataSetToolBar to the form and set its DataSet to People. Now click on
the main form and add the following OnShow handler and add
procedure TForm1.Form1OnShow(Sender: TObject); begin
Database.AutoTransactions := False;
People.Open;
People.LoadRows(
'{ "rows": [
{ "PeopleID": "1",

"PeopleName": "Hon" },
{ "PeopleID": "2",
"PeopleName": "Daniel" }]}', False); end;

Press F9 and you will have a navigable database application, albeit a minimalist one. You
can insert, delete, modify and search the records. Since there is no backend, all the data
changes are lost.

You can add additional records programmatically with the following code in a TButton
OnClick handler:

with People do begin


Open;
Insert;
Columns['PeopleID'].AsString := '1';
Columns['PeopleName'].AsString:='Erick';
Save;
Insert;
Columns['PeopleID'].AsString := '2';
Columns['PeopleName'].AsString:='Rosie';
Save;
end;

Let’s add a Master-Detail relationship. Add a TGrid, set its DataSet property to people,
and press F9. The TGrid automatically uses the correct column names and displays when
you run it.

Clicking on a name in the master table brings it into the current record field for the TEdits.

Change a field in its TEdit, hit update, and the master table reflects the change. The
TDataSet replaces large amounts of code you would think you might have to write.

If you typed in the previous example, you may have run into problems entering the JSON
code.
JSON is a popular standard for data exchange, not unlike XML. But it differs from XML in
that it is quite compact and fast for machines to parse. It is also relatively human-readable.
But entering it manually is a pain, you should never have to do that.

EWB supports database transactions, where you make a bunch of changes on the client
dataset and then can either commit the to the server or rollback the changes.

First set:
Database.AutoTransactions := False;
The code is basically:
Database.StartTransaction;
… do some operations …. if happy then

Database.Commit Else
Database.RollBack;

You can test if you have outstanding changes to commit/rollback by testing


Database.NumPendingRequests > 0. If the result of NumPendingRequests is non-zero when
you think it should be zero, you may have to call Database.RetryPendingRequests.

Database.InTransaction is true if a transaction of ours is currently active.


Database.TranactionLevel returns a non-negative integer to determine the depth of the
transaction.

You can test the TDataSet.State for


- dsInsert : inserting
- dsUpdate: updating
- dsBrowse: browsing
- otherwise, not active
-

The transactions are not Database Locks, they do not prevent others from making changes.
They are only pertinent to the client dataset – ie. the dataset in memory.

There is an excellent example in the EWB samples, see the transactions folder.
Automatic transactions are a feature you can use if you set
Database.AutoTransactions := True;

When you use AutoTransactions, the simple act of inserting or editing a row in the database
locks the row in the client dataset until you commit with a Save operation. No transaction
code needs to be written – assuming your database supports EWB’s locking. Many
databases do not, so then you should turn off Automatic Transactions.
That remains true even if you have a more complicated setup, like a master/detail form.
When you start inserting or editing the master row, EWB starts a transaction that does not
commit until you save the master row. And during that time, any changes in the detail view
will be executed in a nested transaction.

The single call to Save on the master row will commit all nested changes at once.

I recommend re-reading/refreshing the data rows periodically as other users may have
changed them, and also EWB can get out of sync after making changes it doesn’t
understand. For example, when you create a new SQL record with SQL CREATE/Database
Insert, the key field (eg. Index number) does not get assigned until the database enters the
transaction, but it never updates the client. So you shoud refresh the client after every
operation, and otherwise after long periods of inactivity.
For this chapter we will only consider PHP databases using SQL. The reader is assumed to
know some PHP and SQL. We will construct a skeletal PHP program less than 100 lines
long that simulates a database server and logs the EWB-intended operations from the
JSON based RESTful database operations.

This database server code is in lieu of an official PHP database from Elevate which was
planned for release 2.05. My implementation is basic in many ways. The functionality it
lacks: is a long list

• localization
• datetime support
• sorting
• full UTF8 character support (non-alphanumeric characters may change)
• BLOBs
• SQL string length limiting
• Type validation
• Error checking
• Access controls
• Etc.

REST is a truly open standard of how to represent large collections of data objects over a
network connection. Using JSON (or XML on some other systems) over HTTP/HTTPS,
REST allows one to specify the CRUD (create, read, update, delete) operations of a
database. But it is truly stateless, you can reboot your server or replace it with a new
server, and the REST operations will still make sense.

EWB uses two REST HTTP verbs: GET which gets one or more entries (akin to SQL
SELECT), and PUT which batch uploads a transaction of one or more operations (such as
SQL’s UPDATE, CREATE, DELETE).
The rest of this section is optional, you can read it to learn, or you can skip to the
example.

All operations specify a Database name and a DataSet name to the server in the
$_SERVER variable and a request=operationname. Also, if you specify a Userid and
Password, they are transferred in the X-EWBUser and X-EWBPassword parameters of
$_SERVER.

The GET/SELECT operation specifies one or more optional parameters, like


dept=Engineering in addition to the aforementioned parameters.
So a query to SELECT the list of people in Engineering would look like the following GET
HTTPS://our.com/prog?method=rows&database=company&dataset=staff&d
ept=Engineering
And our server will return a JSON array containing the data.
Other INSERT, UPDATE, DELETE operations are POSTed as HTTPS://our.com/prog?
method=commit&database=companyf

Then it will contain a JSON array of the list of operations to be completed in the
transaction with a dataset. Each operation will have an opcode: 1 for INSERT, 2 for
UPDATE, and 3 for DELETE, as well as a list of before and after parameters specifying
the object in question before the operation and the values after the operation.

I think this represents the bare minimum client you would want to consider for an EWB
database application.
The form has an empty TGrid named Grid1, a TDataSet named DataSet1 and buttons names
btInsert, btUpdate and btDelete.
unit sqlclient1;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebData,
WebGrids, WebBtns;
type

TForm1 = class(TForm)
DataSet1: TDataSet;
Grid1: TGrid;
btInsert: TButton;
btUpdate: TButton;
btDelete: TButton;
procedure Form1Show(Sender: TObject);
procedure btInsertClick(Sender: TObject);
procedure btUpdateClick(Sender: TObject);
procedure btDeleteClick(Sender: TObject);

private
{ Private declarations }
procedure Select_Query;
procedure CommitError(Sender: TObject; const ErrorMsg:

String);
procedure AfterCommit(Sender: TObject);

public
{ Public declarations }
end;

var
Form1: TForm1;
implementation

procedure TForm1.Form1Show(Sender: TObject); var


col : TDataColumn;
colgrid : TGridColumn;
begin
Database.OnCommitError := CommitError;
Database.AfterCommit := AfterCommit;

Database.AutoTransactions := False; Database.DatabaseName :=


'staff';
// important URL to our database PHP application
Database.BaseURL := 'simple.php';

// you will normally fill these in from user input


Database.UserName := 'joe';
Database.Password := 'hill'; // could replace with JWT token

// define the dataset, you could also do this visually col


:= Dataset1.Columns.Add;
col.Name := 'StaffID';
col.DataType := dtInteger;

col := Dataset1.Columns.Add; col.Name := 'Name';


col.DataType := dtString;

// define the grid, could also be done visually

Grid1.Dataset := Dataset1;
Grid1.NewColumn;
colgrid := Grid1.Columns[0];
colgrid.Header.Caption := 'StaffID'; colgrid.DataColumn :=
'StaffID';
Grid1.NewColumn;
colgrid := Grid1.Columns[1]; colgrid.Header.Caption :=
'Name'; colgrid.DataColumn := 'Name';

// execute our first query Select_Query();


end;

procedure TForm1.CommitError(Sender: TObject; const


ErrorMsg: String);
begin

HideProgress;
ShowMessage( ErrorMsg , 'SQL Error');
if window.confirm('Do you want to retry the commit ?') then

begin
ShowProgress('Retrying commit...');
Database.RetryPendingRequests;

end;
end;
procedure TForm1.AfterCommit(Sender: TObject); begin
HideProgress;
end;

procedure TForm1.Select_Query;
begin
Dataset1.Params.Add('dept=Engineering');
Database.LoadRows( DataSet1 );
end;

procedure TForm1.btInsertClick(Sender: TObject); begin


Dataset1.Insert;
Dataset1.Columns['Name'].AsString := 'Steve Smith';
Dataset1.Save; end;

procedure TForm1.btUpdateClick(Sender: TObject); begin


Dataset1.First;
Dataset1.Update;
Dataset1.Columns['Name'].AsString :=
Dataset1.Columns['Name'].AsString + ' Esquire';
Dataset1.Save;
end;
procedure TForm1.btDeleteClick(Sender: TObject); begin
Dataset1.Last;
Dataset1.Delete;
end;

end.

The most important setup details appear in the page’s onShow handler. The
AutoTransactions is new for 2.05 and eliminates the need to start and end a transaction to
do simple operations. BaseURL is the URL to our database server PHP application.

Database.AutoTransactions := True; Database.DatabaseName :=


'staff';
// important URL to our database PHP application
Database.BaseURL := 'simple.php';

// you will normally fill these in from user input


Database.UserName := 'joe';
Database.Password := 'hill'; // could replace with JWT token

We also set the user name and the user password. These will be passed to our server. In a
modern OAuth2 environment, you would replace the password with a JSON Web Token
(JWT), but that’s another story.

Next in the function we add zero or more criteria to narrow the search/SELECT, by adding
“dept=Engineering” we are saying only Engineering users will be considered. Then we
load the DataSet.

Dataset1.Params.Add('dept=Engineering');
Database.LoadRows(DataSet1);
The rest of the operations are straightforward.

This code is very incomplete, it is meant to show you how to access the parameters to
produce SQL commands. To keep it simple, there little error checking, no data validation
or access controls in this sample, so it would be a nightmare to place on the big bad
Internet in its present form. This is an educational tool only.

In about 90 lines of boilerplating, and a few lines PHP to SQL conversion code you will
replace (functions starting with sql_...), this code can function with most EWB database
examples, including the transaction example.

It does not handle BLOBs (binary large objects) for things like pictures, but it can return a
URL and you can use that to reference a picture for download. simple.php <?php
function sql_select($user, $pass, $database, $dataset,
$inputargs)
{

$args = 'WHERE ';


$i = 0;
foreach ( $inputargs as $a=>$b) {

if ( $i++ > 0 ) $args .= " && ";


$args .= " ( $a = '$b' )";
}
savelog( "SQL: SELECT * FROM $dataset $args");
return( '{ "rows" : [{"StaffID":1 , "Name":"Erick Jones"},
{"StaffID":2 , "Name":"Rosie Jones"}]}'); }

function sql_insert($user, $pass, $database, $dataset,


$outputargs)
{

$i = 0;
foreach ( $outputargs as $a=>$b) {
if ( $i++ > 0) {

$sqla .= ',';
$sqlb .= ',';
}
$sqla .= $a;
$sqlb .= "'$b'";
}
savelog( "SQL: INSERT INTO $dataset ( $sqla ) VALUES ( $sqlb
)");
return '';
}

function sql_update($user, $pass, $database, $dataset,


$inputargs, $outputargs)
{

$inargs = 'WHERE ';


$i = 0;
foreach ( $inputargs as $a=>$b) {

if ( $i++ > 0 ) $inargs .= " AND "; $inargs .= " ( $a = '$b'


)";
}
$outargs = 'SET ';
$i = 0;
foreach ( $outputargs as $a=>$b) {

if ( $i++ > 0 ) $outargs .= " && "; $outargs .= " $a = '$b'


";
}
savelog("SQL: UPDATE $data $outargs $inargs"); return '';
}

function sql_delete($user, $pass, $database, $dataset,


$inputargs)
{

$inargs = 'WHERE ';


$i = 0;
foreach ( $inputargs as $a=>$b) {

if ( $i++ > 0 ) $inargs .= " AND "; $inargs .= " ( $a = '$b'


)";
}
savelog("SQL: DELETE FROM $dataset $inargs"); return '';
}

function sql_userpassword( $user, $pass )

{
return 1;
}
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=
=-=-=-=-=-=
define("EWB_OPERATION_UNKNOWN", 0);
define("EWB_OPERATION_INSERT", 1);
define("EWB_OPERATION_UPDATE", 2);
define("EWB_OPERATION_DELETE", 3);

header('Cache-Control: no-cache');
//header('Content-Type:vapplication/json; charset=utf-8');
function safetext( $src )

{
return( htmlentities( $src, ENT_QUOTES ));
}
function savelog( $msg )
if ( $f = fopen("log","a+")) { fwrite( $f, "$msg\n" );
fclose( $f );
}
}
function getpostdata()
{
$postdata = file_get_contents("php://input"); return
($postdata);
}

function rest_error($msg) {
header("EWB_HTTP_ERROR", "HTTP/1.0 500 $msg"); exit(0);
}

if( ! isset($_REQUEST['method'])) exit;


$method = $_REQUEST['method'];

$user = (isset($_SERVER['X-EWBUser']))? safetext(


$_SERVER['X-EWBUser']): '';
$pass = (isset($_SERVER['X-EWBPassword']))?
safetext($_SERVER['X-EWBPassword']) : '';

if ( ! sql_userpassword( $user, $pass )) rest_error( “bad


userid or password”);
switch($method)

{
case 'rows':
$options = array();
if( ! isset($_REQUEST['dataset']))

rest_error('missing dataset');
$dataset = safetext( $_REQUEST['dataset'] ); if( !
isset($_REQUEST['database']))

rest_error('missing database');
$database = safetext( $_REQUEST['database'] );
foreach($_REQUEST as $directive=>$value) if(($directive !=
'database') && ($directive != 'dataset') && ($directive !=
'method'))
$options[safetext($directive)] = safetext($value); }
$output = sql_select($user, $pass, $database, $dataset,
$options);
break;
case 'commit':

$data = getpostdata();
$json = json_decode($data, TRUE);
$json2 = $json['operations'];

foreach($json2 as $operation) {
if( ! isset($operation['operation'])) rest_error('missing
operation');
$op = $operation['operation'];
if( ! isset($_REQUEST['database']))
rest_error('missing database');
$database = safetext( $_REQUEST['database'] ); if( !
isset($operation['dataset']))
rest_error('missing dataset');
$dataset = safetext( $operation['dataset'] );

$inputargs = array();
foreach ( $operation['beforerow'] as $a=>$b)
$inputargs[safetext($a)] = safetext($b);

$outputargs = array();
foreach ($operation['afterrow'] as $a => $b )
$outputargs[safetext($a)] = safetext($b);

switch($op) {
case EWB_OPERATION_INSERT:

$output = sql_insert($user, $pass, $database, $dataset,


$outputargs);
break;
case EWB_OPERATION_UPDATE:
$output = sql_update($user, $pass, $database, $dataset,
$inputargs, $outputargs);
break;
case EWB_OPERATION_DELETE:
$output = sql_delete($user, $pass, $database, $dataset,
$inputargs);
break;
default: rest_error("invalid db op $op"); }
}
break;
default: rest_error("Invalid method $method"); }
print "$output\n";

?>

You can see we handle the two types of requests described ealier, rows for SELECT and
commit for all INSERT, DELETE and UPDATE commands.
This simple program produces an output log file called log in the local subdirectory
(assuming it has write permissions).

For this example output, I ran the web page which called rows to get a list of Engineering
people, and then I called UPDATE and DELETE by starting a commit, editing, deleting,
and closing commit.

SQL: SELECT * FROM DataSet1 WHERE ( dept = 'Engineering' )


SQL: INSERT INTO DataSet1 ( StaffID,Name ) VALUES (
'','Steve Smith' )
SQL: UPDATE SET Name = 'Erick Jones Esquire' WHERE ( StaffID
= '1' ) AND ( Name = 'Erick Jones' )
SQL: DELETE FROM DataSet1 WHERE ( StaffID = '' ) AND ( Name
= 'Steve Smith' )

You may wonder why we pass the userid and password (or its cousin the JWT token) to the
SQL function. As you build more complex systems, undoubtedly you will start using other
REST services to answer your queries, and you will need to either identify the user or pass
the OAuth2 JWT token. Until then, it doesn’t hurt to have an unused parameter present.
I would recommend that you reopen the dataset after closing any commit if you use
commits, or after you make any change if you use autocomits, because the master table can
get out of sync when a person makes nonsensical edits. For example, it allows you to set,
change or clear the person id number, which no database ever should allow, and as we saw
in the output above, unassigned values like StaffID of INSERTed records may be missing,
making the subsequent DELETE fail.

I built the JSON by hand in the request= rows response. Sometimes the PHP internal JSON
building library consumes too much memory and the process terminates, so building JSON
by hand avoids that problem.

For a production database you would replace the sql_... functions with your own. They
would clean up the arguments, test their sanity, and then test user permissions before
performing any database operation.

What is surprising to many developers from other technologies, when you write these
functions you are enabling them to be used by whomever and however that person may
choose. There is no guarantee the database request came from any specific line of your
code, it could be another developer, or it could be a hacker. Code wisely my friend.

SQL table JOINs allow you to SELECT and UPDATE on multiple tables at once.
Suppose you were showing a table of userids joined with addresses. You could
SELECT * FROM userids NATURAL JOIN addresses WHERE dept = ‘Engineering’
Likewise, you can update fields on the JOIN

UPDATE userids NATURAL JOIN addresses SET street=’king’ WHERE userid = ‘tomk’
You only need to differentiate tables during INSERT and DELETE operations. Normally
you will add the three A’s, Access control, Authorization and Auditing.

Only the simplest databases do not care for who is connected – say perhaps a phone book
app. But if one can enter or change data, usually some form of access control and
authorization is required to keep the bad people on the Internet from destroying your data.

Even read-only data is precious. If your customer list were exposed, your competitors
could steal your customers.
As mentioned previously, you will sometimes want to download a BLOB, such as a PDF
document, or a jpeg image.

While this code does not handle BLOBs, it can reference a URL. That might seem
awkward, wouldn’t the URL have to check the user’s permission to view the file? Not
really, you can specify a difficult-to-guess and impossible-toenumerate filename and that
will secure your files.

YouTube does exactly that with its videos. It generates a random URL with a packed
base64 encoding for each filename at store time. If another file exists with that name, it
tries other names until it finds a free one. But the namespace is so enormous, multiple hits
are rare.

Also, the odds of a person requesting the any correct filename are miniscule and can be
made infitessimal if we also check for excessively many failed attempts from a given IP
address and impose an exponential backoffs after n failures.
The code to accomplish this is relatively straightforward, but be sure you are using a good
random number generator, or else your filenames will be predictable.
ObjectPascal over JavaScript can only do so much on the client. Eventually you will want
to communicate with a server for:

- Database transactions
- Messaging/Email
- Any number of things not doable by the client directly
- The EWB environment includes built-in functionality to marshal data into a standard
format (JSON) and use protocols (HTTP/HTTPS) to connect to servers and ask them to do
things.

HTTP/HTTPS have verbs such as:


- GET
- PUT
- POST
- DELETE
- HEAD

These commands are enough to Create/Read/Update/Delete (CRUD) objects on the web in


a RESTful manner.

They can also perform RPC (remote procedure calls) where the Delphi code in the client
ends up calling a PHP function in the server, passing parameters to and fro.

The RPCs on the server can talk to other services, such as Google’s or Microsoft’s
network services, and perform all manner of tasks such as geolocation, scheduling,
messaging, etc.

Some of our examples follow the Elevate strategy directly, others are mashups of our own.
We will be sure to explain each.
Remember CORS (Cross Origin Resource Sharing) imposes limitations on your web page:
it means your web page only communicates with the server that is hosting the page. If you
intend other servers to be used, you must enable Cross-Origin Resource Sharing on both
the development site, and the production site. This is not specifically a EWB topic, it
applies to all web client technologies.

If data is going across a network, you really want it encrypted. The following functions can
help.
CheckHTTPS determines if either the connection is HTTPS, or if the connection is to
localhost (ie. the local machine) which means unencrypted is okay. function
CheckHTTPS: boolean;
var

hostname : string;
protocol : string;
begin

protocol := LowerCase(window.location.protocol);
hostname := LowerCase(window.location.hostname);
result := ( hostname = 'localhost')or( protocol = 'https:');

end;

MakeHTTPS makes the current page HTTPS unless it is on the localhost, in which case it
can stay unenecrypted.

procedure MakeHTTPS;
var
s : string;
hostname, port, search : string;
pathname , name : string;
begin
if not CheckHTTPS then begin
hostname := window.location.hostname; port :=
window.location.port; search := window.location.search;
pathname := window.location.pathname;

s := 'https:' + hostname;
if ( port <> '' ) then s := s + ':'+ port; if (pathname <>
'') then s := s + pathname; if ( search <> '' ) then s := s
+ search;

window.location.replace( s ); end;
end;

This first example does not follow Elevate’s standard exchange of data, but it is easy to
read and will set the stage for future examples.

We will define a protocol where our Object Pascal client send data parameters to a PHP
application’s function, and that function returns data variables to the client. This is an RPC
or Remote Procedure Call.

For our first simple example, we will get a message-of-the-day for a given user, so we
need to pass the userid, a password to verify it is truly our trusted user, and a function name
for the request.

Eg.

userid = ‘erick’
password = ‘happy’
method = ‘advice’ : remote function call
And we will get returned
result = 0 : meaning failure result = 1 : meaning success

and if result =1, we will be returned some advice in the variable wisdom. Eg.
wisdom = ‘the secret to life is EWB’

For our example, use a TEdit to hold a userid, a TPasswordEdit to enter a password, a
TButton to invoke the transaction, and a TLabel to hold the result. Here are the two main
EWB functions: We call GetWisdom and it has a callback called GetWisdomResults that
updates the TLabel.

procedure TForm1.Button1Click(Sender: TObject); begin


GetWisdom( Edit1.Text, PasswordEdit1.Text);
Label1.Caption := ‘…busy’;
end;

procedure
TForm1.GetWisdomResults(results:Boolean;wisdom:string);
begin

if results then

Label1.Caption := wisdom
else
Label1.Caption := 'results not found';
end;

The support functions in EWB are:


procedure TForm1.GetWisdom( userid, passwordtext : string );
begin
if not assigned( WebRequest ) then
WebRequest := TServerRequest.Create( self );

with WebRequest do
begin
OnComplete := GetWisdomRequestComplete;
RequestContent.Clear;
RequestHeaders.Clear;
URL:='loginmodule.php';
Method:=rmPost;
RequestHeaders.Values['Content-Type']:='text/plain';
RequestContent.Values['userid']:= userid;
RequestContent.Values['password']:= passwordtext;
RequestContent.Values['method'] := 'GetWisdom';
Execute;
end;

end;

This code allocates a TServerRequest if we don’t already have a previous one, sets the
request handler and marshals in the data and the method name.
When the server replies, the following code is invoked:

procedure TForm1.GetWisdomRequestComplete(Request:
TServerRequest);
var

rpcresults : TStringList;
begin
if (Request.StatusCode <> HTTP_OK) then begin

ShowMessage('Error: '+Request.ResponseContent.Text);
GetWisdomResults( False, '');
end
else
begin
rpcResults := TStringList.Create;
rpcResults.Text := Request.ResponseContent.Text;

if rpcResults.Values['result'] = '1' then

GetWisdomResults( True, rpcResults.Values['wisdom']) else


GetWisdomResults( False, '');
rpcResults.Free;
end;
end;

Now all that’s needed is the PHP server.


$data = parse_data();
if( ! isset($data['method']))
print "result=0\nerror=No method specified\n";

else
{
$method = $data['method']; switch($method)
{
case 'GetWisdom': GetWisdom($data);
default: print "result=0\nerror=method $method not
found\n";
exit();
}

This code parses the POST data, checks for a method, and if we understand the method,
invokes it with the data.
Here is the parse_data() function:

function parse_data() {
global $HTTP_RAW_POST_DATA;

$z = array();
$s = $HTTP_RAW_POST_DATA; $xx = explode("\n", $s);
foreach($xx as $a=>$b) {

$yy = explode('=', trim($b)); if(count($yy) > 1)

$z[$yy[0]] = $yy[1]; }
return ($z);

The RPC function we call is:

function GetWisdom($data) {
if(CheckPassword($data)) {
$res = 'Secret to life is EWB'; print "result=1\n";
print "secret=$res\n";
}
else
{
print "result=0\n"; }

Nice and simple. Note, it calls CheckPassword which would normally be an LDAP
lookup, or an OAuth2 call, or a database lookup, or a RADIUS call. We’ll just hard code it
for now:

function CheckUserid($data) {
if(isset($data['userid']) && isset($data['password'])) {
$userid = $data['userid'];
$password = $data['password'];
if(($userid == 'erick') && ($password == 'happy')) return
(1);
}
return (0);
}

Most of this code was boiler-plating, except the call to GetWisdom().

I’m not recommending you follow this path, but it shows the steps involved if you choose
to roll your own protocol or support an existing system. And once you have one function,
like our GetWisdom() in PHP, adding more is trivial.

Here is our full example EWB source:


unit simpleweb;
interface
uses WebHttp, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebEdits, WebLabels, WebBrwsr;
type

TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
PasswordEdit1: TPasswordEdit;
Label1: TLabel;
Browser1: TBrowser;
procedure Button1Click(Sender: TObject);

private
{ Private declarations }
WebRequest : TServerRequest;
procedure GetWisdom( userid, passwordtext : string );
procedure GetWisdomResults( results : Boolean ; wisdom :
string );
procedure GetWisdomRequestComplete(Request:
TServerRequest);

public
{ Public declarations }
end;

var
Form1: TForm1;

implementation
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-==-=-=-=-=-=-=-=-=-=
procedure TForm1.GetWisdom( userid, passwordtext : string );
begin
if not assigned( WebRequest ) then
WebRequest := TServerRequest.Create( self );

with WebRequest do
begin
OnComplete := GetWisdomRequestComplete;
RequestContent.Clear;
RequestHeaders.Clear;
URL:='loginmodule.php';
Method:=rmPost;
RequestHeaders.Values['Content-Type']:='text/plain';
RequestContent.Values['userid']:= userid;
RequestContent.Values['password']:= passwordtext;
RequestContent.Values['method'] := 'GetWisdom';
Execute;
end;

end;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-==-=-=-=-=-=-=-=-=-=

procedure TForm1.GetWisdomRequestComplete(Request:
TServerRequest);
var
rpcresults : TStringList;

begin
if (Request.StatusCode <> HTTP_OK) then begin
ShowMessage('Error: '+Request.ResponseContent.Text);
GetWisdomResults( False, '');

end
else
begin
Browser1.DocumentText := Request.ResponseContent.Text;
rpcResults := TStringList.Create;
rpcResults.Text := Request.ResponseContent.Text;
if rpcResults.Values['result'] = '1' then

GetWisdomResults( True, rpcResults.Values['wisdom']) else


GetWisdomResults( False, '');
rpcResults.Free;
end;
end;

procedure TForm1.Button1Click(Sender: TObject); begin


GetWisdom( Edit1.Text, PasswordEdit1.Text); end;

procedure TForm1.GetWisdomResults( results : Boolean ;


wisdom : string );
begin

if results then

Label1.Caption := wisdom
else
Label1.Caption := 'results not found'; end;
end.

And here is the complete PHP


<?php
function parse_data() { global $HTTP_RAW_POST_DATA;

$z = array();
$s = $HTTP_RAW_POST_DATA; $xx = explode("\n", $s);
foreach($xx as $a=>$b) {

$yy = explode('=', trim($b)); if(count($yy) > 1)

$z[$yy[0]] = $yy[1]; }
return ($z);

function CheckUserid($data) {
if(isset($data['userid']) && isset($data['password'])) {
$userid = $data['userid'];
$password = $data['password'];
if(($userid == 'erick') && ($password == 'happy')) return
(1);
}
return (0);
}

function GetWisdom($data) {
if(CheckPassword($data)) {
$res = 'Secret to life is EWB'; print "result=1\n";
print "secret=$res\n";
}
else
{
print "result=0\n"; }
}

$data = parse_data();
if( ! isset($data['method']))
print "result=0\nerror=No method specified\n";

else {
$method = $data['method']; switch($method)
{
case 'GetWisdom': GetWisdom($data); break; default: print
"result=0\nerror=method $method not
found\n";
exit();
}

}
?>

There are a few shortcomings to this simple protocol:


- It’s not a standard way to exchange data. A standards based way would use XML, JSON
or similar.
- You cannot specify multi line strings. You would have to break a string apart into separate
lines and say:

Address1 = 200 Stoke Dr. Address2 = Waterloo, On. Address3 = Canada


Address0 = 3

Setting Address0 to 3 indicates there are three Address lines. It’s not hard, but it’s not
great.
Unlike the standards, it’s not self-specifying. You need to make up and know what all the
parameters are.
EWB likes results in JSON format, and that solves these problems. Only minor changes are
needed to the client and server.
First, the server:
We will use the PHP json_encode() function which takes a PHP object as its parameter. So
first we will define the results type:
class result {
public $wisdom = "";
}

We will define a global $error set to null. If an error is found, functions just need to set
$error to a value and the PHP code will return an HTTP error with the error code returned
as the sole text.

$data = parse_data();
$result = new result();
if( ! isset($data['method'])) {

$error = "No method specified"; } else


{

$method = $data['method']; switch($method)


{

case 'GetWisdom': GetWisdom($data); break;


default:
$error = "method $method not found"; }
}

// if error is set, then we had an error, otherwise success


if ( $error !== null ) {
header("HTTP/1.1 400");
print $error;
} else {
print json_encode( $result );
}

Here is our updated GetWisdom():

function GetWisdom($data ) {
global $error, $result; if(CheckPassword($data)) {
$res = 'Secret to life is EWB'; $result->wisdom= $res;
}
else
{
$error = 'bad userid or password';
}
}

So the whole PHP file is:


<?php
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= class result {
public $wisdom = "";
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // boilerplate
function parse_data()
{

global $HTTP_RAW_POST_DATA;

$z = array();
$s = $HTTP_RAW_POST_DATA; $xx = explode("\n", $s);
foreach($xx as $a=>$b) {

$yy = explode('=', trim($b)); if(count($yy) > 1)

$z[$yy[0]] = $yy[1]; }
return ($z);

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // simple check


userid/password function CheckPassword($data )
{

global $result;
if(isset($data['userid']) && isset($data['password'])) {

$userid = $data['userid']; $password = $data['password'];


if(($userid == 'erick') && ($password == 'happy'))

return (1);
}
return (0);

}
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // updated GetWisdom

function GetWisdom($data ) {
global $error, $result; if(CheckPassword($data)) {
$res = 'Secret to life is EWB'; $result->wisdom= $res;
}
else
{
$error = 'bad userid or password'; }
}

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= // main code


$data = parse_data();
$result = new result();
if( ! isset($data['method'])) {

$error = "No method specified"; } else


{

$method = $data['method']; switch($method)


{

case 'GetWisdom': GetWisdom($data); break;


default:
$error = "method $method not found"; }
}

// if error is set, then we had an error, otherwise success


if ( $error !== null ) {
header("HTTP/1.1 400"); print $error;

} else {
print json_encode( $result );
}
?>

The major changes to the client program is that we use a TWisdom to hold the result set:
type
TWisdom = class ( TPersistent )
private
fwisdom : string;
published
property wisdom : String read fwisdom write fwisdom; end;
The data receiver is greatly simplified:

procedure TForm1.GetWisdomRequestComplete(Request:
TServerRequest);
begin

if (Request.StatusCode <> HTTP_OK) then begin


ShowMessage('Error: '+Request.ResponseContent.Text);
GetWisdomResults( False, Request.ResponseContent.Text);
end
else
begin

if not Assigned( reader ) then


reader := TReader.Create;
if not Assigned( wisdom ) then
wisdom := TWisdom.Create;
reader.Initialize( Request.ResponseContent.Text );
wisdom.Load( reader );
GetWisdomResults( True ,'');
end;
end;

And the upcall method is simplified too:

procedure TForm1.GetWisdomResults( results : Boolean ;


errmsg : string );
begin
if results then
Label1.Caption := wisdom.wisdom
else
Label1.Caption := 'results not found:' + errmsg;
end;

So the whole client looks like this:


unit simpleweb3;
interface
uses WebHttp, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebEdits, WebLabels, WebBrwsr;
type
TWisdom = class ( TPersistent )
private
fwisdom : string;
published
property wisdom : String read fwisdom write fwisdom; end;

TForm1 = class(TForm)
Button1: TButton;
Edit1: TEdit;
PasswordEdit1: TPasswordEdit;
Label1: TLabel;
Browser1: TBrowser;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
WebRequest : TServerRequest;
Wisdom : TWisdom;
reader : TReader;
procedure GetWisdom( userid, passwordtext : string );
procedure GetWisdomResults( results : Boolean ; errmsg:

string );
procedure GetWisdomRequestComplete(Request:
TServerRequest);

public
{ Public declarations }
end;

var
Form1: TForm1;
implementation
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-
procedure TForm1.GetWisdom( userid, passwordtext : string );
begin
if not assigned( WebRequest ) then
WebRequest := TServerRequest.Create( self );

with WebRequest do
begin
OnComplete := GetWisdomRequestComplete;
RequestContent.Clear;
RequestHeaders.Clear;
URL:='jsonserver.php';
Method:=rmPost;
RequestHeaders.Values['Content-Type']:='text/plain';
RequestContent.Values['userid']:= userid;
RequestContent.Values['password']:= passwordtext;
RequestContent.Values['method'] := 'GetWisdom';
Execute;
end;

end;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-= procedure TForm1.GetWisdomRequestComplete(Request:
TServerRequest);
begin

if (Request.StatusCode <> HTTP_OK) then begin


ShowMessage('Error: '+Request.ResponseContent.Text);
GetWisdomResults( False, Request.ResponseContent.Text);

end
else
begin

if not Assigned( reader ) then


reader := TReader.Create;
if not Assigned( wisdom ) then
wisdom := TWisdom.Create;
reader.Initialize( Request.ResponseContent.Text );
wisdom.Load( reader );
GetWisdomResults( True ,'');
end;
end;

procedure TForm1.Button1Click(Sender: TObject); begin


GetWisdom( Edit1.Text, PasswordEdit1.Text); end;
procedure TForm1.GetWisdomResults( results : Boolean ;
errmsg : string );
begin
if results then

Label1.Caption := wisdom.wisdom
else
Label1.Caption := 'results not found:' + errmsg; end;
end.

In this chapter we will deal with interfacing with JavaScript by loading JavaScript
modules, calling JavaScript functions, interfacing with the Document Object Model, and
learning how to translate code fragments into Object Pascal.

There are a number of reasons why you would want to call JavaScript routines.





In some cases, you will call JavaScript for the above reasons, other times you will choose
to implement the functionality in EWB Object Pascal. Personally, I prefer Object Pascal
for the reasons I outlined in the first chapter, even though the resulting code that executes is
in fact JavaScript.

For the rest of this chapter and any units you write which involve JavaScript, include
WebDom in your uses line. It contains the essentials needed to interface with JavaScript
and you will get random errors if you forget it.

Define your types in the interface section. Declare all types before you declare variables.

Things written in JavaScript are of class object according to EWB terminology, whereas
most EWB classes are of class TObject. JavaScript itself is a classless language, it’s an
object language.

EWB prepends a header on class variables of type TObject. Consider

Type
TDog = class( TObject ) public
petname : string;

end;
var
mydog : TDog;
begin
mydog = TDog.Create;
mydog.petname := ‘harry’; end;

EWB will convert this to the following JavaScript:


mydog = test_tdog.$p.create.call(new test_tdog());
mydog.petname = "harry";

which gives a similar but slightly different result than usual JavaScript: mydog := new
Object();
mydog.petname := ‘harry’;

In many cases the default class(TObject) will work, but there are some libraries which
will get upset because the mydog object is different due to the creation technique.

If you get an error and need to simulate the above JavaScript code, you have to do things
slightly differently. First, declare the class of no ancestor type, or of type object, either
way it will be a descendant of object. You will also have to declare it external.
Type
External TGlobalDog = class
Public

Property petname : string read wirte; End;


Var myglobaldog : TGlobalDog;

begin
myglobaldog := TGlobalDog(
eval( ‘new Object();’)); myglobaldog.petname := ‘harry’;
end;

This will produce the JavaScript code:


myglobaldog := eval(‘ new Object()’); myglobaldog.petname :=
‘harry’;

EWB has two types we will use frequently in this chapter: variant and object. Object refers
to classes. Variant refers to any type: it can be an integer, string, object, etc.

When you need to access a JavaScript function, declare it external as we did above with
eval().
External function eval ( s : string ) : object;
If you do not care about the result of a JavaScript function, you can declare it as a
procedure rather than function.
Variant is the type of JavaScript variables if you are not concerned about the type, and cast
it to the type you want.

var
External myArg : variant; S : string;

Begin
S := string( myArg );
End;

If the external object is a class with methods and properties, declare them as such. Declare
the class of no particular type, or declare it of type object, which is the same thing. For
example, consider a JavaScript class we call library, declared to the EWB class TLibrary.

Type
External Tlibrary emit library = class

Public
Property name : string read; Property obj : integer read
write; Function init( arg1: integer;

arg2 : variant ):integer; End;

Then declare the global variable of that type


Var external library : TLibrary;
And you can use it in code:

Begin
Result := Library.init( 1, 3 );
End;

Note, EWB does not reference the global variable until your code does. So if you load the
library later, you must wait until it’s fully loaded before accessing the variables. The
easiest way to do this are to use TScript to load the library, then either call the library from
its onload handler, or set a flag during onload that indicates the library is available for use.
We’ll see an example of this flagging later in the OAuth 2.0 example.

Some of these preliminaries are difficult now, but they should become clearer with the
examples shown next.
The DOM is used to access parts of your web page to read, change, or create new ones.
You can also use the DOM to add JavaScript or CSS files if you wish. Google is your
friend to learn about the DOM.

To access the DOM, include the WebDom EWB module to your source file. It exposes the
external (meaning JavaScript) variable window of class type TWindow.

var
external window: TWindow;
And TWindow is defined by the EWB uses files as:

external TWindow emit Window = class


public
{ Properties }
property closed: Boolean read;
property defaultStatus: String read write; property
document: TDocument read; property event: TEvent read; //
IE-only property frames: TWindowList read; property google:
TGoogle read;
property history: THistory read;
property innerHeight: Integer read;

// Supported by IE9 or higher


property innerWidth: Integer read;
// Supported by IE9 or higher
property localStorage: TStorage read; property location:
TLocation read; property name: String read write; property
navigator: TNavigator read; property opener: TWindow read;
property orientation: Integer read;
// Mobile platforms only
property outerHeight: Integer read;
// Not supported by IE
property outerWidth: Integer read;
// Not supported by IE
property pageXOffset: Integer read;
// Not supported by IE
property pageYOffset: Integer read;
// Not supported by IE
property parent: TWindow read;
property screen: TScreen read;
property screenLeft: Integer read; // IE-only property
screenTop: Integer read; // IE-only property screenX:
Integer read; // Not supported by IE property screenY:
Integer read; // Not supported by IE property
sessionStorage: TStorage read;
property status: String read write;
property top: TWindow read;
property window: TWindow read;
{ Events }
property onblur: TEventHandler read write;
property onerror: TErrorEventHandler read write; property
onfocus: TEventHandler read write;
property ongooglemapsload:
TGoogleMapsLoadHandler read write;
property onload: TEventHandler read write;
property onresize: TEventHandler read write; property
onunload: TEventHandler read write; { Methods }
procedure addEventListener(
const type: String; listener: TEventHandler;
useCapture: Boolean);
procedure alert(const message: String);
procedure blur;
procedure cancelAnimationFrame(animationId: Integer);
procedure clearInterval(intervalId: Integer); procedure
clearTimeout(timeoutId: Integer);
procedure close;
function confirm(const question: String): Boolean; procedure
detachEvent(const type: String;
handler: TEventHandler);
procedure focus;
function getComputedStyle(
elt: TDOMElement; const pseudoElt: String):
TCSS2Properties;
procedure moveBy(dx, dy: Integer);
procedure moveTo(x, y: Integer);
function open(
const url: String; const name: String='';
const features: String='';
replace: Boolean=False): TWindow; procedure print;
function prompt(
const message: String; default: String): String; procedure
removeEventListener(
const type: String; listener: TEventHandler;
useCapture: Boolean);
function requestAnimationFrame(
callback: TAnimationHandler): Integer;
procedure resizeBy(dw, dh: Integer);
procedure resizeTo(w, h: Integer);
procedure scrollBy(dx, dy: Integer);
procedure scrollTo(x, y: Integer);
function setInterval(
code: TIntervalHandler; intervalId: Integer):Integer;
function setTimeout(
code: TIntervalHandler; intervalId: Integer):Integer; end;

I will not normally show the whole classes, but this one is quite useful and I wanted to
pique your interest. The emit word means to use the following JavaScript class but refer to
it with an EWB name, usually starting with T for convention sake.

Some of the methods and values are specific to certain web browsers, eg. Orientation,
OuterHeight, etc.
What can you do with it? Almost anything!
You can call window.print and the page will ask the user if the current window should
print.
If you want to pop up a message and temporarily stop your code, call
window.alert(‘some message’). It creates a modal dialog.

If you wanted to ask a question of a user, you could call


window.confirm(‘are you there’) and check the Boolean result. If the user
replies with Ok, the result is true. Cancel corresponds to false.

You can see that window.screen returns a TScreen, which is defined as:
external TScreen emit Screen = class
public

{ Properties }
property availHeight: Integer read; property availWidth:
Integer read; property colorDepth: Integer read; property
height: Integer read; property width: Integer read;

end;

So you could use that to see the color depth, the screen height, etc.
Look at THTMLElement:

external THTMLElement emit HTMLElement = class(TDOMElement)


public
{ Base Properties }
….

This is because an THTMLElement is a special type of TDOMElement. Other DOM


elements include CSS and JavaScript which are definitely not HTML and the
THTMLElement properties and methods do not apply.

If you are given a TDOMElement for an HTML element, you can cast it to THTMLElement
and then call the THTMLElement methods or access its properties.

For example, suppose you were given an HTML <div id=’qunit’></div> tag pair
and wanted to insert a message between them:
The JavaScript way would be to call the DOM;

function message( var msg ) {


var html = window.document.getElementById(‘qunit’);
html.innerHTML = msg;
}

The EWB procedure to do the same would be:


procedure message( msg : string ); var
html : THTMLElement;
begin
html := THTMLElement(
window.document.getElementById('qunit') ); html.innerHTML :=
msg;
end;

Note the extra step of casting window.document.getElementByID() to type THTMLElement


because it just returns a TDOMElement. This allows the compiler to type check your
assignment.

Suppose you wanted to create that ‘qunit’ DIV on the fly, you could use
window.document.body.appendChild and window.document.createElement.

Procedure createqunit;
Var
ele : TDomElement;
html : THTMLDocument;
begin
ele := window.document.createElement('div');
ele.setAttribute('id','qunit');
html := THTMLDocument(window.document );
html.body.appendChild( ele );
end;

If you wanted to load an external CSS file, you could do that by extending the HEAD DOM
element.

Procedure loadqunitcss;
Var
css : TDomElement;
html : THTMLDocument;
url : string;
begin
url := 'qunit.css';
css := window.document.createElement('link');
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', url);
html := THTMLDocument(window.document );
html.body.appendChild( css );
end;

While you could do something similar to our loadqunitcss to load a JavaScript file, I prefer
to use the EWB strategy and either have a GUI created TScript, or call TScript when you
need it with:
Procedure Form1.loadqunitscript; begin
script := TScript.Create( self );
script.onerror := script1error;
script.onload := script1load;
script.url :='qunit.js';
end;

And then define script1error which displays an error, and script1load which can then call
the loaded script.
In JavaScript redirecting to a different page is accomplished with
window.location.href = ‘http://google.com’;
EWB Object Pascal is almost identical:
window.location.href := ‘http://google.com’;
It is common to include libraries for specific functionality because including a library is a
lot easier than recoding it in EWB ObjectPascal.

In general, you will usually call either global procedures/functions, or deal with JavaScript
classes.
We will work with the Clippy.JS library that provides simulated Microsoft Office Clippy
characters. See the documentation and demonstrations at: https://www.smore.com/clippy-
js

A typical use would load clippy with the EWB external libraries code, or a TScript
strategy as discussed in the previous chapter. You will also need to have it load a JQuery
library using the same technique.

Then the JavaScript documents tell you to call something like:


clippy.load('Genius', function(agent){ agent.show();
agent.animate();
agent.speak('Welcome to my page.'); );

To do that from EWB, you need to define a subset of the class. You don’t need the whole
class, just the methods and attributes we will access. And you declare the global variable
of the class.

Type

external TAgent emit agent = class public


procedure show;
procedure animate;
procedure speak( msg : string );

end;
// we define the global function TYPE Tclippyproc =
procedure( agent : TAgent );

external TClippy emit clippy = class


public procedure load( name : string ; proc : Tclippyproc);
end;

// now define the global class variable var


external clippy : TClippy;

Once that is done, you can call it:

Implementation procedure TForm1.Form1Show(Sender: TObject);


begin

clippy.load('Genius', myclippyproc ); end;

procedure myclippyproc ( agent : TAgent ); begin


agent.show();
agent.animate();
agent.speak('Welcome to my page.'); end;

Sometimes you need a form of RichEdit to accept user input. There are several free and
several commcerical ones available. I picked CK Editor as it has support for spellchecking
in many languages and an obscene number of plugins.

To use CK Editor you place a normal MultiLineEdit on the page at design time, we’ve
named ours MultiLineEdit1, and we will tell CK Editor to replace the MultiLineEdit1 with
its own editor at run time.

We’ve also created an HTML DIV which will hold the rich text we create, and placed it to
the side. When you run your program it will look like this:
unit richeditor1;

interface
uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebComps,
WebEdits, WebLabels, WebBrwsr;

type

TForm1 = class(TForm)
Script1: TScript;
MultiLineEdit1: TMultiLineEdit;
Browser1: TBrowser;
procedure Script1Load(Sender: TObject); procedure
MultiLineEdit1Change(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

external TCKEDITOR = class


public procedure replace( name : string ); end;
CKProc = procedure( sender : TObject) of object; var
Form1: TForm1;
external CKEDITOR : TCKEDITOR;
external function eval( s : string ): object;

implementation

procedure TForm1.Script1Load(Sender: TObject); var


p : CKProc;
text1 : string;
begin

MultiLineEdit1.clientID := 'Edit1';
CKEDITOR.replace('Edit1');
text1 :=
'<h1>Hi There</h1><p>Welcome to <i>our</i> editor.</p>';
eval('CKEDITOR.instances.Edit1.setData("'+ text1+'" );');
p := MultiLineEdit1Change;
eval('CKEDITOR.instances.Edit1.on("change", p );');
MultiLineEdit1Change( Nil ); end;
procedure TForm1.MultiLineEdit1Change(Sender: TObject);
begin
browser1.DocumentText := string(
variant( eval('CKEDITOR.instances["Edit1"].getData();')));
end;
end.

In order to tell CK Editor to replace the default MultiLineEdit with itself, it needs to know
MultiLineEdit1’s HTML ID. EWB does not normally set IDs, but we can set it with
clientID. So we say:

MultiLineEdit1.clientID := 'Edit1';
CKEDITOR.replace('Edit1');

Next we want to set the data string, so we call setData(). We could have defined the whole
Pascal objects, but it was simpler just to call JavaScript’s eval() routine and let it deal
with the complicated mess.

eval('CKEDITOR.instances.Edit1.setData("'+ text1+'" );');

We need to update the screen after each keystroke or mouse change, so we want to set
CKEditor’s Change function. You would think we could specify a typical function to do
this, but EWB changes the functions names, especially when you compress your output. So
first we set variable p to the name of the function we want to call – EWB automatically
knows the correct function name at compile time, so it fills it in. Then we assign the
Change event to p at run time.

p := MultiLineEdit1Change;
eval('CKEDITOR.instances.Edit1.on("change", p );');
MultiLineEdit1Change( Nil );
We call MultiLineEdit1Change to update the DIV right away, so it displays the supplied
text the instant one opens the web page.

MultiLineEdit1Change() just copies the getData() data from the CK Editor into the DIV. But
remember I was lazy and did not create all the structures but just called eval(), well eval()
returns an object but I need a string. I cannot cast an object to a string, but I can cast it to a
variant, and variants can be cast to strings. So we get this nasty casting that throws out all
the protections of EWB but convinces the compiler it makes sense.

procedure TForm1.MultiLineEdit1Change(Sender: TObject);


begin
browser1.DocumentText := string(
variant( eval('CKEDITOR.instances["Edit1"].getData();')));
end;

You may wonder why I have Sender : TObject as a passed parameter. Again, laziness. I
used MultiLineEdit1’s OnChange default handler to create this. Unlike some programming
enviroments where passed parameters must be cleaned up by the function, JavaScript (and
thus EWB) can be passed any number of unused parameters. JavaScript is just that way.
EWB creates applications with the speed and flair of real applications. But traditionally
JavaScript could not read local files as that would be a security hole. That changed with
the File Reader. Now you can have the ability to load local files into your application and
process the data any way you like. An example of this would be to have a YouTube-like
uploader which sends files upstream and gives a visual indicator of the progress.

For our example we will ask the user for a file to read, then load it into a multiline edit
control. Obviously text files would look best in that editor.

Since the user may load a large file, we display the EWB busy message while the file
loads. In fact, try loading a huge file like a movie, it will take forever but at least the busy
message explains why.

Our app will look like this:


The grey area is an EWB HTMLForm from the containers section. Place a FileComboBox
in that area.
Put a Label beneath it, a MultiLineEdit control with both scrollbars enabled, and add a
Timer which will be used to initialize everything.
Make the FileComboBox, MultiLineEdit and Label invisible by setting Visible to false;
Then you just need the following code:
unit filereader1;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebBrwsr,
WebEdits, WebBtns, WebComps, WebLabels;
type

TForm1 = class(TForm)
HTMLForm1: THTMLForm;
FileComboBox1: TFileComboBox;
Timer1: TTimer;
Label1: TLabel;
MultiLineEdit1: TMultiLineEdit;
Label2: TLabel;
Label3: TLabel;
procedure Timer1Timer(Sender: TObject);

private
{ Private declarations }
procedure StartReading( f : TFileType ; r : TFileReader);
function readSingleFile( evt : TMyEvent):boolean;

public
{ Public declarations }
end;

external TArgTarget = class public


property result : string read; end;
external TArgFileReader = class public
property target : TArgTarget read; end;
external TMyEventFiles = class
public
property files : array of TFileType read; end;
external TMyEvent = class
public
property target : TMyEventFiles read; end;
TArgFileReaderFn = function ( x : TArgFileReader ) :
integer;

external TFileType = class


public
property name : string read; property size : integer read;
property type : string read;

end;

external TFileReader = class public


property onload : TArgFileReaderFn read write; procedure
readAsText(f : TFileType);

end;

var
Form1: TForm1;
external function eval( arg : string ) : object;

implementation
function myreader( x : TArgFileReader ):integer; var
contents : string;

begin
contents := x.target.result;
HideProgress;
Form1.MultiLineEdit1.Lines.Text := contents ;
Form1.MultiLineEdit1.Visible := True; result := 0;
end;

procedure TForm1.StartReading( f : TFileType ; r :


TFileReader); begin
r := TFileReader( eval('new FileReader();') );
r.onload := myreader;
r.readAsText(f);
end;

function TForm1.readSingleFile( evt : TMyEvent):boolean; var


f : TFileType;
r : TFileReader;
begin
//Retrieve the first (and only!) File from the FileList
object
Label1.Visible := True;
f := evt.target.files[0];
if f <> Nil then begin
Label1.Caption := 'Reading file: '+ f.name ;
ShowProgress('reading file');
Async StartReading( f , r );
end
else
Label1.Caption := 'Failed to load file';
end;

procedure TForm1.Timer1Timer(Sender: TObject); var


ele : TDOMElement;

begin
Timer1.Enabled := False;
// set the ID of FileComboBox1 to FileComboBox1
FileComboBox1.ClientID := 'FileComboBox1';
ele := TDOMElement(window.document.getELementByID(

'FileComboBox1'));
ele.addEventListener('change',readsinglefile, False );
FileComboBox1.Visible := True;

end;
end.

Some explanation is necessary. Look at Timer1Timer event. First we cancel the timer so
we don’t get subsequent events. The EWB normal onchange handler does not pass the
Event, just the Sender, but we need the Event from from the FileComboBox1 change event
as it contains the file information. So we need to call addEventListener to tell the DOM to
call our readsinglefile routine in the event of a filename change.

That introduces a second problem, DOM likes to refer to elements by their ID, but EWB
does not use ID’s and so doesn’t bother setting them. So first we set the ID by setting the
clientID = ‘FileComboBox1’. Then we can use getElementByID(‘FileComboBox1’) and
make our changes.

ReadSingleFile could contain all the code of StartReading, but we call it Async so that
ShowProgress() can start first and Label1.Caption will be displayed.
MyReader is called when the file loading is complete, so it does something with the data
and clears the progresss meter.

For production code you would need to better test with try/except to detect browsers that
cannot handle the reader. We left them out of this sample to keep it shorter.

This example lets you select a local file and then upload it using JavaScript. We will
indicate the filename and filesize while uploading.
unit fileuploader1;
interface
uses WebDom, WebHttp, WebCore, WebUI, WebForms, WebCtrls,
WebBrwsr, WebEdits, WebBtns, WebComps,
WebLabels;
type

TForm1 = class(TForm)
HTMLForm1: THTMLForm;
FileComboBox1: TFileComboBox;
Timer1: TTimer;
Label2: TLabel;
Label3: TLabel;
procedure Timer1Timer(Sender: TObject);

private
{ Private declarations }
HttpRequest : TServerRequest;
fname : string;
fsize : integer;
function myreader( x : TArgFileReader ):integer; procedure
StartReading( f : TFileType ; r : TFileReader); function
readSingleFile( evt : TMyEvent):boolean; procedure
UploadComplete( sr : TServerRequest ); procedure UploadFile(
base64 : string );

public
{ Public declarations }
end;

external TArgTarget = class public


property result : string read; end;
external TArgFileReader = class public
property target : TArgTarget read; end;
external TMyEventFiles = class
public
property files : array of TFileType read; end;
external TMyEvent = class
public
property target : TMyEventFiles read; end;
TArgFileReaderFn = function ( x : TArgFileReader ) : integer
of object;
external TFileType = class

public
property name : string read; property size : integer read;
property type : string read;

//
end;
external TFileReader = class

public
property onload : TArgFileReaderFn read write; procedure
readAsText(f : TFileType); procedure readAsDataURL( f :
TFileType );

end;

var
Form1: TForm1;
external function eval( arg : string ) : object;

implementation

procedure TForm1.UploadComplete( sr : TServerRequest );


begin
HideProgress;
if (sr.StatusCode <> HTTP_OK) then
ShowMessage('Error: '+sr.ResponseContent.Text, 'Results')
else
ShowMessage('Done','Results');
end;

const
theurl = 'PostBase64.php';

procedure TForm1.UploadFile( base64 : string );


begin
//The script will convert the Base64 into a PNG, and save it
if not Assigned( HttpRequest ) then
HttpRequest := TServerRequest.Create(self);

with HttpRequest do
begin
method := rmPost;
onComplete := UploadComplete;
RequestHeaders.Clear;
RequestContent.Clear;
url := theurl;

//need to specify the content type

RequestHeaders.add('Content-Type: application/x-www-
formurlencoded');
RequestContent.add('imagedata=' + base64 );
RequestHeaders.add('Content-Length: ' + IntToStr( Length(
RequestContent[0])));
execute;
end;
end;

function TForm1.myreader( x : TArgFileReader ):integer; var


contents : string;

begin
contents := x.target.result;
HideProgress;
ShowProgress('Uploading File :' + fname +', '+IntToStr(
fsize)

+ ' bytes');
UploadFile( contents );
result := 0; end;

procedure TForm1.StartReading( f : TFileType ; r :


TFileReader); begin
r := TFileReader( eval('new FileReader();') );
r.onload := myreader;
r.readAsDataURL(f);
end;
function TForm1.readSingleFile( evt : TMyEvent):boolean; var
f : TFileType;
r : TFileReader;
begin
//Retrieve the first (and only!) File from the FileList
object f := evt.target.files[0];
if f <> Nil then begin
fname := f.name;
fsize := f.size;
ShowProgress('reading file : '+ fname +
', '+IntToStr( fsize) + ' bytes');
Async StartReading( f , r );
end
else
ShowMessage('Failed to load file','Error'); end;

procedure TForm1.Timer1Timer(Sender: TObject); var


ele : TDOMElement;

begin
Timer1.Enabled := False;
FileComboBox1.ClientID := 'FileComboBox1';
ele := TDOMElement(

window.document.getELementByID('FileComboBox1'));
ele.addEventListener('change',readsinglefile, False );
FileComboBox1.Visible := True;

end;
end.

The first real difference is

procedure TForm1.StartReading( f : TFileType ; r :


TFileReader);
begin
r := TFileReader( eval('new FileReader();') ); r.onload :=
myreader;
r.readAsDataURL(f);
end;

We use the readAsDataURL() method which reads the file and converts it to a Base64
notation then calls myreader().
Base64 is quite compact, it store 6 bytes of data in every 8 printable characters. We will
see Base64 again in the Camera examples under Cordova.
function TForm1.myreader( x : TArgFileReader ):integer; var
contents : string;

begin
contents := x.target.result;
HideProgress;
ShowProgress('Uploading File :' + fname
+', '+IntToStr( fsize) + ' bytes'); UploadFile( contents );

result := 0;
end;
myreader is given the contents of the file in base64 and calls the UploadFile() function
after informing the user of our activity.

procedure TForm1.UploadFile( base64 : string );


begin
//The script will convert the Base64 into a PNG, and save it
if not Assigned( HttpRequest ) then
HttpRequest := TServerRequest.Create(self);

with HttpRequest do
begin
method := rmPost;
onComplete := UploadComplete;
RequestHeaders.Clear;
RequestContent.Clear;

url := theurl;
//need to specify the content type
RequestHeaders.add(

'Content-Type: application/x-www-form-urlencoded');
RequestContent.add('imagedata=' + base64 );
RequestHeaders.add('Content-Length: '

+ IntToStr( Length( RequestContent[0]))); execute;


end;
end;

UploadFile() ensures we have a fresh HttpRequest TServerRequest, indicates we will


POST – which sends the data in the data section rather than the header section, and adds
the Base64 data. It also indicates the Content-Length as that is a polite thing to do.

When the upload is complete, UploadComplete is called

procedure TForm1.UploadComplete( sr : TServerRequest );


begin
HideProgress;
if (sr.StatusCode <> HTTP_OK) then

ShowMessage('Error: '+sr.ResponseContent.Text, 'Results')


else
ShowMessage('Done','Results');
end;

So it does some GUI stuff to indicate the upload worked or it didn’t. Usually servers
discard files larger than an arbitrary size, and they may not handle this failure gracefully.
So your app should double-check that the file was copied, and you can even make
decisions based on the fileszie before you upload. Eg. ‘Hmmm, 33GB, no, that’s not going
to work.’

The disadvantage of this upload strategy is that the file must be able to be copied to
memory, in expensive Base64 sizing.

Before we continue, here is the PHP upload code. This is a very simple version, you will
need to add error checking and file storage, and you can look at the MIME type which I just
discard here. I assume I can write to the local subdirectory and write the data to junk.txt
(Base64 encoded) and junk.png (binary). Don’t be fooled by the png extension, you can
upload any file using this.

PostBase64.php
<?php
$imageFilename = 'junk';
$img = $_POST['imagedata'];
file_put_contents( "$imageFilename.txt", $img );

$offset = strpos( $img, ',');


$img = substr( $img, $offset+1, strlen( $img) - $offset );
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
file_put_contents($imageFilename . '.png', $data);

?>

$offset is the offset of the end of the header as it always ends in a comma. So we construct
a string of the remaind of the incoming string after the comma. Our Uploader2 sample
builds on the previous examples by cutting the file into manageable segments or chunks and
uploading each segment. After each segment is uploaded, we can update our progressbar so
the user knows something is happening. And by using smaller segments, we get around the
maximum upload size limit of the PHP server. On the downside, both the client and server
must still have enough available RAM to hold the complete file in Base64 encoding, though
we will explain how to alleviate that server problem in the followup text..

To accomplish this segmentization, we introduce a few new form variables:


contents segment

startindex the contents of the upload in Base64


holds the segment number of the file
1 – first SEGMENTSIZE bytes
2 – second, next SEGMENTSIZE bytes 3, 4, 5, …
0 is End of File, time to convert Base64 to binary the starting position of this segment

SEGMENTSIZE can be any number of bytes for each segment. In this example we set it
incredibly low, at 512 bytes each just to show how it works. The TCP/HTTP setup for
each segment eats into performance and adds to the HTTP server logs. In reality, TCP and
HTTP/S get more efficient with larger byte sizes, so you will want to tradeoff between
efficiency bigger sizes, and user updates which are less frequent with bigger sizes. I
suggest setting SEGMENTSIZE to 16kB or 64kB.

You will need to add a ProgressBar1 to your form. I suggest setting the ProgressBar’s
Background FillColor to something other than White, maybe a dull gray.

unit fileuploader2a;
interface
uses WebDom, WebHttp, WebCore, WebUI, WebForms, WebCtrls,
WebBrwsr, WebEdits, WebBtns, WebComps,
WebLabels, WebProgs;
type
TForm1 = class(TForm)
HTMLForm1: THTMLForm;
FileComboBox1: TFileComboBox;
Timer1: TTimer;
Label2: TLabel;
Label3: TLabel;
ProgressBar1: TProgressBar;
Label1: TLabel;
procedure Timer1Timer(Sender: TObject);

private
{ Private declarations }
segment : integer; // 0=EndOfFile, 1 is first, 2, 3, ...
contents : string; // moved to HERE to store between

calls to upload file


startindex : integer; // where in the file we are starting
this segment

HttpRequest : TServerRequest;
fname : string;
fsize : integer;
function myreader( x : TArgFileReader ):integer; procedure
StartReading( f : TFileType ; r : TFileReader); function
readSingleFile( evt : TMyEvent):boolean; procedure
UploadComplete( sr : TServerRequest ); procedure UploadFile;

public
{ Public declarations }
end;

external TArgTarget = class public


property result : string read; end;
external TArgFileReader = class public
property target : TArgTarget read; end;
external TMyEventFiles = class
public
property files : array of TFileType read; end;
external TMyEvent = class
public
property target : TMyEventFiles read; end;
TArgFileReaderFn = function ( x : TArgFileReader ) : integer
of object;
external TFileType = class
public
property name : string read; property size : integer read;
property type : string read;

//
end;
external TFileReader = class

public
property onload : TArgFileReaderFn read write; procedure
readAsText(f : TFileType); procedure readAsDataURL( f :
TFileType );

end;

var
Form1: TForm1;
external function eval( arg : string ) : object;

const
SEGMENTSIZE = 512;
implementation
procedure TForm1.UploadComplete( sr : TServerRequest );
begin
if segment > 0 then begin
inc( segment );

UploadFile;
end else begin
HideProgress;
if (sr.StatusCode <> HTTP_OK) then
ShowMessage('Error: '+sr.ResponseContent.Text, 'Results')
else
ShowMessage('Done','Results');
end;
end;

const
theurl = 'PostBase64Seg.php'; // handle segments

procedure TForm1.UploadFile;
var
base64 : string ; // now compute this
len : integer;
begin

if segment = 1 then startindex := 1 else inc( startindex,


SEGMENTSIZE ); if startindex > fsize then begin
segment := 0; // end of file
base64 := '';
end else begin

// copy at most SEGMENTSIZE, or to the end of the file len


:= fsize - startindex + 1;
if len > SEGMENTSIZE then len := SEGMENTSIZE ;

base64 := Copy( contents, startindex , len );


ProgressBar1.Position := startindex; end;

//The script will convert the Base64 SEGMENTed file into a


binary file, and save it
if not Assigned( HttpRequest ) then
HttpRequest := TServerRequest.Create(self);

with HttpRequest do
begin
method := rmPost;
onComplete := UploadComplete;
RequestHeaders.Clear;
RequestContent.Clear;

url := theurl;
//need to specify the content type
RequestHeaders.add(
'Content-Type: application/x-www-form-urlencoded');
RequestContent.add('imagedata=' + base64 +
'&segment='+IntToStr(segment) );
RequestHeaders.add('Content-Length: ' +
IntToStr( Length( RequestContent[0])));
execute;
end;
end;

function TForm1.myreader( x : TArgFileReader ):integer;


begin
contents := x.target.result;
HideProgress;
ShowProgress('Uploading File :' + fname +', '+IntToStr(
fsize) + ' bytes');
segment := 1; // start with First
UploadFile;

result := 0; end;

procedure TForm1.StartReading( f : TFileType ; r :


TFileReader); begin
r := TFileReader( eval('new FileReader();') );
r.onload := myreader;
r.readAsDataURL(f);
end;
function TForm1.readSingleFile( evt : TMyEvent):boolean; var
f : TFileType;
r : TFileReader;
begin
//Retrieve the first (and only!) File from the FileList
object f := evt.target.files[0];
if f <> Nil then begin
fname := f.name;
fsize := f.size;
ProgressBar1.MaxValue := fsize;
ProgressBar1.Position := 0;
ShowProgress('reading file : '+ fname +', '+IntToStr( fsize)
+ ' bytes');
Async StartReading( f , r );
end
else
ShowMessage('Failed to load file','Error');
end;

procedure TForm1.Timer1Timer(Sender: TObject);


var
ele : TDOMElement;

begin
Timer1.Enabled := False;
FileComboBox1.ClientID := 'FileComboBox1';
ele :=

TDOMElement(window.document.getELementByID('FileComboBox1'));
ele.addEventListener('change',readsinglefile, False );
FileComboBox1.Visible := True; end;
end.

What’s changed starts with myreader:

function TForm1.myreader( x : TArgFileReader ):integer;


begin
contents := x.target.result;
HideProgress;
ShowProgress('Uploading File :' + fname +', '+IntToStr(
fsize) + ' bytes');
segment := 1; // start with First
UploadFile;

result := 0; end;

We save the Base64 text into the Form’s contents variable, initialize the segment to 1 and
call UploadFile which now has no arguments.
UploadFile is called repeatedly, and is the workhorse of this version.

procedure TForm1.UploadFile;
var
base64 : string ; // now compute this
len : integer;
begin

// start at the beginning of the file, position 1


if segment = 1 then startindex := 1
else inc( startindex, SEGMENTSIZE );

// are we at the end of the file?, if so, send Segment 0


if startindex > fsize then begin
segment := 0; // end of file
base64 := '';

end else begin


// copy at most SEGMENTSIZE, or to the end of the file len
:= fsize - startindex + 1;
if len > SEGMENTSIZE then len := SEGMENTSIZE ; base64 :=
Copy( contents, startindex , len ); ProgressBar1.Position :=
startindex;

end;

//The script will convert the Base64 SEGMENTed file into a


binary file, and save it
if not Assigned( HttpRequest ) then
HttpRequest := TServerRequest.Create(self);

with HttpRequest do
begin
method := rmPost;
onComplete := UploadComplete;
RequestHeaders.Clear;
RequestContent.Clear;

url := theurl;
//need to specify the content type
RequestHeaders.add(
'Content-Type: application/x-www-form-urlencoded');
RequestContent.add('imagedata=' + base64 +
'&segment='+IntToStr(segment) );
RequestHeaders.add('Content-Length: ' +
IntToStr( Length( RequestContent[0])));
execute;
end;
end;

The other change is to chain successive FileUploads, which we do in the UploadComplete


function.
procedure TForm1.UploadComplete( sr : TServerRequest );
begin
if segment > 0 then begin
inc( segment );

UploadFile;
end else begin
HideProgress;
if (sr.StatusCode <> HTTP_OK) then
ShowMessage('Error: '+sr.ResponseContent.Text, 'Results')
else
ShowMessage('Done','Results');
end; end;

The simple PHP for this is: PostBase64Seg.php


<?php
$imageFilename = 'junk';;
$imageTEXT = "$imageFilename.txt";
$img = $_POST['imagedata']; $seg = $_POST['segment'];

switch ( $seg ) {
case 1 : // new file
file_put_contents( $imageTEXT, $img ); break;

case 0 : // end of file


$img = file_get_contents($imageTEXT );
$offset = strpos( $img, ',');

$img = substr( $img, $offset+1,


strlen( $img) - $offset );
$img = str_replace(' ', '+', $img);
$data = base64_decode($img);
file_put_contents($imageFilename . '.png', $data);
break;
default : // somewhere in the middle
$img2 = file_get_contents($imageTEXT);
file_put_contents( $imageTEXT, $img2 . $img );
break;
}
?>

The PHP needs more error checking and all sorts of sanity tests, but I wanted to keep the
example simple.

To overcome the server memory requirements, you could simply append the incoming data
to a text file with file i/o, then run base64 –D (available on most Unix systems) to convert
the text file to binary. See man base64 for details. The EWB progress animation prevents
user input. If you do not use ShowProgress/HideProgress, you could put up a cancel button
which permits users to interrupt transfers at the end of the current segment by adding a
segment=-1 state which does not call the upload function, and which displays a ‘Cancelled’
message.

Uploads on many Internet connections are one fifth to one tenth the speed of downloads, so
uploading is often annoyingly slow for users. Your users will greatly appreciate the user
friendliness of the progress bar and a cancel button. Local file i/o is not limited to reading.
HTML5 has a wonderful file writing function which will someday make it trivial to write
files from JavaScript… once that function is implemented by the browser authors.

Until that time, you have the choice of writing a hugely complex subroutine which is
compatible with every browser by employing their multitude of strategies or enjoy the
benefits of the filesaver.js library which has done exactly that.
Here is an editor which lets you write/edit data and then save to a local file.

The source code is quite simple. Note, we have to specify the MIME type of the file.
unit firefoxsave1;

interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebBrwsr, WebComps, WebEdits;

type

TForm1 = class(TForm)
Button1: TButton;
Script1: TScript;
MultiLineEdit1: TMultiLineEdit;
procedure Button1Click(Sender: TObject);

private
{ Private declarations }
public
{ Public declarations }
end;

TProc = procedure ( x : integer) ;


external function eval( s : string ) : object;
external function saveAs( blob : object ; filename : string
):object; // filesaver.js
var
Form1: TForm1;
implementation

procedure TForm1.Button1Click(Sender: TObject); var


blob : object;
buffer : string;
begin
buffer := MultiLineEdit1.Lines.Text;
blob := eval('new Blob([buffer],{type:"text/plain"});');
saveAs( blob, 'test.dat');
end;

end.

You will need to define a TScript to load filesaver.js. Again, production code should check
that the script loads successfully.
The exact outcome of using filesaver.js depends on the browser. Current versions of
Chrome and IE and Edge display an indicator saying the file is downloaded and available
on the bottom of the window. FireFox displays a Save/Open popup box. Safari opens the
new text in a new browser window and it is up to the user to use File | Save.

You can do more than text. This next project downloads an Image from the Web, uses the
graphics routines to copy the image and place a red circle in the corner, then lets you save
the image to a local file. It uses filesaver.js and a library named canvastoblob.js which
takes a canvas and creates a PNG file from it suitable for saving locally. You will need
two TScripts, one for filesaver.js and one for canvastoblob.js.
Here I load a shopping cart image into a TImage. Then when you press ‘draw graphics’ the
app copies the image and adds a circle.
Press ‘save graphics’ to save the new image as a PNG file. Note, any area not drawn on by
our circle or the source picture will be saved as transparent. If this is a problem, first
cover the paint image with a background color.

Here’s the source code:


unit firefoxsave1;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebBrwsr, WebComps, WebEdits,
WebPaint;
type

TForm1 = class(TForm)
Script1: TScript;
btSaveGraphics: TButton;
Paint1: TPaint;
Image1: TImage;
Script2: TScript;
btDrawGraphics: TButton;
procedure btDrawGraphicsClick(Sender: TObject); procedure
btSaveGraphicsClick(Sender: TObject);

private
{ Private declarations }
savefilename : string;
procedure savefn( blob : object );

public
{ Public declarations }
end;

TBlobSave = procedure( blob : object ) of object;


external Tcanvastoblob = class (THTMLElement) public
procedure toBlob( blobsavefn : TBlobSave ); end;
external function eval( s : string ) : object;
external function saveAs( blob : object ; filename : string
):object; // filesaver.js
var
Form1: TForm1;
implementation
procedure DrawCircleAt( pain : TPaint ; x, y, r : integer );
begin
with pain.canvas do begin
BeginPath;
MoveTo( x+1,y+1);
Arc( x, y, r,0,7);
ClosePath;
FillColor := clRed;
Fill;

end;
end;

procedure TForm1.btDrawGraphicsClick(Sender: TObject); var


image : THTMLElement;
paint : THTMLElement;
ele : TElement;
context : THTMLCanvasRenderingContext2D; begin
image1.clientID := 'myimage';
image := THTMLElement(
window.document.getElementById('myimage'));
image := THTMLElement( image.childNodes[0] );

Paint1.clientID := 'mycanvas';
paint :=
THTMLElement(window.document.getElementById('mycanvas'));
context := THTMLCanvasElement( Paint ).getContext('2d');
eval( 'context.drawImage(image, 1,1);');
DrawCircleAt( Paint1, 25 ,25, 25 ); end;
procedure TForm1.savefn( blob : object ); begin
saveAs( blob, savefilename);
end;
procedure TForm1.btSaveGraphicsClick(Sender: TObject); var
paint : TCanvasToBLob;

begin
Paint1.clientID := 'mycanvas';
paint :=

TCanvasToBlob(window.document.getElementById('mycanvas'));
savefilename := 'test.png'; paint.toBlob( savefn ); end;
end.

If you are looking at this example to see how to copy an image into a TPaint, you will be
confused by:

image := THTMLElement(
window.document.getElementById('myimage'));
image := THTMLElement( image.childNodes[0] );

The thing is: EWB puts the image inside a DIV, and the clientID is assigned to the DIV, not
the IMG. So when we use getElementById we get the DIV but we want its child IMG. The
next line extracts the first (and only) child of the DIV, which is the IMG.
Smoothie is a scrolling chart JavaScript library. Use it to display real-time graphs of your
data.

Our example just plots two random numbers between 0 and 1000, using slight colorization
to make the charts interesting. In your real-world uses you could plot any real-time data.
For this project you will need a TTimer, a TPaint (which is really a JavaScript Canvas),
and a TScript to load the smoothie.all.js file.
unit smoothi1;
interface
uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebLabels,
WebComps, WebPaint;
type

TForm1 = class(TForm)
Label1: TLabel;
Script1: TScript;
Paint1: TPaint;
Timer1: TTimer;
procedure Timer1Timer(Sender: TObject); procedure
Script1Load(Sender: TObject);

private
{ Private declarations }
smoothie : TGlobalSmoothieChart; line1, line2 :
TGlobalTimeSeries;

public
{ Public declarations }
end;

external TGlobalTimeSeries emit GlobalTimeSeries = class


public procedure append( s : integer; y : double );
end;

external TGlobalSmoothieLineOptions = class public


property strokeStyle: string read write;
property fillStyle: string read write;
property lineWidth: integer read write; end;

external TGlobalSmoothieChart emit SmoothieChart = class


public
procedure streamTo( e : TDOMElement ; delay : integer = 0

);
procedure addTimeSeries( s : TGlobalTimeSeries; options
: TGlobalSmoothieLineOptions );
end;

var
Form1: TForm1;
external function eval( s : string ) : object;
implementation
function LocalTime : integer; begin
result := integer(now); end;

procedure TForm1.Timer1Timer(Sender: TObject); var


begin

if smoothie <> Nil then begin


// Add a random value to each line every second
line1.append( localtime , random(0,1000)); line2.append(
localtime, random(0,1000));

end;
end;
procedure TForm1.Script1Load(Sender: TObject); var
options : TGlobalSmoothieLineOptions;

begin
Paint1.clientID := 'mycanvas';
smoothie := TGlobalSmoothieChart(

eval('new SmoothieChart();'));
smoothie.streamTo(window.document.getElementById(
'mycanvas'),1000);

line1 := TGlobalTimeSeries( eval('new TimeSeries();'));


line2 := TGlobalTimeSeries( eval('new TimeSeries();'));

// Add to SmoothieChart
options := TGlobalSmoothieLineOptions( eval('new
Object();')); options.strokeStyle := 'rgb( 0, 255, 0 )';
options.fillStyle := 'rgba( 0, 255, 0, 0.4 )';
options.lineWidth := 3;
smoothie.addTimeSeries(line1, options );
options.strokeStyle := 'rgb(255, 0, 255)';
options.fillStyle := 'rgba( 255, 0, 255, 0.3 )';
smoothie.addTimeSeries(line2, options );

end;
end.

The streamTo() call takes an options delay parameter we set to 1000. This is the number of
milliseconds to buffer before displaying data. Since we only provide new data every 1000
ms (TTimer’s default), the output would appear jerky if we did not buffer it for a full
second.

For the options, JavaScript’s rgb() returns an red green blue color-space value, whereas
rgba() returns the color-space with an opacity alpha, meaning the fill is semi-transparent.
Google Charts is an excellent way to add an interactive graph to your program. If the user
mouses over parts of the graph they will highlight and give various data information.

Pie, bar and other charts are easy once you see how to use the library.
Add a TScript which loads the library from Google. Set its onload to
You will need a DIV with ID set to chart_div to hold the graph. Simply place a
TBasicPanel on the form, it’s a DIV, and set its ID property with clientID := ‘chart_div’
before you use it.

For this example I used eval() more liberally to compute parameters for various functions.

Data is supposed to be an array of arrays by two, where the first element is a string and the
second is a number. We declare it as an array of array of variants, as variants are
compatible with strings and numbers. We must use SetLength() to set the length when
adding values.

The code is about 100 lines, and the results are very effective.
unit googlecharts1;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebComps,
WebCtnrs, WebLabels;
type

TForm1 = class(TForm)
Script1: TScript;
BasicPanel1: TBasicPanel;
Label1: TLabel;
procedure Script1Load(Sender: TObject);

private
{ Private declarations }
options : TGVChartOptions ;
chart : TGChart;
data2 : array of Tdatum;
data : TGVis;

procedure drawChart;
public
{ Public declarations }
end;
TDatum = array of variant; TDrawProc = procedure of object;
external TGChart = class
public procedure draw( data : variant ; options : variant );
end;

external TGVis = class


public
procedure addColumn( ctype : string; name : string );
procedure addRows( x : variant );

end;

external TGCharts = class


public
procedure load( s : string ; options : variant ); procedure
setOnLoadCallback(drawChart : TDrawProc );

end;

external TGVChartOptions = class


public
property title : string read write; property width : integer
read write; property height : integer read write;

end;
external TGoogle emit google = class public
property charts : TGCharts read write; end;
var
Form1: TForm1;
external google : TGoogle;
external function eval( s : string ) : object;
implementation

procedure TForm1.Script1Load( Sender : TObject );


begin
// Load the Visualization API and the corechart package.
// example of using eval() to just plain run some JavaScript
code
eval('google.charts.load("current",{"packages":
["corechart"]});' );
BasicPanel1.clientID := 'chart_div';
// Set a callback to run when the Google Visualization API
is loaded.
google.charts.setOnLoadCallback(drawChart);
end;

// Callback that creates and populates a data table, //


instantiates the pie chart, passes in the data and // draws
it.
procedure TForm1.drawChart; var
v : variant;

begin
// Create the data table.
data := TGvis( eval('new
google.visualization.DataTable()'));
data.addColumn('string', 'Topping');
data.addColumn('number', 'Slices');

setlength( data2, 5 );
data2[0] := [ variant('Mushrooms'), variant(3)]; data2[1] :=
[ variant('Onions'), variant(1)]; data2[2] := [
variant('Olives'), variant(1)]; data2[3] := [
variant('Zucchini'), variant(1)]; data2[4] := [
variant('Pepperoni'), variant(2)];

data.addRows( data2 );

// Set chart options


options := TGVChartOptions( eval('new Object();'));
options.title := 'How Much Pizza I Ate Last Night';
options.width := BasicPanel1.Width;
options.height := BasicPanel1.Height;

// Instantiate and draw our chart, passing in some options.


chart := TGChart(eval( 'new google.visualization.PieChart(
document.getElementById("chart_div"));'));

chart.draw(data, options);
end;
end.

OAuth 2.0 is a protocol which allows social sharing. You can use it to find the identity of a
user from such social networks as Google, Microsoft, Facebook, Linked In, etc. You can
even use it to access friend lists, pictures, Google Drives, etc. Pinterest, for example, uses
it to determine your identity and your friends so it can notify them with what strikes your
fancy.

Unlike protocols of yore, OAuth 2.0 does not require your userid or password. Through a
complex handshake, it pops up a window which either asks for your credentials at the
social host (eg. Google), or it will find cached credentials from a previous time.

There are whole books on OAuth 2.0 and countless web sites explaining it We will use the
popular JavaScript library called Hello.JS which does most of the heavy lifting. A key
benefit of Hello.JS is that it makes it easy to support multiple vendor OAuth 2.0
implementations and hides the ugly differences.

A typical simple Hello.JS program would look like this:

<html><head>
<script src='hello.all.js'></script>
</head>
<body>
<button onclick="hello('google').login( function (){

token = hello('google').getAuthResponse.access_token;
});">Google</button>
<script>

var GOOGLE_CLIENT_ID =

'5770808996983asdfasdfasdfasdfasdfasdf.apps.googleusercontent.co
m' ;
hello.init(

{google : GOOGLE_CLIENT_ID },

{redirect_uri:'redirect.html',
scope: 'basic'}
);
hello.on('auth.login', function(auth) {

// Call user information, for the given network


hello(auth.network).api('me').then(function(r) { alert('got
'+r.thumbnail);

});
});
</script></body></html>

Note, you will need to get your own IDs from Facebook, Microsoft and Google for this
project. See the Hello.JS documentation to learn how to do this.
This simple program loads the Hello.JS library and adds a button which pops up an alert
with the URL to the user’s Google thumbnail picture.
In order to work, it also needs a redirect.html file that looks like this and is registered as
the redirect page for your ID:
<!DOCTYPE html>
<html>
<head>

<title>Hello, redirecting...</title>

<meta name="viewport" content="width=device-width, initial-


scale=1.0, user-scalable=yes" />
<link href="./assets/redirect.css" rel="stylesheet" />
</head>
<body>

<div
class="loading"><span>&bull;</span><span>&bull;</span>
<span>&bul l;</span></div>

<h2>Please close this window to continue.</h2> <script>


window.onbeforeunload = function(){
document.getElementsByTagName('h2')[0].innerHTML="Redirect
ing, please wait";
}
</script>

<script src="hello.all.min.js"></script> </body>


</html>

We will reuse the redirect.html file exactly as the JavaScript example did, there is no
benefit to recoding it in EWB, and EWB is bigger and slower to load.

The test.html file is a little bit confusing because it references hello.on( … )


And hello( network ).api(…).then(…)

How can it be both a variable and a function? To be honest, it is more complicated than
most libraries to decipher.

A quick perusal of the hello.all.js source file indicates that hello( network )… is an
alias for hello.use( network )… So substituting that is our first step.

I built a sample application which displays the user’s name and thumbnail using EWB. It’s
a bit longer than the above JavaScript, but that’s because it does more, and it uses the
strong Object Pascal typing.
The source code is as follows:

unit test;
interface
uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebComps, WebBrwsr, WebLabels;
type
TForm1 = class(TForm)

Script1: TScript;
btGoogle: TButton;
Image1: TImage;
Label1: TLabel;
Timer1: TTimer;
btFaceBook: TButton;
btWindows: TButton;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedure btGoogleClick(Sender: TObject);
procedure Script1Load(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure btFaceBookClick(Sender: TObject); procedure
btWindowsClick(Sender: TObject); private
{ Private declarations }
inited : integer; // when set to 1, timer enables the
buttons, then sets to 2
public
{ Public declarations }
end;

// classes uses by Hello.JS, these are declared external


External TGlobHelloArgs = class
public

property facebook : string read write ;


property google : string read write;
property windows : string read write;

end;

external TGlobHelloRedirect = class public


property redirect_uri : string read write ;
property scope : string read write; end;

external TAuth = class


public
property network : string read; end;
type
external TLoginUser = class
public

property thumbnail : string read; property name : string


read; property email : string read;

end;

TProc = procedure;
TOnFn = procedure( auth : TAuth ); TThenFn = procedure( r :
TLoginUser );

// Hello.JS promise
external TApi = class
public
procedure then( r : TThenFn); end;
external TAuthResponse = class
public
property access_token : variant read; end;
external THello emit hello = class
public
property services : array of variant read write; function
use( vendor : string ) : THello; procedure init( args :
TGlobHelloArgs ; arg2 :

TGlobHelloRedirect );
procedure on( arg : string ; fn : TOnFn ); function api(
what : string ):TApi; procedure login( f: TOnFn );
function getAuthResponse : TAuthResponse;

end;
var
Form1: TForm1;

var
external hello : THello;
external procedure helloinit;
external procedure hellogo;
external function eval( s : string ): object;

implementation procedure HelloEWBInit;


var

myargs : TGlobHelloArgs;
myargs2 : TGlobHelloRedirect;
begin
myargs := TGlobHelloArgs( eval( 'new Object()')); myargs2 :=
TGlobHelloRedirect( eval( 'new Object();'));

myargs.facebook := '1490242342342845';
myargs.google :=
myargs.google :=

39nj9os1muja453trrp54dt50crthhv3.apps.googleusercontent.com'
; myargs.windows := '223423432f-07d8-234233-9940-
0233daf9ecd6';
myargs2.redirect_uri :=
'https://dark.uwaterloo.ca/oauth2/redirect.html';
myargs2.scope := 'basic';

hello.init( myargs, myargs2 ); end;

procedure loginfn2( r : TLoginUser );


begin
form1.Label1.Caption := 'got a response....';
try

form1.Label1.Caption := r.name;
form1.Image1.URL := r.thumbnail;
except
form1.Label1.Caption := 'error reading response'; end;
end;

procedure loginfn( auth : TAuth );


begin
form1.Label1.Caption := 'reading....';
try
hello.use( auth.network ).api('me').then( loginfn2 ); except
form1.Label1.Caption := 'Your login failed.'; end;
end;
procedure TForm1.Script1Load(Sender: TObject); begin
inc( inited );
end;

procedure TForm1.Timer1Timer(Sender: TObject); begin


if inited = 1 then begin

inc( inited );
HelloEWBInit; // initialize the library btGoogle.Visible :=
True;
btFacebook.Visible := True; btWindows.Visible := True;

end;
end;
procedure TForm1.btGoogleClick(Sender: TObject); begin
hello.use('google').login(loginfn );
end;
procedure TForm1.btFaceBookClick(Sender: TObject); begin
hello.use('facebook').login(loginfn ); end;
procedure TForm1.btWindowsClick(Sender: TObject); begin
hello.use('windows').login(loginfn);
end;
end.

The buttons are initially set to invisible, so users do not click them before the library is
fully initialized.

Script1 loads hello.all.js, and calls the Script1 onload. I had it increment a variable, and
then that variable reaches the number of libraries we need to load (one in this case), timer1
enables the buttons. If you decide to load more libraries, repeat this step and enable the
buttons when all the libraries have loaded. There is a slight delay because Timer1 defaults
to one second intervals. The program is now pretty easy to read. Pressing a button calls the
appropriate vendor’s login with loginfn, and loginfn calls the ‘me’ function with loginfn2.
Loginfn2 updates the GUI. If case you are wondering, the reason we pass functions like
loginfn, loginfn2 to these functions is because they are called asynchronously after the
network calls complete.

OAuth2 is all about sharing. Having the client believe you are a certain identity is useful,
but often you will want to convince a server of your supposed identity. Enter the token.

OAuth2 returns a token which can be used to verify you identity and various other attribtues
about you.
unit test2;
interface
uses Webdom, WebHttp, WebCore, WebUI, WebForms, WebCtrls,
WebBtns, WebComps, WebBrwsr, WebLabels;
type

TForm1 = class(TForm)
Script1: TScript;
Script2: TScript;
btGoogle: TButton;
Image1: TImage;
Label1: TLabel;
Timer1: TTimer;
btFaceBook: TButton;
btWindows: TButton;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedure btGoogleClick(Sender: TObject); procedure
Script2Load(Sender: TObject); procedure Timer1Timer(Sender:
TObject); procedure btFaceBookClick(Sender: TObject);
procedure btWindowsClick(Sender: TObject);

private
{ Private declarations }
inited : integer;

serverrequest : TServerRequest; localtoken : string;


localnetwork : string;

procedure UploadToServer( token, network, name : string );

procedure HelloEWBInit;
procedure RequestComplete( req : TServerRequest ); procedure
loginfn2( r : TLoginUser );
procedure loginfn( auth : TAuth );

public
{ Public declarations }
end;

External TGlobHelloArgs = class


public
property facebook : string read write ;
property google : string read write;
property windows : string read write; end;

external TGlobHelloRedirect = class public


property redirect_uri : string read write ;
property scope : string read write; end;

external TAuth = class


public
property network : string read; end;
type
external TLoginUser = class
public

property thumbnail : string read; property name : string


read; property email : string read;

end;

TProc = procedure of Object;


TOnFn = procedure( auth : TAuth ) of Object; TThenFn =
procedure( r : TLoginUser ) of Object;

//promise
external TApi = class
public

procedure then( r : TThenFn);


end; external TAuthResponse = class

public
property access_token : variant read;
end;

external THello emit hello = class


public
property services : array of variant read write; function
use( vendor : string ) : THello; procedure init( args :
TGlobHelloArgs ; arg2 :

TGlobHelloRedirect );
procedure on( arg : string ; fn : TOnFn ); function api(
what : string ):TApi; procedure login( f: TOnFn );
function getAuthResponse : TAuthResponse;

end;
var
Form1: TForm1;

var
external hello : THello;
external procedure helloinit;
external procedure hellogo;
external function eval( s : string ): object;

implementation
procedure TForm1.RequestComplete( req : TServerRequest );
begin
ShowMessage( req.ResponseContent.Text ,'results');
HideProgress; end;

procedure TForm1.UploadToServer( token, network, name :


string );
begin
if not Assigned( ServerRequest ) then

ServerRequest := TServerRequest.Create(Self);
with ServerRequest do begin
method := rmGet;
url :=
'assure.php?token='+token+'&brand='+network+'&name='+name;
onComplete := RequestComplete;
execute;
end;
ShowProgress('Waiting for local server to verify with
'+network );
end;

procedure TForm1.HelloEWBInit;
var
myargs : TGlobHelloArgs;
myargs2 : TGlobHelloRedirect;
begin
myargs := TGlobHelloArgs( eval( 'new Object()'));
myargs.facebook := '149024688853845';
myargs.facebook := '149024688853845';

39nj9os1muja453trrp54dt50crthhv3.apps.googleusercontent.com'
;
myargs.windows := '24d7e37f-07d8-4703-9940-0233daf9ecd6';

myargs2 := TGlobHelloRedirect( eval( 'new Object();'));


myargs2.redirect_uri :=
'https://dark.uwaterloo.ca/oauth2/redirect.html';
myargs2.scope := 'basic,email';
hello.init( myargs, myargs2 ); end;

procedure TForm1.loginfn2( r : TLoginUser ); begin


// window.alert('got response');

Label1.Caption := 'got a response....'; try


Label1.Caption := r.name ;
Image1.URL := r.thumbnail;
UploadToServer(localtoken,localnetwork, r.name );

except
Label1.Caption := 'error reading response'; end;
end;
procedure TForm1.loginfn( auth : TAuth );
begin
Label1.Caption := 'reading....';
localnetwork := auth.network;
localtoken :=
hello.use(auth.network).getAuthResponse.access_token; try
hello.use( auth.network ).api('me').then( loginfn2 ); except
Label1.Caption := 'Your login failed.';
end;
end;

procedure TForm1.Script2Load(Sender: TObject); begin


inc( inited );
end;
procedure TForm1.Timer1Timer(Sender: TObject); begin
if inited = 2 then begin

inited := 3;
HelloEWBInit;
btGoogle.Visible := True;
btFacebook.Visible := True; btWindows.Visible := True;

end;
end;
procedure TForm1.btGoogleClick(Sender: TObject); begin
hello.use('google').login(loginfn );
end;
procedure TForm1.btFaceBookClick(Sender: TObject);
hello.use('facebook').login(loginfn ); end;
procedure TForm1.btWindowsClick(Sender: TObject); begin
hello.use('windows').login(loginfn);
end;
end.

The major changes start where we grab the token and the network in the loginfn()
localnetwork := auth.network;
localtoken :=
hello.use(auth.network).getAuthResponse.access_token;

Then in loginfn2() we call to the PHP server:


UploadToServer(localtoken,localnetwork, r.name );
This code does a basic call to our PHP server through UploadToServer()

procedure TForm1.UploadToServer( token, network, name :


string );
begin

if not Assigned( ServerRequest ) then

ServerRequest := TServerRequest.Create(Self); with


ServerRequest do begin
method := rmGet;
url :=
'assure.php?token='+token+'&brand='+network+'&name='+name;
onComplete := RequestComplete;
execute;
end;
ShowProgress('Waiting for local server to verify with
'+network );
end;

And when it’s done, RequestComplete() is called to cancel the progress message and
display the message from the PHP server.

procedure TForm1.RequestComplete( req : TServerRequest );


ShowMessage( req.ResponseContent.Text ,'results');
HideProgress;

end;

All we need now is the PHP code and some hope that everyone implements something
similar:
assure.php
<?php
// given a token, brand, check it is genuine

$server['facebook'] = "https://graph.facebook.com/me";
$server['github'] = "https://api.github.com/user";
$server['google'] = "https://www.googleapis.com/oauth2/v3/tokeninfo";
$server['windows'] = "https://apis.live.net/v5.0/me";

$token = $_REQUEST['token']; $brand = $_REQUEST['brand'];


// default to google if not specified if ( !isset( $server[ $brand ]))
$brand = 'google';
$useserver = $server[ $brand ];
$query = "$useserver?access_token=$token";
$curl = curl_init();
curl_setopt( $curl, CURLOPT_GET, 1 );
curl_setopt( $curl, CURLOPT_URL, $query );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 ); $result = curl_exec( $curl );

// print "query: $query\n";


// print $result;
$json = json_decode( $result );
$useful = "network: $brand\n";
if ( isset( $json->name )) $useful .= "name: {$json->name}\n"; if ( isset( $json->email))
$useful .= "email: {$json->email}\n"; logone( $useful );

print "A log has been made on the server";


function logone( $info )

{
if ( $f = fopen("log","a")) { fwrite( $f, "$info\n"); fclose( $f );

}
}
?>

We have three network providers:


• Google returns the Email address
• FaceBook returns the person’s name
• Windows rreturns an error

In any case, we take what we get and log it to a log file in the same subdirectory. On unix,
the file can be marked write-only so nobody can read it but those we intend.

Our OAuth adventure has been fun. There are surprisingly many things you can now do
with JavaScript, and most of them can be easily accomplished with JavaScript libraries.
Luckily EWB can interface to most JavaScript libraries with just a little effort. And you do
not need to wait for Elevate software to adopt your preferred JavaScript library, you can
usually interface to it yourself.
Most web browsers now run on multi-core CPUs, so it only makes sense that
multithreading would make its way to JavaScript, and thus to EWB through Web Workers.

Note: not all browsers support multithreading, and some do, but are still only co-operative
threading based.
The main page is the primary thread, it is the only thread that can access the GUI.
Other threads cannot:
There are no synchronization objects like in most other multithreaded environments;
instead it acts much like a network-connected service: you post messages to and from the
thread using postMessage() and receiving through onmessage().

Each thread is loaded from a JavaScript source file. It too can spawn additional threads
with some limitations. Read the Web Workers documentation for more details.

It can be painful to debug multithreading in most environments, and JavaScript makes it


even tougher because you usually cannot set breakpoints or watch your code fail. But you
can log to the browser’s debug console, so we will do that with every message we send or
receive – again something you need to remove before releasing your code.

Here is a very simple HTML/JavaScript web page which loads and talks to a thread, and
can terminate the thread when needed:

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Web Worker Example</title>

<script type="text/javascript">
function QueryableWorker (sURL, fDefListener, fOnError) {
var oInstance = this, oWorker = new Worker(sURL);

oWorker.onmessage = function (oEvent) {


var d = oEvent.data;
document.getElementById("answer").innerHTML = d.value ;

};
this.sendQuery = function ( v ) {
oWorker.postMessage({ "fn" : 0 , 'value' : v });
};
this.postMessage = function (vMsg) {
oWorker.postMessage(vMsg);
};
this.terminate = function () {
oWorker.terminate();
};
};
// your custom "queryable" worker
var oMyTask = new QueryableWorker("worker1.js");

</script>
</head>
<body>

<ul>
<li><a id="firstLink" href="javascript:oMyTask.sendQuery( 0

);">reset counter</a> : <div id='answer'></div></li> <li><a


href="javascript:oMyTask.terminate();">terminate the
worker</a></li>
</ul>
</body>
</html>

The thread is loaded with the following JavaScript:


var oMyTask = new QueryableWorker("worker1.js");

This line loads and starts running our worker1 JavaScript file. Our example will simply
count seconds and send the second count to the main GUI every second.

oWorker.onmessage is called whenever the thread sends us data, and we display it in the
DIV we created with ID ‘answer’.
Whenever we want to send a message, in this case to reset our counter, we call sendQuery
which executes:
oWorker.postMessage({ "fn" : 0 , 'value' : v });
The function (fn) will be ignored in our example, but 0 is the value the second counter is
reset to.
We can terminate the thread at any time with oWorker.terminate().
Now we will write the worker1.js file in EWB Object Pascal. Create a NONVISUAL
project called worker1. The main source should look like this:
project worker1; contains workunit;
uses Webdom, WebCore;
begin
myOnMessageInit; // initialize the library
// log something to say we are started
console.log('started');

// in this example, we call doOne, it schedules itself again


in a number of millisecond
doOne;

// just to note everythign is looking good


console.log('started queue');
end.

And we will need a unit called workunit.


unit workunit;
interface
uses Webdom, WebCore;
type
external TEvent = class
public property data : variant read write; end;
// we will share TMyShare's back and forth between processes

TMyEvent = class
public
fn : string ;
value : integer;

end;
TProc = procedure;
TProcEvent = procedure( e : TEvent );
external TConsole emit console = class public procedure log(
msg : string ); end;
external function eval( s : string ):object;
var
external console : TConsole;
external onmessage : variant; // we will fill this with our
own onmessage handler
external procedure postMessage( v : variant );
external procedure setTimeout( fn : TProc ; interval :
integer );

procedure doOne;
procedure myOnMessageInit;
procedure myOnMessage( e : TEvent );

implementation
procedure findPrimes; var
i : integer;

begin
i := 2;
repeat

if isPrime( i ) then postMessage( i );


inc( i );
until False;
end;

var
count : integer = 0;
procedure doOne; var
my : TMyEvent; begin
inc( count ); console.log('uploading ' + IntToStr( count ));
my := TMyEvent.Create; my.value := count;
postMessage( my );
setTimeout(doOne, 500 ); end;
var eventcount :integer = 0;
procedure myOnMessage( e : TEvent );
var
my : TMyEvent;

begin inc( eventcount );


console.log('got event ' + IntToStr( eventcount ));

my := TMyEvent( e.data ); count := my.value;


end;
procedure myOnMessageInit;
begin
if False then
myOnMessage( nil ); // links it in but never executes

// set the global message handler to this unit's


myOnMessage() function
onmessage := eval( 'workunit_myonmessage' );
end;
end.
There’s a lot going on in this code.

external TEvent = class


public property data : variant read write;
end;

declares the TEvent passed by the Web Workers library. All we care about is the data
property.
We will cast the TEvent.data to class

TMyEvent = class public


fn : string ; value : integer;

end;

It is TMyEvent which the two threads will pass back and forth. Fn refers to a function
name, so that they can specify different functions if you want. We won’t use fn in this
example, but it’s handy for more complex ones.

The myOnMessage procedure takes these incoming events and processes them. So if the
other side said the new value should be 100, we will blindly set our counter to that value.
We also log to the console, because otherwise debugging is terrible difficult with threads.

procedure myOnMessage( e : TEvent );


var
my : TMyEvent;

begin inc( eventcount );


console.log('got event ' + IntToStr( eventcount ));

my := TMyEvent( e.data ); count := my.value;


end;

The doOne procedure increments our lucky counter, logs it to the console and posts the
updated counter to the main thread. It also schedules itself to run again in 500 milliseconds.

procedure doOne; var


my : TMyEvent; begin
inc( count );
console.log('uploading ' + IntToStr( count ));
my := TMyEvent.Create; my.value := count;
postMessage( my ); setTimeout(doOne, 500 ); end;
The workhorse is a bit weird:
procedure myOnMessageInit;
begin
if False then
myOnMessage( nil ); // links it in but never executes

// set the global message handler to this unit's //


myOnMessage function
onmessage := eval( 'workunit_myonmessage' );

end;

The lines:
if False then
myOnMessage( nil ); // links it in but never executes

never actually run myOnMessage, but by writing this condition we tell the linker to copy
the myOnMessage() function into the output JavaScript. Otherwise the linker optimization
will skip it because it is not otherwise referenced by anything.

We need to have a global function called onmessage(), and the code:


onmessage := eval( 'workunit_myonmessage' );

creates that global variable and sets it to our function. Note that the function name is
workunit_myonmessage(), which EWB creates because this unit name is workunit and the
function within the unit is myonmessage (no capitalization).

Compile your code and make one last critical change, edit the resulting worker1.js into an
editor and add the last line:
worker1_load();

Now load the master web page (main.html) and be amazed. The counter will increment
frequently, and you can reset the counter or kill the counter by clicking on the labels.

Note, the worker1.html file is not used and can be deleted.

Consider the implications of what you have just seen. It is possible to write a program in
EWB Object Pascal and teach any JavaScript application to load and exchange data with
that program in real-time, let alone with multithreading with only a few additional lines of
JavaScript.

EWB is preferable to JavaScript for many of these threading situations because the
compiler should find most of your mistakes and you will spend less time debugging.

Create a Visual EWB project, we’ll call it mainewb, and a main form in a unit called
workmain.
unit workmain;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebLabels;
type

TForm1 = class(TForm)
btReset: TButton;
btTerminate: TButton;
Label1: TLabel;
procedure Form1Show(Sender: TObject); procedure
btTerminateClick(Sender: TObject); procedure
btResetClick(Sender: TObject);

private
{ Private declarations } worker : TQueryableWorker;

public
{ Public declarations }
end;

TMyEvent = class public


fn : string ;
value : integer; end;

external TEvent = class


public
property data : TMyEvent read; end;
TEventProc = procedure ( e : TEvent ) of object;
external TWorker emit Worker = class
public
property onmessage : TEventProc read write; procedure
postMessage( e : TMyEvent ); procedure terminate;
end;
TQueryableWorker = class public
oWorker : TWorker;

oInstance : variant;
procedure init;
procedure onMessageHandler( msg : TEvent ); procedure
postMessage( msg : TMyEvent ); procedure terminate;

end;
var
Form1: TForm1; external function eval( s : string ):object;
implementation
procedure TQueryableWorker.onMessageHandler( msg : TEvent );
begin
Form1.Label1.Caption := IntToStr(msg.data.value); end;

procedure TQueryableWorker.init;
begin
oWorker := TWorker( eval('new Worker("worker1.js");'));
oWorker.onmessage := onMessageHandler;
end;

procedure TQueryableWorker.postMessage( msg : TMyEvent );


begin
oWorker.postMessage( msg );
end;

procedure TQueryableWorker.terminate; begin


oWorker.terminate;
oWorker := Nil;
end;

procedure TForm1.Form1Show(Sender: TObject); begin


worker := TQueryableWorker.create;
worker.Init;
end;

procedure TForm1.btTerminateClick(Sender: TObject); begin


worker.terminate;
end;
procedure TForm1.btResetClick(Sender: TObject); var
my : TMyEvent;

begin
my := TMyEvent.Create;
my.fn := 'whatever';
my.value := 0;
worker.postmessage( my ); my.Free;

end;
end.

The only confusing things are that onMessageHandler() receives a TEvent which it must
convert into a TMyEvent, and the capitalization of onmessage is different from the
capitalization of postMessage… it’s just not consistent.

A very real use of threads is when you need to do scientific calculations or Internet
communications and want to offload them to other CPU cores to leave your GUI fast and
responsive. Again, not all browsers will do that, and some users still have single-core
machines out there. But the option exists for you to use, and as it is a standard, it is
becoming quite common. Check the web for latest compatibility news of Web Workers.
And EWB’s Object Pascal is perfect for doing complex calculations.
It is possible to write applications that will run natively on cell phones, tablets, set-top TV
boxes and other devices using EWB. Unike mere web pages, these applications will have
access to the built-in compass, GPS, gyrometer, camera, local file storage, etc.

The technology blending HTML5/JavaScript with phone/tablet platforms was originally


called PhoneGAP, though now it is called Apache Cordova. A web search will find lots of
information.

What is spectacular is that native PhoneGap/Codova modules are available which can do
all sorts of things as add-in libraries. They are written in the language native to the device,
and they present a JavaScript-callable interface. As we saw earlier, if it’s accessible to
JavaScript: you can probably access it from EWB.

You have choices about how you convert these web pages into mobile apps: you can
download the free Cordova toolkit, and android toolkit, and Java toolkit, and IOS toolkit
(in which case you better be on a Mac), etc. Or you can spend $9.99 on an Adobe
PhoneGapBuild monthly subscription where they do all the work maintaining the libraries.
I’ve done it both ways in the past, using the PhoneGapBuild when I was lazy, and local
verions when I was cheap.

If you decide to do it locally, installation takes about an hour of downloading with a good
network connection. I won’t copy the instructions here because they seem to change every
month. Refer to the web for details. You’re probably wondering why I am discussing
distribution before even writing the app. Well, to test and share an Apple IOS app, you
have to get digital signatures from Apple. And everyone who tests your app has to have
their device entered into a list you maintain, up to 100 test users per app, no more. It’s all
quite complicated.

Testing Android apps is much easier. You will just generate an APK file, which is really
just a ZIP file, with a certain layout. Any android device can test it by downloading your
app from a web page.

I’m using cordova on the local machine for this description.


Your application needs to be saved as index.html and associated JavaScript file. You can
include JavaScript libraries too.
Once you have loaded all the prerequisites, you issue the following commands from the
Shell.

mkdir cordova
cd cordova
cordova create hello
cd hello
cordova platform add android cordova platform add camera

Copy in your HTML and JavaScript files to the www subdirectory that you find.
cordova build android

You can copy almost any EWB project to the index.html its associated JavaScript file. In
fact, you can have an EWB project named: HappyDancer.html and HappyDancer.js. Just
rename the HTML file to index.html and keep HappyDancer.js, and it will work.

The project uses config.xml in the www subdirectory – cordova create hello put
it there. By default, the project uses the default icon. You can edit it and add your own
icons, etc. You really should edit the author field too. Read the whole file, but I recommend
keeping a backup in case you accidentally mess up the fields.

For our examples we will leave the config.xml file in its default state.

As we just discussed, you can place any html5/JavaScript code in index.* and it will make
an installable application. But what is really cool is that you can do more: you can access
hardware and software services on the device using Cordova/PhoneGap extensions.
Accessing those libraries and services is the challenge we shall solve.

Our first example will be a Camera will be able to take pictures and display them in an
TImage. You could also extend the program to upload images, keep inventory records, etc.
unit camera1;
interface
uses WebDom, WebCore, WebUI, WebForms, WebCtrls, WebComps,
WebBtns, WebBrwsr, WebSizer,
WebCtnrs;
type

TForm1 = class(TForm)
Script1: TScript;
Button1: TButton;
ScrollPanel1: TScrollPanel;
Image1: TImage;
procedure Script1Error(Sender: TObject);
procedure Form1Create(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Image1Load(Sender: TObject);

private
{ Private declarations }
procedure onDeviceReady;
procedure onCameraSuccess( s : string ); procedure
onCameraFail( s : string );

public
{ Public declarations }
end;

TCameraProc = procedure( s : string ) of object;

external TCameraOptions = class


public
property quality: integer read write;
property destinationType: integer read write;
// Camera.DestinationType.FILE_URI
end;

external TCameraDestinationType = class public property


FILE_URI : integer read ;
end;

external TCamera = class


public property DestinationType :

TCameraDestinationType read; procedure getPicture(


onSuccess : TCameraProc; onFail : TCameraProc ; settings :
TCameraOptions );

end;
external TNavigator2 = class (TNavigator) public property
camera : TCamera read write;
end;

TProc = procedure of object;


external TCordovaDocument = class (TDocument)
public
procedure addEventListener( code : string; proc : TProc;
flag : boolean );
end;

var
Form1: TForm1;
external function eval( s:string) : object; implementation

procedure TForm1.Image1Load(Sender: TObject); begin


Image1.Width := Image1.ActualWidth;
Image1.Height := Image1.ActualHeight; end;

procedure TForm1.Script1Error(Sender: TObject); begin


ShowMessage('cordova did not load','ERROR'); end;
procedure TForm1.onDeviceReady; begin
Button1.Visible := True; end;
procedure TForm1.Form1Create(Sender: TObject); begin

TCordovaDocument(window.document).addEventListener(
'deviceready', onDeviceReady, false);
Width := window.Screen.Width;
Height := window.Screen.Height;
end;

procedure TForm1.onCameraSuccess( s : string ); begin


Image1.url := s;
end;
procedure TForm1.onCameraFail( s : string ); begin
ShowMessage( s, 'Error');
end;
procedure TForm1.Button1Click(Sender: TObject); var
options : TCameraOptions;

begin
options := TCameraOptions(eval( 'new
Object()'));//TCameraOptions.Create;
options.quality := 20;
options.destinationType := 1; ///
Camera.DestinationType.FILE_URI;

TNavigator2(window.Navigator).camera.getPicture(onCameraSuccess,
onCameraFail, options );
end;
end.

You will need TScrollBox set with both scrollbars enabled, and its Layout set to cover the
form. Also you need a TScript we will explain in a second, a Button1 set to Visible:=
False and its caption set to ‘Take Photo’. And we need a TImage to hold the picture.

First, onCreate is called. It registers a callback which occurs sometime later when
PhoneGap is loaded and ready.

TCordovaDocument(window.document).addEventListener(
'deviceready', onDeviceReady, false); Width :=
window.Screen.Width;
Height := window.Screen.Height;

It also adjusts the window dimensions to the Screen’s dimensions.

Then we have a TScript (not shown) which loads the URL cordova.js from the local
directory. Which cordova.js, there is none? At compile time, the Cordova system will
automatically place the correct JavaScript for this target OS in the local target
subdirectory.

The TScript onError is set to


procedure TForm1.Script1Error(Sender: TObject); begin
ShowMessage('cordova did not load','ERROR'); end;
and we ignore onSuccess.

Cordova fires an event called deviceready when it’s up and ready to do things. And
remember, we scheduled onDeviceReady to be called in onCreate. So it get’s called.

procedure TForm1.onDeviceReady; begin


Button1.Visible := True; end;

As you can see, it enables the user button to take a photo. Until now we had the button’s
Visible set to false so that they couldn’t take a picture before the system was ready.

Here’s the magic in this case:


procedure TForm1.Button1Click(Sender: TObject); var
options : TCameraOptions;
begin
options := TCameraOptions(
eval( 'new Object()'));//TCameraOptions.Create;
options.quality := 20;
options.destinationType := 1;
/// Camera.DestinationType.FILE_URI;
TNavigator2(window.Navigator).camera.getPicture(
onCameraSuccess, onCameraFail, options ); end;

Options is a class of options. We create the class (see chapter on JavaScript if it looks
strange). Then we call what JavaScript knows as
window.Navigator.camera.getPicture(). PhoneGap/Cordova has added a camera.getPicture
to the Navigator object. EWB is much more sensible than JavaScript-do-whatever-you-
want, so we must cast window.Navigator to a class we defined as Navigator2 above, and
TCamera

external TNavigator2 = class (TNavigator) public property


camera : TCamera read write;
end;

and
external TCamera = class
public property

DestinationType : TCameraDestinationType read; procedure


getPicture(
onSuccess : TCameraProc; onFail : TCameraProc ; settings :
TCameraOptions );

end;

If the picture is taken, onCameraSuccess is called, and it copies the data url to the TImage
url property.
procedure TForm1.onCameraSuccess( s : string ); begin
Image1.url := s;
end;
Finally, when the image loads, we tell the system to expand the TImage to the size of the
Photograph by making space inside the TScrollPanel.

procedure TForm1.Image1Load(Sender: TObject); begin


Image1.Width := Image1.ActualWidth;
Image1.Height := Image1.ActualHeight; end;
If you try running the resulting program on your computer in EWB, it will give you an error
because PhoneGap isn’t loaded. But you can compile it for Windows, Linux or OS/X and
make a desktop app.

Use cordova platform list

This will list supported platforms.


Cordova platform add osx --save Cordova build osx

This creates a HelloWorld.app which can run under OS/X. Unfortunately, it does not
appear to support the camera, but at least it does not give errors. The screen looks like this
In all honesty, EWB added very little to this partiular project. It would have been faster to
code it in JavaScript. But where EWBl pays off is when you construct something more
heavy-duty, because that’s where EWB shines.

Our next two projects give you a taste of that superiority.

But first, let’s make this example more exciting. What good is taking an image until you can
upload it.
This example extends the previous one. It takes a picture then lets you upload the file if you
wish by pressing the Upload Button2.

The things to note are:


• We copy the Image1 to a Paint1 so we can copy its canvas
• We use TScript1 to load cordova.js, and TScript2 to load canvastoblob.js
• We use EWB’s ConvertToDataUrl to convert the Paint1 to a PNG file in

Base64 encoding
• We use TServerRequest to upload the data with a POST
This builds on previous examples from other chapters.

What is not done in any previous chapters is that we set the host name of the URL. This
would break CORS on web pages, but it’s perfectly safe on an application.

unit camera1;
interface
uses WebDom, WebHTTP, WebCore, WebUI, WebForms, WebCtrls,
WebComps, WebBtns, WebBrwsr, WebSizer,
WebCtnrs, WebPaint;
type

TForm1 = class(TForm)
Script1: TScript;
Button1: TButton;
ScrollPanel1: TScrollPanel;
Image1: TImage;
Button2: TButton;
Script2: TScript;
Paint1: TPaint;
procedure Script1Error(Sender: TObject); procedure
Form1Create(Sender: TObject); procedure Button1Click(Sender:
TObject); procedure Image1Load(Sender: TObject); procedure
Button2Click(Sender: TObject);
private
{ Private declarations }
strimg : string;
HttpImageRequest : TServerRequest; procedure onDeviceReady;
procedure onCameraSuccess( s : string ); procedure
onCameraFail( s : string ); procedure UploadPicture;
procedure CopyPictureToImage;

procedure UploadPicture2( blob : string ); procedure


UploadComplete( sr : TServerRequest ); public
{ Public declarations }
end;
TCameraProc = procedure( s : string ) of object;

external TCameraOptions = class


public
property quality: integer read write;
property destinationType: integer read write; //
Camera.DestinationType.FILE_URI
end;

external TCameraDestinationType = class public property


FILE_URI : integer read ;
end;

external TCamera = class


public property DestinationType : TCameraDestinationType
read;

procedure getPicture(onSuccess : TCameraProc; onFail :


TCameraProc ;
settings : TCameraOptions );

end;

external TNavigator2 = class (TNavigator) public property


camera : TCamera read write;
end;

TProc = procedure of object;


external TCordovaDocument = class (TDocument) public
procedure addEventListener( code : string; proc : TProc;
flag : boolean );
end;
TBlobSave = procedure( blob : object ) of object; external
Tcanvastoblob = class (THTMLElement) public
procedure toBlob( blobsavefn : TBlobSave ); end;

var
Form1: TForm1;
external function eval( s:string) : object;

implementation

procedure TForm1.Image1Load(Sender: TObject); begin


Image1.Width := Image1.ActualWidth;
Image1.Height := Image1.ActualHeight;
Button2.Visible := True;
end;

procedure TForm1.Script1Error(Sender: TObject); begin


ShowMessage('cordova did not load','ERROR'); end;
procedure TForm1.onDeviceReady; begin
Button1.Visible := True; end;
procedure TForm1.Form1Create(Sender: TObject); begin

TCordovaDocument(window.document).addEventListener(
'deviceready', onDeviceReady, false);
Width := window.Screen.Width;
Height := window.Screen.Height;
end;

procedure TForm1.onCameraSuccess( s : string ); begin


strimg := s; // save for later
Image1.url := s;
end;

procedure TForm1.onCameraFail( s : string ); begin


ShowMessage( s, 'Error');
end;
procedure TForm1.Button1Click(Sender: TObject); var
options : TCameraOptions;

begin
options := TCameraOptions(eval( 'new
Object()'));//TCameraOptions.Create;
options.quality := 20;
options.destinationType := 1; ///
Camera.DestinationType.FILE_URI;

TNavigator2(window.Navigator).camera.getPicture(
onCameraSuccess, onCameraFail, options ); end;
procedure TForm1.UploadComplete( sr : TServerRequest );
begin
if (sr.StatusCode <> HTTP_OK) then

ShowMessage('Error: '+sr.ResponseContent.Text, 'Results')


else
ShowMessage('Done','Results');
end;

const
host = 'https://erickengelke.com/temp'; theurl =
'/PostBase64PNG.php';

procedure TForm1.CopyPictureToImage; var


image : THTMLElement;
paint : THTMLElement;
context : THTMLCanvasRenderingContext2D; begin
Image1.clientID := 'myimage';
Paint1.Width := Image1.Width;
Paint1.Height := Image1.Height;

image := THTMLElement(
window.document.getElementById('myimage')); image :=
THTMLElement( image.childNodes[0] );
Paint1.clientID := 'mycanvas';
paint := THTMLElement(

window.document.getElementById('mycanvas')); context :=
THTMLCanvasElement( Paint ).getContext('2d'); eval(
'context.drawImage(image, 1,1);');

end;
procedure TForm1.UploadPicture;
var
s : string;

begin
// similar to the filesave example earlier in the book
CopyPictureToImage;
// s will be a Base64 version of the image
s := Paint1.Canvas.ConvertToDataUrl( 'image/png', 100 );
UploadPicture2( s );

end;

procedure TForm1.UploadPicture2( blob : string );


begin
//The script will convert the Base64 into a PNG, and save it
if not Assigned( HttpImageRequest ) then
HttpImageRequest := TServerRequest.Create(self);

with HttpImageRequest do
begin
method := rmPost;
onComplete := UploadComplete;
RequestHeaders.Clear;
RequestContent.Clear;

url := host + theurl;


//need to specify the content type
RequestHeaders.add(

'Content-Type: application/x-www-form-urlencoded');
RequestContent.add('imagedata=' + blob );
RequestHeaders.add('Content-Length: ' +

IntToStr( Length( RequestContent[0]))); execute;


end;

end;
procedure TForm1.Button2Click(Sender: TObject); begin

UploadPicture;
end;
end.

Of course you will need a script on the server to accept the uploaded image.
PostBase64PNG.php
<?php
$imageFilename = 'junk';
$img = $_POST['imagedata'];
file_put_contents( "$imageFilename.txt", $img );

$img = str_replace('data:image/png;base64,', '', $img); $img


= str_replace(' ', '+', $img);
$data = base64_decode($img);
file_put_contents($imageFilename . '.png', $data); ?>

This upload PHP script tries to save the file in the local subdirectory, you will want to
change that and also add code to ensure you trust this user to upload. The Map and GPS
example will create a working GPS system. It will get updates from the GPS every few
seconds and display our changing position on a Google Map. Elevate software has taken
the hassle out of Google Maps, and so this program is surprisingly short for what it
accomplishes.
unit geo1;
interface
uses Webdom, WebCore, WebUI, WebForms, WebCtrls, WebBtns,
WebLabels, WebMaps, WebComps;
type

TFormGeo = class(TForm)
Button1: TButton;
Label1: TLabel;
Label2: TLabel;
Map1: TMap;
Button2: TButton;
Script1: TScript;
Label3: TLabel;
procedure Button1Click(Sender: TObject); procedure
Button2Click(Sender: TObject); procedure
Script1Error(Sender: TObject); procedure
FormGeoCreate(Sender: TObject);

private
locallocation : integer;
watch : string;
procedure onSuccess( res : TGeoResults ); public procedure
onError( error : TGeoError ); procedure onDeviceReady;

end;
// which options can we supply to GPS?
external TGeoOptions = class
public

property maximumAge : integer read write;// = 3000; property


timeout : integer read write; // = 5000; property
enableHighAccuracy:boolean read write; // true

end;

// function type called when there is a successful read


TGeoSuccessFn = procedure ( results : TGeoResults ) of
object; TGeoErrorFn = procedure ( error : TGeoError ) of
object;

// results returned give these coordinates external


TGeoCoords = class
public property latitude : double read;

property longitude : double read;


property altitude : double read;
property accuracy : double read;
property altitudeAccuracy : double read; property heading:
double read;
property speed : double read;
end;
// results returned from GPS
external TGeoResults = class
public property coords : TGeoCoords read; property timestamp
: variant read; end;

external TGeoError = class


public property code : string read; property message :
string read; end;
external TGeoLocation = class
public
function watchPosition(

onGeoSuccess : TGeoSuccessFn ;
onGeoError : TGeoErrorFn;
options : TGeoOptions ):string;

procedure clearWatch( watchID : string ); // from a previous


watchPosition call
procedure getCurrentPosition(
onGeoSuccess : TGeoSuccessFn;
onGeoError : TGeoErrorFn; options : TGeoOptions ); end;

external TGeoNavigator = class (TNavigator) public


property geolocation : TGeoLocation read; end;
TProc = procedure of object;
external TCordovaDocument = class (TDocument) public
procedure addEventListener(
code : string; proc : TProc; flag : boolean ); end;

var
FormGeo: TFormGeo;
external function eval( s : string ):object;

implementation
procedure TFormGeo.Script1Error(Sender: TObject); begin
ShowMessage('cordova did not load','ERROR'); end;
procedure TFormGeo.onDeviceReady; begin
Button1.Visible := True;
end;
procedure TFormGeo.onSuccess( res : TGeoResults ); begin
Label1.Caption :=
'Latitude: ' + FloatToStr( res.coords.latitude );
Label2.Caption :=
'Longitude: ' + FloatToStr( res.coords.longitude );
Map1.Locations.Add;
with Map1.Locations[locallocation] do begin
Latitude := res.coords.latitude;
Longitude := res.coords.longitude;
showmarker := True;
end;
inc( locallocation );
end;

procedure TFormGeo.onError( error : TGeoError ); begin


Label1.Caption := 'GPS:' + error.message;
Label2.Caption := '';
end;

procedure TFormGeo.Button1Click(Sender: TObject); var


options : TGeoOptions;

begin
options := TGeoOptions( eval('new Object();')); options.
maximumAge := 3000;
options.timeout := 5000;
options.enableHighAccuracy := true;

watch :=
TGeoNavigator(window.navigator).geolocation.watchPosition(
onSuccess, onError , options );
Button1.Enabled := False; Button2.Enabled := True; end;
procedure TFormGeo.Button2Click(Sender: TObject); begin
if watch <> '' then begin

TGeoNavigator(window.navigator).geolocation.clearWatch(
watch );
watch := '';
Button2.Enabled := False;
Button1.Enabled := True;
end;
end;

procedure TFormGeo.FormGeoCreate(Sender: TObject); begin


TCordovaDocument(window.document).addEventListener(
'deviceready', onDeviceReady, false);
Width := window.Screen.Width;
Height := window.Screen.Height;
Button2.Enabled := False;
end;

end.

The OnCreate event is almost identical to our last project, so it doesn’t need a explanation
except to say it disables the Disable GPS button.
When we get the deviceready, we enable the Start GPS button. So far so simple.

When the user clicks Button1, we prepare to call watchPosition() with two event handlers
and some options. This function tells the GPS to upcall our event handlers when changes
happen to the GPS position, or errors results such as when we are out of range of the
satellites. It will update the location usually every 3000 ms, or timeout after 5000ms of no
position recording.

Options.enableHighAccuracy means to use the GPS and not the cell towers location.
Higher accuracy results from this decision, but it also drains the batteries more quickly.

procedure TFormGeo.Button1Click(Sender: TObject); var


options : TGeoOptions;

begin
options := TGeoOptions( eval('new Object();')); options.
maximumAge := 3000;
options.timeout := 5000;
options.enableHighAccuracy := true;

watch :=
TGeoNavigator(window.navigator).geolocation.watchPosition(
onSuccess, onError , options );
Button1.Enabled := False; Button2.Enabled := True; end;

The watchPosition returns a string token which we will use again when we want to turn off
the GPS.
procedure TFormGeo.Button2Click(Sender: TObject); begin
if watch <> '' then begin

TGeoNavigator(window.navigator).geolocation.clearWatch(
watch );
watch := '';

Button2.Enabled := False;
Button1.Enabled := True;
end;
end;

And an important part of this project is the onSuccess handler called by the GPS system:
procedure TFormGeo.onSuccess( res : TGeoResults ); begin

Label1.Caption :=
'Latitude: ' + FloatToStr( res.coords.latitude );
Label2.Caption :=
'Longitude: ' + FloatToStr( res.coords.longitude );
Map1.Locations.Add;
with Map1.Locations[locallocation] do begin Latitude :=
res.coords.latitude;
Longitude := res.coords.longitude;
showmarker := True; end;
inc( locallocation ); end;

It displays the latitude and longitude and adds a marker to the map. Markers are an indexed
array, so we need to inc( locallocation) to record the next location to a new marker.

You could use an upcall to Google services to convert the GPS coordinates to an
approximate street location. My experience is that it’s usually accurate to the street name
and approximate house, but not necessarily the exact house.

Note that Google starts charging for or blocking access to location awareness nformation
when you use it too much. So be careful to limit requests to a reasonable number per day
where reasonable is defined by Google.

Look at the Apache Cordova web site and you will find extensions that do all sorts of
things. They can make your program
run in the background
accept asynchronous notifications like facebook does
access files
access an SQL-lite based database system
and much more

All the libraries use the same sort of calls as we did in our two examples, so there is no
excuse for not persuing these options.
The next steps are generally to register for Android, IOS and other developer keys and
signatures. This will cost some money but it is the frustration with endless web pages
which will probably be the hardest part. Also, Apple is picky about what it will allow on
its store, you need to meet their user interface guidelines and various other criteria.

Not every developer will feel a need to make a mobile app, but with EWB it is relatively
easy to accomplish, and definitely no harder than competing environments.

Mormot is an enormous database ecosystem. It contains, among other things:


• Server compatibility between Win32, Win64 and Linux 32 and 64
• embedded web server capable of 50,000 concurrent users (Win32, Win64

even more)
• ability to use Microsoft embedded / IIS server
• connectivity to every major brand of SQL server and NOSQL server

o including MySQL, Oracle DB, PostgreSQL, ODBC, and others


• high speed native SQLite3 server
• caching server
• an Object Relational Mapping
• Web MVC
• Service Oriented Architecture
• Password management
• And much more

Personal Aside
It was while researching my unfinished Mormot book that I got sidetracked by discovering
EWB, eventually leading me to write this book. My first Mormot book will likely be my
next.

This short chapter will not introduce Mormot in detail. It’s an environment much more
complex than EWB. But we will create three examples which work with programs that
ship as Mormot’s own server samples.

Mormot can be used as an Object Relational Mapping or ORM, which means you do not
have to write a line of SQL, and using it this way offers some benefits. But if you want to
write SQL, that’s fine too, you just bypass the ORM layer.
The following chapter describes my EWBMormot interface library and applications that
use it.

The first step in your EWB web page unit is to include both EWBmormot and WebHTTP.
Then there are two important classes for all Mormot EWB applications:
TMormotConnection class which communicates with the Database/Web server
TMormotTable class from which classes you create will descend.

In all Momot applications on all platforms you utilize a class that knows how to
communicate with the database/web server: TMormotConnection, somewhat akin to the
TDatabase of EWB.

Also, you define data objects as classes that descend from an ancestor Mormotrelated class
which knows how to express them to the aforementioned connection class.

For our first two examples, we will use the Project 18 database server that comes with
Mormot. Project18 is a password-free database that lets you create, read, update and
delete Fish names.

In all EWBMormot applications you start by defining the class that describes the database
object you will connect to.
In our case, it’s TFish that describes Fish.

TFish = class (TMormotTable ) private


fID : integer;
fName : string;
ftimeD : string;
fQuestion : string;
published
property ID : integer read fID write fID;
property Name : string read fName write fName; property
timeD : string read ftimeD write ftimeD; property Question :
string read fQuestion write fQuestion; public
//
end;

Noticed that it descends from TMormotTable rather than TPersistent. Otherwise it should
be quite familiar.
Next, in our form we define instances of the Web/Database and a Fish.

private
{ Private declarations } fish : TFish;
mormot : TMormotConnection;

To give us an easy output log, we place a MultiLineEdit box on the screen.


In the OnShow event for the form we call
mormot := TMormotConnection.Create;
Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord', self);
fish := TFish.Create; fish.Name := 'Bobby';
MultiLineEdit1.Lines.Add('Default ID for fish is ' +
IntToStr( fish.Id ));
As you would expect, the ID property for Fish:Bobby is 0 because we have not initialized
that value.
Next we add an event handler and call Add
mormot.PostOp := PostAddFish; mormot.Add( fish, True );
The event handler has a scary parameter list, but we can ignore most of it and look for a
successful addition.

procedure TForm1.PostAddFish( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
begin

if statuscode < 300 then


MultiLineEdit1.Lines.Add('Updated, fish has ID: '
+ IntTOStr( fish.id ))
else MultiLineEdit1.Lines.Add('Error adding fish');
end;

The output to this simple program is:


If you know the ID for a Fish you can query it directly. In the PostAddFish routine add:
mormot.PostOp := PostGet;
mormot.Get( fish, 4 ); // get record # 4
and you will need a PostGet routine with that.

procedure TForm1.PostGet( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
begin

MultiLineEdit1.Lines.Add('Fish ['+ IntToStr(Fish.ID)+'] = '+


Fish.Name );
end;

The complete program follows.

unit sample2a;
// Demonstrates typical Mormot style of CRUD on tables
interface

uses ewbmormot, webhttp, WebCore, WebUI, WebForms, WebCtrls,


WebEdits;
type

TForm1 = class(TForm)
MultiLineEdit1: TMultiLineEdit;
procedure Form1Show(Sender: TObject);

private
{ Private declarations } fish : TFish;
mormot : TMormotConnection;
procedure PostAddFish( sr : TServerRequest;

method : TRequestMethod ;
statuscode : integer; rh : TStrings; Response : TStrings );

procedure PostGet( sr : TServerRequest; method :


TRequestMethod ;
statuscode : integer;
rh : TStrings;
Response : TStrings );
public

{ Public declarations } end;

TFish = class (TMormotTable )


private
fID : integer;
fName : string;
ftimeD : string;
fQuestion : string;
published
property ID : integer read fID write fID;
property Name : string read fName write fName; property
timeD : string read ftimeD write ftimeD; property Question :
string read fQuestion write fQuestion; public
//
end;

var
Form1: TForm1;
implementation

procedure TForm1.PostAddFish( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
begin

if statuscode < 300 then


MultiLineEdit1.Lines.Add('Updated, fish has ID: '
+ IntTOStr( fish.id ))
else MultiLineEdit1.Lines.Add('Error adding fish');
mormot.PostOp := PostGet;
mormot.Get( fish, 4 ); // get record # 4 end;

procedure TForm1.PostGet( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
begin

//
MultiLineEdit1.Lines.Add('Fish ['+ IntToStr(Fish.ID)+'] = '
+ Fish.Name );
end;
procedure TForm1.Form1Show(Sender: TObject);
begin

//
mormot := TMormotConnection.Create;
Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord',

self);
fish := TFish.Create; fish.Name := 'Bobby';
MultiLineEdit1.Lines.Add('Default ID for fish is ' +
IntToStr( fish.Id ));

mormot.PostOp := PostAddFish; mormot.Add( fish, True ); end;


end.

Our next example merges Mormot with TDataSet, so you can use EWB DataSet-enabled
controls with Mormot-proven database backends.
Specifically, we will use TGrid to display a number of fish in a master-detail view, with
TEdits and other controls showing the details.

For this example we need a screen layout that looks like this:
It also has a invisible TDataSet1 which is connected to the edit boxes, the date box, and the
TGrid.

I used Layout to make the TGrid shrink or swell to the size of available space. You can
learn more about that in our chapter on Responsive Design. The other controls were not
made responsive in this simple example.

We declare our private classes in the TForm just as before


fish : TFish;
mormot : TMormotConnection;
And we declare a new OnPopulate method which will connect the DataSet1
procedure OnPopulate( s : string );

Then we use TFish exactly as defined in the first example.


Following the definitions, we have an OnCreate event which starts up the database.

procedure TForm1.DataSet1Show(Sender: TObject); begin


fish := TFish.Create;
mormot := TMormotConnection.Create;
Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord', self);

// new code next


Mormot.Populate := OnPopulate; Mormot.GetAllData( fish );
end

Instead of Get, we call GetAllData, which downloads the data into our DataSet1 and it
flows beautifully into our table.
But to get it into the dataset, we have to implement a very short OnPopulate helper:

procedure TForm1.OnPopulate( s : string ); begin


DataSet1.Open;
DataSet1.LoadRows( s );
end;

Much of the rest of the code couldn’t be simpler. For example, when a user pressed
btDelete, our code reads the ID number from the TDataSet cursor and deletes it.

procedure TForm1.btDeleteClick(Sender: TObject); begin


Mormot.Delete( fish, DataSet1.Columns['id'].asString ); end;

The Create and Update functions are both wrapped into the Save button. First, pressing
Create or Update will set a flag to indicate if we are creating new or updating existing
data, that flag is stored in the insert Boolean.

procedure TForm1.btSaveClick(Sender: TObject);


begin
if DataSet1.Modified then begin

fish.LoadFromDataset( DataSet1 );
if mormot.Inserting then
Mormot.Post(fish )

else

Mormot.Put( fish,DataSet1.Columns['id'].asString ); end;


DataSet1.Cancel;
mormot.inserting := False;

end

fish.LoadFromDataSet(..) loads the TFish structure with the values in the current dataset.
If it’s new, we Post( fish), which is the RESTful way to send a new object, otherwise we
Put( fish , and it’s ID ) to update the existing object.
The entire program is about 120 lines, including 65 lines of Interface.
unit sample1a;
interface
uses WebHTTP, WebCore, WebUI, WebForms, WebCtrls, WebCtnrs,
WebGrids, WebLabels, WebData,
WebBtns, WebEdits, ewbmormot;
type

TForm1 = class(TForm)
BasicPanel1: TBasicPanel; BasicPanel2: TBasicPanel; Label1:
TLabel;
Grid1: TGrid;
DataSet1: TDataSet;
GridColumn1: TGridColumn; GridColumn2: TGridColumn;
GridColumn3: TGridColumn; GridColumn4: TGridColumn; Label2:
TLabel;
Label3: TLabel;
Label4: TLabel;
Edit1: TEdit;
DateEditComboBox1: TDateEditComboBox; Edit2: TEdit;
btCreate: TButton;
btUpdate: TButton;
btDelete: TButton;
btRefresh: TButton;
Label5: TLabel;
btSave: TButton;
Label6: TLabel;
procedure DataSet1Show(Sender: TObject); procedure
btCreateClick(Sender: TObject); procedure
btUpdateClick(Sender: TObject); procedure
btDeleteClick(Sender: TObject); procedure
btRefreshClick(Sender: TObject); procedure
btSaveClick(Sender: TObject);

private
{ Private declarations }
fish : TFish;
mormot : TMormotConnection;

public
{ Public declarations }
procedure OnPopulate( s : string );

end;

TFish = class (TMormotTable )


private
fID : integer;
fName : string;
ftimeD : string;
fQuestion : string;
published
property ID : integer read fID write fID;
property Name : string read fName write fName; property
timeD : string read ftimeD write ftimeD; property Question :
string read fQuestion write fQuestion; end;

var
Form1: TForm1;

implementation procedure TForm1.DataSet1Show(Sender:


TObject); begin

fish := TFish.Create;
mormot := TMormotConnection.Create;

Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord', self);
Mormot.Populate := OnPopulate;
Mormot.GetAllData( fish );
end;

procedure TForm1.btCreateClick(Sender: TObject); begin


DataSet1.Insert;
mormot.inserting := True;
DateEditComboBox1.SelectedDate := now; end;

procedure TForm1.btUpdateClick(Sender: TObject); begin


mormot.Inserting := False;
DataSet1.Update;
end;

procedure TForm1.btDeleteClick(Sender: TObject); begin


Mormot.Delete( fish, DataSet1.Columns['id'].asString ); end;

procedure TForm1.btRefreshClick(Sender: TObject); begin


Mormot.Populate := OnPopulate;
Mormot.GetAllData( fish );
end;

procedure TForm1.btSaveClick(Sender: TObject);


begin
if DataSet1.Modified then begin

fish.LoadFromDataset( DataSet1 );
if mormot.Inserting then
Mormot.Post(fish )

else

Mormot.Put( fish,DataSet1.Columns['id'].asString ); end;


DataSet1.Cancel;
mormot.inserting := False;

end;

procedure TForm1.OnPopulate( s : string ); begin


DataSet1.Open;
DataSet1.LoadRows( s );
end;

end.

The two preceding examples demonstrate Mormot Server’s REST API. Mormot also
supports interfaces for remote procedure calls (RPC). With the RPC you can send data to
the server, implement any server functionality, and return results that may but need not be
database results.

RPCs are great for implementing business logic, so the logic is defined and carefully
controlled by the server and not the possibly hacked web client.

For our simple example we define an RPC that just adds two numbers and wish to execute
it on the server for the client. The client gets an upcall when the results are done, and it
displays the results graphically.

Mormot’s own example 14 includes a server which does exactly that. It accepts two
numbers, parameters: num1 and num2, and adds them to return a result.

However, unlike the previous examples, it requires a login – which is probably more akin
to what you will encounter in a typical non-public database. All your production code
should use HTTPS rather than HTTP. But is that enough to make you safe? Certainly not.
HTTPS traffic is not immune to manin-the-middle (MITM) attacks. In fact, even a typical
user can press F12 on most browsers to see your passwords in clear text.
In addition to HTTPS, Mormot uses an excellent api where the client and server verify
who each other is, thereby preventing a man-in-the-middle attack by: never transmitting a
clear-text password, keeping it a secret
always putting a timestamp on requests to prevent attacker replays
digitally signing every message from client to server

Using relatively complex mathematics called cryptography, it is possible to accomplish


these goals. For this protocol, we take a SHA256 hash of the password plus a salt
(something to further randomize it). The SHA256 algorithm converts any text into a
meaningless almost random order of bits where the change of even a single bit in the
password text causes a totally different result, and it is not bijective, you cannot reverse the
SHA256 algorithm from the hash to recover the password. The only effective way to
reverse it is to try every possible password until you stumble across the one that makes the
hash come to that value or have an enormous table of precomputed hashs for every number.
But since we never transmit the hash, that risk is further reduced.

So the client and the server keep a hash of the shared password, but they never
communicate it to each other. Instead, the server presents a challenge to the client saying
“use this challenge number in a mathematical formula with the secret hash”, to which there
is exactly one answer in 4 billion random attempts. The client can easily solve the problem
if he knows the hash, but can only guess if he does not. If the client just guesses, the server
knows and can take action to thwart further hacking attempts.

Our interface library does all that work for you. You just supply the userid/password pair
with a call to Authenticate.
For this sample program we have two edit boxes for the two numbers to add, two more
edits for the userid and password, two buttons (login and calculate) and a few labels for
the + plus sign and the answer. We also need a TScript pointing to the sha256.js JavaScript
because at the time of writing I have not yet converted sha256.js to EWB.
The whole EWB program is here:
unit sample3a;
interface
uses WebHTTP, EwbMormot, WebCore, WebUI, WebForms, WebCtrls,
WebEdits, WebLabels, WebBtns,
WebComps;
type

TForm1 = class(TForm)
Edit1: TEdit;
Label1: TLabel;
Edit2: TEdit;
Label2: TLabel;
Button1: TButton;
edUserName: TEdit;
edPassword: TEdit;
Button2: TButton;
Script1: TScript;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Form1Show(Sender: TObject);

private
{ Private declarations }
mormot : TMormotConnection; contract : string;
procedure sum( num1, num2 : string ; proc : TFnOnSum );
procedure OnSum( s : string );

procedure OnMyLogin( s : string ); procedure PostSumOp( sr :


TServerRequest; method : TRequestMethod ;
statuscode : integer;
rh : TStrings; Response : TStrings ); procedure
sum_contract;

public
{ Public declarations }
end;

TFnOnSum = procedure( s : string ) of object; var


Form1: TForm1;
implementation
procedure TForm1.Button1Click(Sender: TObject); var
num1, num2 : string;
begin
num1 := Edit1.Text; num2 := Edit2.Text;
Label2.Caption := ' = calculating...';
sum( num1, num2, OnSum ); end;
procedure TForm1.OnSum( s : string ); begin
Label1.Caption := s;
end;
type
TSum = class (TMormotTable )
private
fn1, fn2 : integer;

published
property n1 : integer read fn1 write fn1; property n2 :
integer read fn2 write fn2;

end;

procedure TForm1.PostSumOp( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
var

s : string;
i, j : integer;
begin
if statuscode < 300 then begin
s := Response.Text; // eg. {result: [5]}
i := Pos('[',s ); // find first
j := Pos(']',s, -1 ); // search backwards from end s :=
Copy( s, i+1, j-i-1 );

if contract = '' then contract := s


else label2.Caption := ' = ' + s;
end else begin

s := InttoStr(sr.Statuscode )+ #13 + sr.StatusText +#13


+rh.Text;
ShowMessage( s, 'Network Calculator.Add Failed)'); end;
end;
procedure TForm1.sum_contract;
var
s : string; begin
s := 'http://localhost:888/root/Calculator._contract_';
contract := '';
mormot.PostOp := PostSumOp;
mormot.RawPost( s, nil, '[]' );
end;
procedure TForm1.sum( num1, num2 : string ; proc : TFnOnSum
); var
sum1 : TSum;
s : string;
begin
//
sum1 := TSum.Create;

sum1.n1 := StrToInt(num1); sum1.n2 := StrToInt(num2);

s := 'http://localhost:888/root/Calculator.Add';
mormot.PostOp := PostSumOp;
mormot.RawPost( s, sum1, '') ; // or call RawPost(s, Nil, '[

'+num1+', '+num2+']' );
sum1.Free;
end;

procedure TForm1.OnMyLogin( s : string ); begin


ShowMessage('Logged in as ' +s, 'User App' );
Button1.Enabled := True;
sum_contract;
end;

procedure TForm1.Button2Click(Sender: TObject);


var
hostport : string = 'http://localhost:888';

begin
mormot := TMormotConnection.Create;
Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord',

self);
mormot.OnLogin := OnMyLogin;
mormot.Authenticate( hostport , edUserName.Text,

edPassword.Text );
end;
procedure TForm1.Form1Show(Sender: TObject);
begin

Button1.Enabled := False; // disable Calculate button until


logged in
end;

end.

The first step is Form1Show which disables the Calculate button until the user is logged
into the system.
Button1.Enabled := False; // disable Calculate button until
logged in
Ensure your application always makes it clear that a user must login before accessing the
server.
Button2 is the login button.

var
hostport : string = 'http://localhost:888';
begin
mormot := TMormotConnection.Create;
Mormot.InitServer(
'http://127.0.0.1:8080/root/SampleRecord', self);

mormot.OnLogin := OnMyLogin;
mormot.Authenticate( hostport , edUserName.Text,
edPassword.Text );
end;

The login button sets an OnLogin pointer to our OnMyLogin function to change the user
interface when we are logged in. Then it stores and processes the login
connection/userid/password.
Within a fraction of a second Mormot will reply and, if your password is correct, you will
get an upcall to OnMyLogin.

procedure TForm1.OnMyLogin( s : string ); begin


ShowMessage('Logged in as ' +s, 'User App' );
Button1.Enabled := True;
sum_contract;
end;

In addition to showing the user we are logged in, and re-enabling the Calculate button, it
calls sum_contract to verify the sum procedure works. Mormot replies with a contract
number. On a more advanced implementation, this would do something clever, but ours just
discards this contract number.

Now the user is free to enter two numbers and press Button1 which is the calculate button.
procedure TForm1.Button1Click(Sender: TObject); var
num1, num2 : string;
begin
num1 := Edit1.Text; num2 := Edit2.Text;
Label2.Caption := ' = calculating...';
sum( num1, num2, OnSum ); end;
Button1Click converts the text entries into number and calls sum() which is a local function
call… but it calls a remote function call to do the work.

procedure TForm1.sum( num1, num2 : string ; proc : TFnOnSum


); var
sum1 : TSum;
s : string;
//
sum1 := TSum.Create;

sum1.n1 := StrToInt(num1); sum1.n2 := StrToInt(num2);

s := 'http://localhost:888/root/Calculator.Add';
mormot.PostOp := PostSumOp;
mormot.RawPost( s, sum1, '') ; // or call RawPost(s, Nil, '[

'+num1+', '+num2+']' );
sum1.Free;
end;

There are a variety of ways you can transfer the numbers to the sum() RPC. The easiest is
what we’ve shown, to declare a structure and populate it with the parameters needed. In
our case, the reference document said the two parameters are num1 and num2. If you use
names, as we have, you do not need to worry about ordering.

An alternative approach would be to use unnamed parameters by calling


Momort.Rawpost( s, Nil, [‘+num1+’,’+num2+’]);
But this strategy is cumbersome when the parameter list gets longer.
Note that before calling RawPost we set the upcall parameter to PostSumOp.

PostSumOp is called by both the contract setup sequence and by the sum() function. But we
know the difference between the two because at contract time the contract number is ‘’,
whereas at sum() time it will have already been set by our previous call.

procedure TForm1.PostSumOp( sr : TServerRequest; method :


TRequestMethod ; statuscode : integer; rh : TStrings;
Response : TStrings );
var

s : string;
i, j : integer;
if statuscode < 300 then begin

s := Response.Text; // eg. {result: [5]} i := Pos('[',s );


// find first
j := Pos(']',s, -1 ); // search backwards from end s :=
Copy( s, i+1, j-i-1 );

if contract = '' then contract := s


else label2.Caption := ' = ' + s;
end else begin

s := InttoStr(sr.Statuscode )+ #13 + sr.StatusText +#13


+rh.Text;
ShowMessage( s, 'Network Calculator.Add Failed)'); end;
end;

PostSumOp is passed something a 200 series result code and a string like { result : [ 5] }.
Rather than make a complicated result parsing system, I just remove the
surrounding [ ]’s and process the middle text.
If this is our first call, we know it’s a contract number (which we just note but do nothing),
and on subsequent results we know we have the result of the remote Sum() function, so we
update the label saying the resut.

If the StatusCode is 300 or higher, there is an error of some sort, so we display it.

There are a large number of samples that ship with Mormot, and with the three examples
given here demonstrate four important components of Mormot development:

• API to talk to the REST objects


• Using the EWB DataSet to improve access over pure API
• Using message signing and userids/passwords
• Performing RPCs to accomplish anything Mormot can do While the initial learning curve
with Mormot appears steep, there are many examples to guide you and you will benefit
from a very scalable, very secure and trusted and very capable code base on which to
build your applications. We have covered a lot of material in this book so congratulations
on getting to the end. It’s a rich environment and I hope you enjoyed the selection of topics I
picked and that they represent the sorts of challenges you face.

This is my fourth book and it was a pleasure to research and write. I think I’m getting better
with each book, but that’s just my take. I know the project has taught me a lot. And I find the
experience very intimate with the reader, trying to anticipate your questions all while trying
to remain short and concise.

I learned long ago that elegance means beautifully simple, though in English we often
confuse it with ornate which is actually the opposite. The simpler your code is, the less
likely it is to confuse you and contain errors. My code is usually simple, and I also try to
exend that to my writing style; you are the judge of whether that has been successful.

Your feedback will help make the book better. If there was something I glossed over or
missed entirely or maybe got wrong, please do let me know so I can improve the
experience for future readers.

One of the benefits of modern publishing is small batches and on-demand printing, so we
can make new revisions without a large write-off of inventory. For the consumer this means
authors are encouraged to update their material.

I also offer consulting services if you have a difficult problem and need assistance.
Finally, I wish you the best of luck with your future EWB projects. Maybe our paths will
cross again.

Erick Engelke
erickengelke@gmail.com http://erickengelke.com
Alpha Arc
ArcTo

asSineEaseIn
asSineEaseInOut asSineEaseOut asSineEaseOutIn

asBackEaseIn
asBackEaseInOut asBackEaseOut
asBackEaseOutIn asBounceEaseIn asBounceEaseInOut asBounceEaseOut asBounceEaseOutIn asCircEaseIn
asCircEaseInOut asCircEaseOut
asCircEaseOutIn asCubicEaseIn
asCubicEaseInOut asCubicEaseOut asCubicEaseOutIn asElasticEaseIn
asElasticEaseInOut asElasticEaseOut asElasticEaseOutIn asExpoEaseIn
asExpoEaseInOut asExpoEaseOut
asExpoEaseOutIn asLinear
asNone
asQuadEaseIn
asQuadEaseInOut asQuadEaseOut
asQuadEaseOutIn asQuartEaseIn
asQuartEaseInOut asQuartEaseOut
asQuartEaseOutIn asQuintEaseIn
asQuintEaseInOut asQuintEaseOut
asQuintEaseOutIn

BeginPath
BezierCurveTo
ClearRect
Clip
ClosePath
CompositeOperation

ConvertToDataURL FillColor
FillGradient FillPattern

FillStyle
LineCapStyle LineJoinStyle
LineWidth

ShadowBlur
ShadowColor ShadowOffsetX ShadowOffsetY

M iterLimit

StrokeColor StrokeGradient StrokePattern StrokeRect


StrokeStyle
StrokeText
Transform Translate

TextAlign
TextBaseLine

S-ar putea să vă placă și