Skip to content

Utilities and Decorators

Heliguy edited this page May 15, 2026 · 4 revisions

Along with the core improvements GObjectify adds to GNOME JS, GObjectify also provides a set of extra quality-of-life utilities as well.

dedent()

function dedent(strings: TemplateStringsArray, ...values: any[]): string

Allows writing multi-line strings with indents that match source code. This results in pretty looking code and pretty looking text.

const text = dedent`
	Hello,
		this line is indented relative to the block.
	Goodbye!
`
// Becomes:
// "Hello
//     this line is indented relative to the block.
// Goodbye!"

Note that beginning and ending whitespace is entirely removed, meaning no leading or trailing newlines.

next_idle()

function next_idle(): Promise<void>

Sometimes a section of code handles something that will take a while that might freeze the application or cause it to stutter. next_idle schedules a promise to be resolved when GLib's main loop is idle (not working on anything intense like window resizing or animations). This keeps your app responsive while heavy work is done.

function blocking(): void {
	// Calling this function will freeze the app
	for (let i = 0; i < 999999; i += 1) {
		print(i)
	}
}

async function non_blocking(): Promise<void> {
	// Calling this function will not freeze the app
	for (let i = 0; i < 999999; i += 1) {
		if (i % 10 === 0) {
			// On every tenth iteration, wait for idle time
			await next_idle()
		}
		print(i)
	}
}

Do note that using next_idle will make the function slower, but this is a compromise to avoid freezes and stutters.

timeout_ms()

function timeout_ms(milliseconds: number): Promise<void>

Since GJS is not a browser or Node environment, the setTimeout methods is not available. To make up for this GObjectify provides a similar function.

The function returns a promise that is resolved after the amount of milliseconds provided. GLib.timeout_add is utilized with GLib.PRIORITY_DEFAULT_IDLE to allow for an asynchronous waiting period. The promise is resolved once the time has elapsed.

async function () {
	print("Begin!")
	await timeout_ms(500)
	print("It has been 500 milliseconds!")
}

ConstMap

class ConstMap { ... }

The ConstMap class is a convenient helper to make the Map class type-safe. It's named ConstMap because its contents cannot be changed, and the types of its members must be fully known at transpile time.

let sizes = new ConstMap(
	["a", true],
	[false, 42],
	[128, "128"],
	["d", new Gtk.Box()],
)

let value1 = sizes.get(false) // `value1` is known to be type `42`
let value2 = sizes.get(128) // `value2` is known to be type `"128"`
let value3 = sizes.get("d") // `value3` is known to be type `Gtk.Box`

@Debounce()

function Debounce(
	milliseconds: number,
	params: {
		trigger: "leading" | "trailing" | "leading+trailing"
	} = {
		trigger: "trailing",
	},
): MethodDecorator

Method decorator that will pause calls to its method if previous calls have been made within its milliseconds of wait-time.

@GClass()
class MyBox extends from(Gtk.Box, {}) {
	count = 0

	@Debounce(200)
	some_method(): void {
		this.count += 1
		print(`Called ${count} time(s)`)
	}

	async main(): Promise<void> {
		this.some_method() // Called 1 time(s)
		this.some_method() // Called 1 time(s)
		await timeout_ms(200)
		this.some_method() // Called 2 time(s)
		this.some_method() // Called 2 time(s)
	}
}

@Notify()

function Notify(): void

Setter accessor decorator that will emit the notify signal for computed property.

@GClass()
class MyBox extends from(Gtk.Box, {
	item_id: Property.string(),
}) {
	#item_id = ""
	override get item_id(): string {
		return this.#item_id
	}
	@Notify // automatically emits the "notify::item-id" signal
	override set item_id(v: string) {
		this.#item_id = v
	}
}

See the Properties and Flags page for more information.

@WatchProp()

function WatchProp(property_name: string): MethodDecorator

Method decorator that will run its method whenever the specified property changes.

The method will be called asynchronously on idle after class instantiation, and will be ran any time the property's value changes.

@GClass()
class MyBox extends from(Gtk.Box, {
	title: Property.string(),
}) {
	@WatchProp("title")
	#on_title_changed(): void {
		print(`My title is: ${this.title}`)
	}
}

const mb = new MyBox({ title: "File Size" }) // "My title is: File Size" prints on next idle
// Some time later
mb.title = "User Name"

Note: the property name is exactly as it appears in the from function. This means that underscores are permitted, and will be automatically converted to hyphens at runtime.

See the Properties and Flags page for more information.

@OnSignal()

function OnSignal(signal_name: string): MethodDecorator

Method decorator that will connect its method to the signal an instance of the class. The signal name must be a built-in signal on the class, or a user provided signal in from().

See the Signals Page for more information.

@GClass()
class MyButton extends from(Gtk.Button, {
	state_set: Signal(Number),
}) {
	@OnSignal("state-set") // Connect to a new signal
	#on_state_set(new_state: number): void {
		print(`State has been set to: ${new_state}`)
	}

	@OnSignal("clicked") // Connect to a built-in signal
	#on_clicked(): void {
		print("I have been clicked!")
	}
}

@OnSimpleAction

function OnSimpleAction(action_name: string): MethodDecorator

Method decorator that will connect its method to the "activate" signal of the specified action. The action name must be a field on the class that is a Gio.SimpleAction.

See the Simple Actions page for more information.

@GClass()
class MyBox extends from(Gtk.Box, {
	refresh: SimpleAction(),
}) {
	@OnSimpleAction("refresh")
	#on_refresh(): void {
		print("reloading data!")
	}
}

@PostInit

function PostInit(): void

Method decorator that will run its method on idle after an instance of the class has been initialized.

More information on the initialization steps, and when things run, can be found in the Initialization Order page.

@GClass()
class MyBox extends from(Gtk.Box, {}) {
	constructor(params?: typeof MyBox.$params) {
		print("I am constructed!")
	}

	@PostInit
	ready(): void {
		print("I am ready!")
	}
}

// elsewhere
const mb = new MyBpx() // prints "I am constructed!"
// on next idle, prints "I am ready!"

Clone this wiki locally