Seed Tutorial : Standalone
v.3.8.1
Introduction

Seed, first and foremost, provides an easily embeddable JavaScript engine to developers looking for a straightforward way to create extensible applications. It also provides bindings between GObject and the WebKit JavaScript engine, giving new developers access to the power of the GNOME stack from a familiar and simple language, and allowing rapid prototyping of applications for hardened GNOME developers.

This tutorial begins with a few brief examples, and then dives right in, following the development of a simple Seed program, from beginning to end. By the end of the tutorial, you'll have your very own tiny WebKit-based web browser, as well as a summary knowledge of the use of Seed to build GTK+ applications.

Beginning Seed

It makes sense to start our exploration with a program you're probably quite familiar with:

    #!/usr/bin/env seed

    print("Hello, world!");

If you were to make this script executable (`chmod +x hello.js`), and run it, you'd hopefully see the following, just as expected (if you don't, for some reason, make sure you have the latest version of Seed installed, then email us):

    Hello, world!

In order to make the file executable, include (`#!/usr/bin/env seed`) at the top of every Seed program you write. This is known as the shebang line, and tells your shell where to find the `seed` interpreter; I'm only going to include it when listing a whole file, from now on.

Variables in JavaScript are not given any type, and conversion between different kinds of values is automatic and painless. For example, you can:

- Add two strings `("Hello, " + "World!")` turns into `"Hello, World!"`
- Add a number to a string `("Example" + (2 * 2))` turns into `"Example4"`

There is one exception: in order to convert a string of digits into a 'number', JavaScript needs to be explicitly instructed to do so: `parseFloat("42.5")`.

Seed also provides a very simple interface to the GNU Readline library, which allows programs to ask the user for input. This interface is in the readline module, which must be imported before it can be used. The only argument `readline.readline()` requires is the prompt for the user. Also, the current version of Seed ensures that everything typed is automatically saved in the prompt's history; if you press the up key while at a prompt, you can access and edit lines you've previously entered. Future versions of Seed will provide more control over the history and other parts of readline.

    readline = imports.readline;
    var my_name = readline.readline("Your name? ");
    var my_age = readline.readline("Your age? ");
    var old = 25;
    var old_age = old + parseFloat(my_age);
    print(my_name + " will be " + old_age + " in " + old + " years!");

You've probably noticed that the word '`var`' precedes the first use of every variable in JavaScript. This is important, because it ensures that the memory consumed by the variable is freed to be used elsewhere at the end of the current block of code, when the variable goes out of scope. If, instead, you want to create a variable which is global (available forever, after it is created), you can omit the '`var`'. Keep in mind that making many global variables is generally considered bad practice, and can be expensive in terms of memory use.

A JavaScript Shell

JavaScript, being a scripting language, includes a construct, `eval()` which allows you to evaluate a string of JavaScript. This allows, for example, a user to input JavaScript with `readline`, and it to be executed as if it had been part of your source file. In addition, `eval()`'s return value is the return value of the snippet of code. For example:

    var output = eval("2+2");
    print(output);

Will output:

    4.000000

When something goes wrong in a piece of JavaScript code, the program will exit, most likely leaving the user in a confused state. For example, if you try to access a variable that doesn't exist: `print(asdf);` Seed will exit with the message: `ReferenceError Can't find variable: asdf`. It is possible to catch this sort of error, or exception, inside of your JavaScript program, ensuring that it doesn't terminate your program - or that if it does, it prints a useful error message. The `try/catch` construct provides a way to try to execute a segment of JavaScript, and, if it fails, run a second segment, without exiting the program. The second segment could print a user-friendly error message, ignore the exception entirely, or try to work around the problem. A quick example of `try/catch`:

    try{
        print(asdf);
    }
    catch(e){
        print("Something went wrong!");
    }

It's also possible to determine what, exactly, went wrong. The '`e`' in the `catch` statement (which, by the way, you cannot omit) is actually an object containing information about the exception! We can access some of the basic properties of this object:

    try{
        print(asdf);
    }
    catch(e){
        print("Something went wrong!");
        print(e.name);
        print(e.message);
    }

This will print a message similar to what would be printed if you hadn't caught the exception, but without exiting the program!

Combining `readline`, `eval`, exceptions, and `print`, we can write a simple shell, allowing interactive use of Seed. This shell is included in the Seed distribution, in `examples/repl.js`. Looking at the source, you'll note that it takes very little code to implement a shell:

examples/repl.js

    #!/usr/bin/env seed

    readline = imports.readline;

    while(1){
        try{
            print(eval(readline.readline("> ")));
        }
        catch(e){
            print(e.name + " " + e.message);
        }
    }

You can (and should!) use this shell in order to experiment with and learn to use Seed.

Getting GTK Going

Thus far in this tutorial, we've been completely ignoring the most useful part of Seed: the ability to use external libraries from within JavaScript. The single most useful of these libraries is GTK, the widget and windowing toolkit used by all GNOME applications, which will provide the ability to create and manipulate graphical windows, as well as just about any sort of widget you should require.

In order to use GTK (or any other external library) in a Seed program, you first have to import the functions from said library. `Gtk = imports.gi.Gtk`, does this for us. The imports.gi object is a special object which handles importing libraries from introspection data.

Once the library has been imported, all of the imported functions are available on the Gtk object: `GTK.init()`, etc.

Let's start off the development of our browser by getting GTK working. It takes very little to get a window displayed with Seed:

    #!/usr/bin/env seed

    Gtk = imports.gi.Gtk;
    Gtk.init(null, null);

    var window = new Gtk.Window();
    window.show_all();

    Gtk.main();

If you've ever used GTK from C, you'll notice some similarities here. All of the GTK functions have been mapped into JavaScript in a reasonable way, but it will certainly take a bit to get used to, for example, `new Gtk.Window()` instead of `gtk_window_new()`.

Executing the above script should give you a window that looks entirely empty and boring, something like the following:

JSON Constructors

Notice that the title of the window is 'seed'. We'll fix that, using another Seed feature: you can use JSON notation to set properties while constructing objects, like so:

    var window = new Gtk.Window({title: "Browser"});

This saves a lot of typing from the alternative, conventional method:

    var window = new Gtk.Window();
    window.set_title("Browser");

You can set any number of properties this way, by separating them by commas (`{"title": "Browser", "default-height": 500}`, etc.). This method should work for any GObject constructor.

Signals

You'll notice that our program, as it stands, fails to quit when you click the 'Close' button. You can, of course, quit it with Ctrl-C, but this is certainly unacceptable behaviour. To fix it, we'll connect a C function to the signal that gets emitted when the 'Close' button is clicked:

    window.signal.hide.connect(Gtk.main_quit);

The signal names are the same as in the GTK documentation, except using underscores instead of dashes between words.

GObject Subclassing

Inheritance is a useful feature of many object-oriented languages which provides a way to create your own classes, extending any existing class, while 'inheriting' those behaviors and properties of your parent class which you do not choose to override. Seed provides an incredibly simple interface in order to subclass GObject classes. In order to make our browser, we'll need a number of subclasses. We'll start with our toolbar; since it's a horizontal collection of elements, let's make it a subclass of Gtk.HBox:

    BrowserToolbar = new GType({
        parent: Gtk.HBox.type,
        name: "BrowserToolbar",
        init: function (){
        }
    });

You'll notice that the GType takes a JavaScript object. The three most important properties which we'll be using are parent, the type of the 'parent' class, from which our subclass should inherit its default behavior; name, the UpperCamelCase name of our new class; and init, a JavaScript function which is called each time a new instance of the class is made.

Working with Widgets

We'll start by making the BrowserToolbar's buttons. GTK provides a ToolButton widget, which is generally used for making such toolbars, as well as various different stock icons (to ensure consistency within all GTK applications). Browsing through the GTK Stock Item documentation, we find that we're looking for "`gtk-go-back`", "`gtk-go-forward`", and "`gtk-refresh`". A glance at the GtkToolButton documentation shows us that we can choose a stock icon by setting the `stock-id` property - we'll use JSON constructors to keep things tidy. Do note that we use underscores instead of dashes, because the property name isn't quoted (thus, a dash would indicate subtraction, which isn't what we're looking for!):

    BrowserToolbar = new GType({
        parent: Gtk.HBox.type,
        name: "BrowserToolbar",
        init: function (){
            // Private
            var url_bar = new Gtk.Entry();

            var back_button = new Gtk.ToolButton({stock_id:"gtk-go-back"});
            var forward_button = new Gtk.ToolButton({stock_id:"gtk-go-forward"});
            var refresh_button = new Gtk.ToolButton({stock_id:"gtk-refresh"});

            // Implementation
            this.pack_start(back_button);
            this.pack_start(forward_button);
            this.pack_start(refresh_button);
            this.pack_start(url_bar, true, true);
        }
    });

There are a few things in the snippet above which you probably haven't seen before (unless you've used GTK in another language). Firstly, the Gtk.Entry widget is a simple text entry field, like you would expect in a browser's URL bar. Secondly, you'll notice the use of the Gtk.HBox widget's `pack_start()` function. This serves as the foundation of GUI layout in GTK: a window is subdivided into boxes, which 'pack' widgets in a particular direction (HBoxes pack horizontally, VBoxes pack vertically, as expected). We use a HBox, since we want our toolbar arranged horizontally. `pack_start()` adds a widget to a Box; widgets are packed in the order they're added. There are optional arguments, which are addressed in more depth in the GtkBox documentation, which allow you to force widgets to expand into the usable space (the second and third arguments used when packing `url_bar` above serve this purpose).

To try and get a more visual feel of packing, let's take a look at the Box layout for our browser:

Callbacks Galore

We also need a bunch of callbacks (for all three buttons, and for when you're done entering text in the URL bar). We'll make them just print the function they're supposed to perform, for now, since we don't have a WebKit view to operate on yet. Let's make them private members of the BrowserToolbar class, and connect them to the appropriate signals:

    BrowserToolbar = new GType({
        parent: Gtk.HBox.type,
        name: "BrowserToolbar",
        init: function (){
            // Private
            var url_bar = new Gtk.Entry();

            var back_button = new Gtk.ToolButton({stock_id:"gtk-go-back"});
            var forward_button = new Gtk.ToolButton({stock_id:"gtk-go-forward"});
            var refresh_button = new Gtk.ToolButton({stock_id:"gtk-refresh"});

            var back = function (){
                print("Go Back");
            };

            var forward = function (){
                print("Go Forward");
            };

            var refresh = function (){
                print("Refresh");
            };

            var browse = function (url){
                print("Navigate to: " + url.text);
            };

            // Implementation
            back_button.signal.clicked.connect(back);
            forward_button.signal.clicked.connect(forward);
            refresh_button.signal.clicked.connect(refresh);
            url_bar.signal.activate.connect(browse);

            this.pack_start(back_button);
            this.pack_start(forward_button);
            this.pack_start(refresh_button);
            this.pack_start(url_bar, true, true);
        }
    });

You'll notice that right now, nothing's creating a BrowserToolbar, so if you execute your application, you won't see the toolbar drawn. To remedy this, before `window.show_all()`, add lines to create and pack the toolbar:

    toolbar = new BrowserToolbar();
    window.add(toolbar);

Your code should be in a runnable state now; take a minute to try it out, stand back, and admire what you've learned:

If, for some reason, something doesn't work, compare your code to the tutorial version.

Adding WebKit

It's finally time to start displaying some web pages with our little browser! Let's create and pack a `WebKit.WebView` below our toolbar, first. We should make a WebView subclass to use, to initialize some settings and provide an encapsulated interface to our browser view.

A quick note about WebKit: if you omit the protocol part of a URL (e.g., http://), WebKit won't even bother to try to figure it out - so make sure you specify it! We'll add a `browse` function to our subclass, as well as a callback when the WebView's URL changes, so we can update the URL bar. To get around this shortcoming, we'll use JavaScript's string search function to see if a protocol has been specified, and, if it hasn't, we'll assume it's "`http://`".

Poking around in the WebKit documentation (the WebKit team is a bit behind on documentation, so all we have to work with is header files), we find that the `open()` function on a WebView allows you to navigate to a particular page. We'll use this in our implementation of the `WebView.browse()` function below.

Here's an early version of our new `BrowserView` subclass:

    BrowserView = new GType({
        parent: WebKit.WebView.type,
        name: "BrowserView",
        init: function (){
            // Private
            var update_url = function (web_view, web_frame){
                var toolbar = browser.get_toolbar();

                toolbar.set_url(web_frame.get_uri());
                toolbar.set_can_go_back(web_view.can_go_back());
                toolbar.set_can_go_forward(web_view.can_go_forward());
            };

            // Public
            this.browse = function (url){
                if(url.search("://") < 0)
                    url = "http://" + url;

                this.open(url);
            };

            // Implementation
            this.set_scroll_adjustments(null, null);
            this.signal.load_committed.connect(update_url);
        }
    });

You'll notice that we also turned off WebKit's automatic scrollbars, with the `set_scroll_adjustments` function. We do this in order to get smooth scrolling, by wrapping the WebView in a Gtk.ScrolledWindow, as you'll see shortly.

Also, remember that we need to import a namespace before its functions are available to us! So, go back to the top of the file and import "WebKit", just after you import "Gtk". One final thing, before you again try to run your browser: we haven't yet specified a 'recommended' size for our window - let's go ahead and do that (if we didn't do so, the WebKit view would have no space to fill!). Just after you create the Gtk.Window(), add:

    window.resize(600,600);

Pulling it all together...

As you can see in the last bit of code, we have a few more functions to add to our BrowserToolbar class. Functions to allow toggling the 'clickable' state of the back and forward buttons, and a function to update the URL bar when a link is clicked. We will also update the button callbacks with what we find while again browsing webkitwebview.h: reload(), go_forward(), and go_back().

    BrowserToolbar = new GType({
        parent: Gtk.HBox.type,
        name: "BrowserToolbar",
        init: function (){
            // Private
            var url_bar = new Gtk.Entry();

            var back_button = new Gtk.ToolButton({stock_id:"gtk-go-back"});
            var forward_button = new Gtk.ToolButton({stock_id:"gtk-go-forward"});
            var refresh_button = new Gtk.ToolButton({stock_id:"gtk-refresh"});

            var back = function (){
                browser.get_web_view().go_back();
            };

            var forward = function (){
                browser.get_web_view().go_forward();
            };

            var refresh = function (){
                browser.get_web_view().reload();
            };

            var browse = function (url){
                browser.get_web_view().browse(url.text);
            };

            // Public
            this.set_url = function (url){
                url_bar.text = url;
            };

            this.set_can_go_back = function (can_go_back){
                back_button.sensitive = can_go_back;
            };

            this.set_can_go_forward = function (can_go_forward){
                forward_button.sensitive = can_go_forward;
            };

            // Implementation
            back_button.signal.clicked.connect(back);
            forward_button.signal.clicked.connect(forward);
            refresh_button.signal.clicked.connect(refresh);
            url_bar.signal.activate.connect(browse);

            this.pack_start(back_button);
            this.pack_start(forward_button);
            this.pack_start(refresh_button);
            this.pack_start(url_bar, true, true);
        }
    });

One last thing! We need a `Browser` class, a subclass of `Gtk.VBox`, to contain a BrowserToolbar and BrowserView, and to provide functions from which to access these widgets. We'll also set up the `Gtk.ScrolledWindow` which we discussed earlier:

    Browser = new GType({
        parent: Gtk.VBox.type,
        name: "Browser",
        init: function (){
            // Private
            var toolbar = new BrowserToolbar();
            var web_view = new BrowserView();
            var scroll_view = new Gtk.ScrolledWindow();

            // Public
            this.get_toolbar = function (){
                return toolbar;
            };

            this.get_web_view = function (){
                return web_view;
            };

            // Implementation
            scroll_view.smooth_scroll = true;
            scroll_view.add(web_view);
            scroll_view.set_policy(Gtk.PolicyType.AUTOMATIC,
                                   Gtk.PolicyType.AUTOMATIC);

            this.pack_start(toolbar);
            this.pack_start(scroll_view, true, true);
            this.show_all();
        }
    });

One final thing: we need to create a `Browser` object, and add it to the window, now, instead of a `BrowserToolbar`. The `Browser` object will contain a `BrowserToolbar` and a `BrowserView`. So, change the section near the bottom of the file from:

    toolbar = new BrowserToolbar();
    window.add(toolbar);

into:

    browser = new Browser();
    browser.get_web_view().browse(home_page);
    window.add(browser);

You'll notice we navigate to `home_page`. Assign `home_page` to your favorite web site at the top of the file; perhaps even make a section at the top of the file of browser settings (I'm sure you can think of other things to implement as settings!)

If all goes well, your browser should now be in a working state. Start it up - it ought to look much like the following:

The final version of the tutorial's source code is available if you're having trouble; if, however, you made easy work of the tutorial, you should consider making some improvements to your browser: change the window title when the web page title changes (look at the title_changed signal!); add tabs (GtkNotebook is probably what you're looking for); bookmarks are often useful!; perhaps a status menu? Or, go ahead and write your own application in Seed!