Web Tools
The web tools in Empirical are structured to allow the developer to fully control components of a web page from C++. Note that you must have the emscripten compiler installed for web utilities to function properly.
Empirical web Widgets include Text, Buttons, Images, Tables, or many
other HTML components. All widgets are derived from emp::Widget
and
structured such that multiple widgets can properly refer to and modify
the same component.
A Simple Example
In order to get a simple example working, you need:
an HTML file (the default version is effective in most cases),
the C++ code file to actually control the web page, and
the necessary flags to compile (again, the default version should work).
The required files are all in examples/web/
called Example.html
,
Example.cc
and Makefile
.
Here is Example.html
:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Example</title>
</head>
<body>
<div id="emp_base"></div>
<script type="text/javascript" src="Example.js"></script>
</body>
</html>
The only portions of this file that should be customized for your
project are the title (in the <title>
tags) and the JavaScript file
(change Example.js
to the name of the .js
file that you want to
generate). The div, named "emp_base"
, is where we will hook Empirical
to this web page. You MAY add more divs to control or customize any
aspects of this file’s CSS, but none of this is required for developing
an interface, and most of it can be controlled via C++.
Here is the Example.cc
file:
#include "emp/web/web.hpp"
namespace UI = emp::web;
UI::Document doc("emp_base");
int main() {
doc << "<h1>Hello World!</h1>";
}
This setup will create a web page the says “Hello World!” in large
letters. Manipulations to doc will allow us to control many portions of
the web page. To break down what’s going on, including web/web.h
pulls in Empirical’s web framework. Setting the namespace to UI
provides a shorter prefix for accessing widgets. Creating the doc
Document
object attaches to the div by the same "emp_base"
name in
the HTML file. Note the doc
is global because we want the web page to
keep functioning after main()
finishes. In emscripten, main()
is
like any other function and does not force the termination of the
execution.
Now that we have the two files we need, the only other thing we need to
do is compile. The provided Makefile can be run by typing
make Example.js
. This will trigger:
emcc -std=c++20 -Wall -Wno-unused-function -I../../include/emp/ -Os -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" -s TOTAL_MEMORY=67108864 --js-library ../../include/emp/web/library_emp.js -s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']" -s NO_EXIT_RUNTIME=1 Example.cc -o Example.js
emscripten uses the
emcc
compiler (orem++
, since we are using C++).-std=c++20
: Empirical requires c++20.-Wall -Wno-unused-function
: turn on all warnings by default except for unused functions, since not all library functions are going to be used.-I../../include/
: The compiled file is two directories up in the Empirical library, so this flag properly includes the source files.-Os
: Optimize for size, though-O3
may perform better in some situations.-s TOTAL_MEMORY=67108864
: Make sure we have enough memory; in this case reserve 64 MB.--js-library ../../include/emp/web/library_emp.js
: Load Empirical JS functions that we might need.-s EXPORTED_FUNCTIONS="['_main', '_empCppCallback']"
: Make sure we can run the C++ functionsmain()
and_empCppCallback()
from Javascript, to facilitate two-way communication.-s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']"
: expose necessary Emscripten utilities for internal Empirical use-s NO_EXIT_RUNTIME=1
: In most cases, we don’t want our program to stop whenmain()
finishes.
…finally we list the source file we are compiling (Example.cc
) and
the output file that we want to produce (Example.js
). The majority of
these flags shouldn’t change from one compilation to the next, other
than changing optimization to debugging options, which we’ll cover in
the debugging guide.
If you want to speed things up further, you may also want to add the compiler option
-DNDEBUG
: turn off debugging for a faster executable
To test the results, open Example.html
in your web browser!
Controlling a Web Page: The Basics
To understand how most of the HTML widgets work we need only change the main code file; the current HTML file and compiler options can be left the same.
As indicated by our starting point, text can be streamed into an HTML document in a similar way to an output stream in the standard library. For example, we can update our main function to mix text and variables:
int main() {
int x = 5;
doc << "<h1>Hello World!</h1>";
doc << "x = " << x << ".<br>";
}
In additional to regular variables, emp::Document
(and other Empirical
web containers) can also take a range of Empirical Widgets.
void Ping() { doc << "Ping! "; }
int main() {
int x = 5;
doc << "<h1>Hello World!</h1>";
doc << "x = " << x << ".<br>";
// Insert an image (in place)
doc << UI::Image("url.goes.here") << "<br>";
// Create a button and then insert it.
UI::Button my_button( Ping, "Click me!" );
doc << my_button;
}
Notice now that you not only have a pretty picture, but you also have a button that will add new text on to the screen each time it’s clicked.
But what if we want to update existing content? We can do this in two ways: either by marking a variable (or function) as “Live” or by simply changing a widget that is already on the screen.
“Live” Variables and Function
Anything sent to a web page that is inside a UI::Live()
function will
always have its most current value used whenever that portion of the
page is redrawn. For example, let’s make our button modify the value of
x
and redraw it.
int x = 5;
int main() {
doc << "<h1>Hello World!</h1>";
doc << "Original x = " << x << ".<br>";
doc << "Current x = " << UI::Live(x) << ".<br>";
// Create a button to modify x.
UI::Button my_button( [](){ x+=5; doc.Redraw(); }, "Click me!" );
doc << my_button;
}
Notice that we also moved x
to be a global variable. This is because
if it were local to main()
it would be freed as soon as main ended.
We’re also using a lambda this time instead of a previously defined
function. Either option is fine.
Try clicking on the button – you’ll see that x
will be updated, and
then the document is signaled that it needs to redraw, so the change is
reflected on the screen. Note that we didn’t actually need to redraw
the entire document to update x
, just the Text
widget it is in;
we’ll talk more about how to do that below.
Of course, we can put a function in the UI::Live()
and that function
will be called each time the containing Widget is redrawn.
int x = 5;
int main() {
doc << "<h1>Hello World!</h1>";
doc << "Original x = " << x << ".<br>";
doc << "Current x = " << UI::Live(x) << ".<br>";
doc << "x/5 = " << UI::Live( [](){ return x/5; } ) << ".<br>";
// Create a button to modify x.
UI::Button my_button( [](){ x+=5; doc.Redraw(); }, "Click me!" );
doc << my_button;
}
Of course, we need to be able to modify Widgets in addition to variables; fortunately this is easy as well.
Finding and Modifying Existing Widgets
There are two ways to keep track of Widgets in Empirical. One is to
simply hold on to a variable associated with the Widget, such as
my_button
in our previous examples. At any point we can still modify
something about my_button
. For example, if we added a line at the end
of main:
my_button.Label("PLEASE Click Me!");
We will see that the button label updates to the new string.
The other option we have to keep track of a widget is to specify its
HTML identifier so that we can look it up again later. For example, if
when we first declared my_button
we had given it an extra string
argument, that string would be used as its identifier.
UI::Button my_button( [](){ x+=5; doc.Redraw(); }, "Click me!", "my_button" );
At any point after we insert the button into a container (such as
doc
), we are able to request it back from the container again. So, for
example instead of
my_button.SetLabel("PLEASE Click Me!");
we could have said
doc.Button("my_button").SetLabel("PLEASE Click Me!");
and doc
will properly look up the correct button for us (or trip an
assert if the required button cannot be found.) In practice, allowing
containers to track Widgets is much easier than juggling links to all of
them yourself.
Controlling CSS
Web page aesthetics are controlled by adjusting the CSS of the widgets,
and Empirical is no different. You have two options for controlling CSS
– you can do it the traditional way by modifying the HTML file (often
with the help of other packages) or else you can control specific CSS
settings with the .CSS
member function associated with all Widgets.
For example, if we wanted our button to be green with red text, we could
add to the end of main
the statement
my_button.SetCSS("background-color", "green").SetCSS("color", "red");
Note the chaining of modifiers.
Most common settings are directly defined as member functions, so the above could also be expressed as
my_button.SetBackground("green").SetColor("red");
which can be cleaner. See the class documentation for a full list of available functions to modify each Widget type.
Empirical Tables
Tables are one of the features of Empirical that differs most in style from the underlying HTML it modifies, preferring a more exact form where the user sets the number of rows and columns to be used.
For example, to build a table with 7 rows and 3 columns, we can declare
UI::Table my_table(7, 3, "my_table");
To access a cell from a table, we can simply use the .GetCell(x,y)
member function. So if we want to fill the table with data, we might do
something like
for (size_t r = 0; r < 7; r++) {
for (size_t c = 0; c < 3; c++) {
my_table.GetCell(r,c) << (r+3*c);
}
}
To make the table pretty, we probably want to add some CSS.
my_table.SetCSS("border-collapse", "collapse");
my_table.SetCSS("border", "3px solid");
my_table.CellsCSS("border", "1px solid");
my_table.CellsCSS("padding", "3px");
Note that we are able to target the CSS of all TableCells
at once.
We can target individual cells using GetCell()
, as well as using
GetRow()
, GetCol()
, GetRowGroup()
, and GetColGroup()
. Each of
these returns a TableWidget
with the appropriate component in focus,
so additional modifications are handled correctly.
Finally, of course, make sure to insert the table into the document.
doc << my_table;
Canvas
Canvas widgets in Empirical are a slightly streamlined version of
canvases in HTML. To build one, you simply need to create an
emp::Canvas
object (with the appropriate size) and place it into the
document. For example
UI::Canvas my_canvas(300, 400, "my_canvas");
would create a 300x400 canvas. We can then use member functions to draw
lines, circles, and rectangles on the Canvas
. For example:
my_canvas.Circle(100, 100, 40, "red", "black");
would draw a circle at (100,100) with a radius of 40, a face color of red, and an outline of black.
Note also that a number of Draw()
function exist for Canvas
that
will allow more complex structures to be drawn easily, such as grids
where the colors of each position are specified. Several other Empirical
tools hook into Canvas
. For example if you are building a world with
emp::Surface
, that surface can be handed to canvas to have all of the
shapes on it (currently just circles) drawn for you.
Other topics, coming soon!
TextArea
sThe Text Widget extras (specifying, closing, modifying, etc.)
Listeners and Capturing input events
Working with keypresses
Customized Divs
Uploading files
Interfacing between HTML and Empirical
Using JSWrap
Freezing and Activating Widgets
Animations