Actions

In the previous chapter, we used a chain of signals from a widget to the top-level window to trigger an action.

However, there’s a more convenient solution: actions!

When a widget activates an action, GTK walks up the hierarchy to find a widget implementing the requested action, and run it if it does.

# Adding an Action

Actions can be directly added to a Gtk.Application or a Gtk.ApplicationWindow, because they implement the Gio.ActionMap interface. Actions added to the application can be activated from any widget in any window, while those added to a window can only be activated by widgets from this window.

Actions can also be added to any widget, for instance by creating a Gio.SimpleActionGroup, adding actions to it, and inserting it into the widget with the insert_action_group() function.

We’ll add an action to our window that will allow us to change the view. This action will have:

  • a name, change-view, that will get used to activate the action
  • a parameter, telling which view we want to display
  • a callback function, implementing the actual code

We’ll create the action with the Gio.SimpleAction class.

Because actions can actually be called from outside the application, the parameter has to be serialized through a GLib.Variant. We’ll use a simple string variant, but it’s possible to create much more complex variants, refer to the GLib documentation on format strings and text format and the GJS guide to learn more about it. If your action doesn’t need a parameter, you can simply pass null.

It’s time to code! In data/ui/Window.ui, remove the signal element we added previously, and in src/Window.js, remove the onWelcomeButtonClicked() callback. Then, in the window constructor, we create the action and add it:

1import Gio from 'gi://Gio';
2import GLib from 'gi://GLib';
10class extends Gtk.ApplicationWindow {
11	constructor(params={}) {
12		super(params);
13		this.#setupActions();
14	}
15
16	/* ... */
17
18	#setupActions() {
19		// Create the action
20		const changeViewAction = new Gio.SimpleAction({
21			name: 'change-view',
22			parameterType: GLib.VariantType.new('s'),
23		});
24
25		// Connect to the activate signal to run the callback
26		changeViewAction.connect('activate', (_action, params) => {
27			this._viewStack.visibleChildName = params.unpack();
28		});
29
30		// Add the action to the window
31		this.add_action(changeViewAction);
32	}
33}

As you can see, GJS has a convenient unpack() function to deserialize the variant back to the original value.

# Activating an Action

Now that we created the action, we have to activate it.

We can do so in code with the activate_action() function, but because our button implements the Gtk.Actionable interface, we can actually define it in the UI template.

We start by removing the onButtonClicked() callback in src/WelcomeWidget.js, and the signal element in data/ui/WelcomeWidget.ui. Then we add the action-name and action-target properties to the button:

24<object class="GtkButton">
25	<property name="action-name">win.change-view</property>
26	<property name="action-target">'files'</property>
27	<!-- ... -->
28</object>

We prefix the action-name with the name of the action group the action is in. For the application window, the action group is win, for the application it is app.

The action-target is our parameter in GVariant text format. Because it’s a string, we put it between quotes.

One neat thing to note is that if the action is unavailable, the button will become insensitive.


Now, when we run our application, clicking on the button still changes the view to the Files view, but we no longer have a chain of signals from the widget to the window, the action can be called from anywhere!