Custom interfaces can be defined in GJS, although this is rarely done
Interfaces are defined in GJS by inheriting from GObject.Interface
and providing the class definition property Requires
. The general rules for interfaces defined in GJS are:
-
Methods
Methods defined on an interface must be implemented, if the method throws the special error
GObject.NotImplementedError()
. Methods that do not throw this error are optional to implement.Note that unlike GObject Interfaces defined by a C library, methods are overridden directly rather than by virtual function.
-
Properties
Properties defined on an interface must be implemented, using
GObject.ParamSpec.override()
in theProperties
class definition property. -
Signals
Signals defined on an interface do not need to be implemented. Typically interface definitions will provide emitter methods, such as
Gio.ListModel.items_changed()
, otherwise they can be emitted by callingGObject.Object.prototype.emit()
on an instance. -
Requirements
Interfaces can define prerequisites for implementation, using the
Requires
class definition property. Usually this is simplyGObject.Object
, but interfaces may require an implementation to implement more than one interface.
Below is a simple example of defining an interface that only requires GObject.Object
:
const GObject = imports.gi.GObject;
const SimpleInterface = GObject.registerClass({
GTypeName: 'SimpleInterface',
Requires: [GObject.Object],
Properties: {
'simple-property': GObject.ParamSpec.boolean(
'simple-property',
'Simple property',
'A property that must be implemented',
GObject.ParamFlags.READABLE,
true
),
},
Signals: {
'simple-signal': {},
},
}, class SimpleInterface extends GObject.Interface {
/**
* By convention interfaces provide methods for emitting their signals, but
* you can still call `emit()` on the instance of an implementation.
*/
emitSimple() {
this.emit('simple-signal');
}
/**
* Interfaces can define methods that MAY be implemented, by providing a
* default implementation.
*/
optionalMethod() {
return true;
}
/**
* Interfaces can define methods that MUST be implemented, by throwing the
* special error `GObject.NotImplementedError()`.
*/
requiredMethod() {
throw new GObject.NotImplementedError();
}
});
Note that unlike with interfaces defined by C libraries, we override methods like requiredMethod()
directly, not vfunc_requiredMethod()
. Below is a minimal implementation of SimpleInterface
:
const GObject = imports.gi.GObject;
const SimpleImplementation = GObject.registerClass({
Implements: [SimpleInterface],
Properties: {
'simple-property': GObject.ParamSpec.override('simple-property',
SimpleInterface),
},
}, class SimpleImplementation extends GObject.Object {
get simple_property() {
return true;
}
requiredMethod() {
log('requiredMethod() implemented');
}
});
Instances of the implementation can then be constructed like any class. instanceof
can be used to confirm it is both a GObject
and implements a given interface:
let simpleInstance = new SimpleImplementation();
if (simpleInstance instanceof GObject.Object)
log('An instance of a GObject');
if (simpleInstance instanceof SimpleInterface)
log('An instance implementing SimpleInterface');
More complex interfaces can also be defined that depend on other interfaces. ComplexInterface
depends on Gio.ListModel
and SimpleInterface
, while adding a property and a method.
const {Gio, GObject} = imports.gi;
const ComplexInterface = GObject.registerClass({
GTypeName: 'ComplexInterface',
Requires: [Gio.ListModel, SimpleInterface],
Properties: {
'complex-property': GObject.ParamSpec.boolean(
'complex-property',
'Complex property',
'A property that must be implemented',
GObject.ParamFlags.READABLE,
true
),
},
}, class ComplexInterface extends GObject.Interface {
complexMethod() {
throw new GObject.NotImplementedError();
}
});
An implementation of this interface must then meet the requirements of Gio.ListModel
and SimpleInterface
, which both require GObject.Object
. The following implementation of ComplexInterface
will meet the requirements of:
GObject.Object
by inheriting fromGio.ListStore
, aGObject.Object
-based classGio.ListModel
by inheriting fromGio.ListStore
, which implementsGio.ListModel
SimpleInterface
by implementing its methods and properties
const {Gio, GObject} = imports.gi;
const ComplexImplementation = GObject.registerClass({
Implements: [Gio.ListModel, SimpleInterface, ComplexInterface],
Properties: {
'complex-property': GObject.ParamSpec.override('complex-property',
ComplexInterface),
'simple-property': GObject.ParamSpec.override('simple-property',
SimpleInterface),
},
}, class ComplexImplementation extends Gio.ListStore {
get complex_property() {
return false;
}
get simple_property() {
return true;
}
complexMethod() {
log('complexMethod() implemented');
}
requiredMethod() {
log('requiredMethod() implemented');
}
});
By using instanceof
, we can confirm the inheritance and interface support of the implementation:
let complexInstance = new ComplexImplementation();
if (complexInstance instanceof GObject.Object &&
complexInstance instanceof Gio.ListStore)
log('An instance with chained inheritance');
if (complexInstance instanceof Gio.ListModel &&
complexInstance instanceof SimpleInterface &&
complexInstance instanceof ComplexInterface)
log('An instance implementing three interfaces');