2

I am trying to write a GNOME shell extension. I have a class which extends a GNOME shell type. Whenever I call class method on this subclass, it throws an error. If I do not extend the class, then the method is invoked without a problem.

Example:

This works

var Dummy = class Dummy {
  constructor() {}
  foo() {
    log("foo!")
  }
};

let d = new Dummy();
d.foo();

--> RESULT: log shows "foo!"

However, this does not work

const St = imports.gi.St;
var Dummy = class Dummy extends St.Bin {
  constructor() {
    super();
  }
  foo() {
    log("foo!")
  }
};

let d = new Dummy();
d.foo();

--> RESULT: TypeError: d.foo is not a function

I'm not very proficient in Javascript, and I'm having trouble Googling my way out of this situation.

Any help is appreciated. Thanks!

poho
  • 21
  • 1

1 Answers1

1

Unfortunately there is a bit of mix of Class styles, for couple reasons, but namely because GJS existed before ES6 classes. You should avoid usage of the Lang module if at all possible.

Sometimes it won't be clear if the object you're subclassing is a native JS class or a GObject. Any object coming from imports.gi (GObject Introspection) will be a GObject, while objects coming from say imports.ui.popupMenu (an import in GNOME Shell) could be either and you may have to check the source.

If you are subclassing a GObject, this is the proper way to subclass:

var Bin = GObject.registerClass({

    // This is optional, but useful to avoid namespace collisions. By
    // convention you prefix your GType's like StBin or GtkBin.
    GTypeName: 'PohoBin',

    // Custom GProperties
    Properties: {
        'example-prop': GObject.ParamSpec.string(
            'example-prop',                     // property name
            'ExampleProperty',                  // nickname
            'An example read write property',   // description
            (GObject.ParamFlags.READWRITE |     // READABLE/READWRITE/CONSTRUCT/etc
             GObject.ParamFlags.CONSTRUCT),
            null                                // implement defaults manually
        )

    }
}, class Bin extends St.Bin {

    // Currently GObjects use `constructor()` for bootstrapping; you
    // should not call this function in GObject subclasses.
    //
    // Instead, you should chain-up to the parent class's _init() function,
    // which takes a dictionary of properties
    _init(params = {}) {
        // Chaining up. If you need to, you can use this opportunity to
        // pull properties from the @params dictionary
        super._init(params);
    }

    get example_prop() {
        if (this._example_prop === undefined) 
            this._example_prop = null;

        return this._example_prop;
    }

    set example_prop(value) {
        if (this.example_prop !== value) {
            this._example_prop = value;
            this.notify('example-prop');
        }
    }

    foo() {
        log(this.example_prop);
    }
});


let obj = new Bin({
    visible: true,            // Clutter.Actor property
    x_align: St.Align.MIDDLE, // St.Bin property
    example_prop: 'example'   // custom property
});

obj.foo();
// expected output: 'example'

There is more information about mapping GObject to JavaScript here:

https://gitlab.gnome.org/GNOME/gjs/wikis/Mapping

There are also a number of more complete examples here:

https://gitlab.gnome.org/GNOME/gjs/tree/master/examples

andy.holmes
  • 3,383
  • 17
  • 28