2024-01-16 20:43:03 +01:00
# JS Coding Style
2011-10-07 19:46:19 -04:00
Our goal is to have all JavaScript code in GNOME follow a consistent style. In
a dynamic language like JavaScript, it is essential to be rigorous about style
(and unit tests), or you rapidly end up with a spaghetti-code mess.
2018-05-23 17:55:43 +02:00
## A quick note
2011-10-07 19:46:19 -04:00
Life isn't fun if you can't break the rules. If a rule seems unnecessarily
restrictive while you're coding, ignore it, and let the patch reviewer decide
what to do.
2018-10-14 14:05:04 +02:00
## Indentation, braces and whitespace
2011-10-07 19:46:19 -04:00
2018-10-14 14:05:04 +02:00
* Use four-space indents.
* Braces are on the same line as their associated statements.
* You should only omit braces if *both* sides of the statement are on one line.
* One space after the `function` keyword.
* No space between the function name in a declaration or a call.
* One space before the parens in the `if` statements, or `while` , or `for` loops.
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
```javascript
2011-10-07 19:46:19 -04:00
function foo(a, b) {
let bar;
if (a > b)
bar = do_thing(a);
else
bar = do_thing(b);
2024-01-26 00:30:25 +01:00
if (bar === 5) {
2019-11-25 20:17:38 +01:00
for (let i = 0; i < 10 ; i + + )
2011-10-07 19:46:19 -04:00
print(i);
} else {
print(20);
}
}
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## Semicolons
2011-10-07 19:46:19 -04:00
JavaScript allows omitting semicolons at the end of lines, but don't. Always
end statements with a semicolon.
2018-05-23 17:55:43 +02:00
## js2-mode
2011-10-07 19:46:19 -04:00
If using Emacs, do not use js2-mode. It is outdated and hasn't worked for a
while. emacs now has a built-in JavaScript mode, js-mode, based on
espresso-mode. It is the de facto emacs mode for JavaScript.
2018-05-23 17:55:43 +02:00
## File naming and creation
2011-10-07 19:46:19 -04:00
For JavaScript files, use lowerCamelCase-style names, with a `.js` extension.
We only use C where gjs/gobject-introspection is not available for the task, or
where C would be cleaner. To work around limitations in
gjs/gobject-introspection itself, add a new method in `shell-util.[ch]` .
Like many other GNOME projects, we prefix our C source filenames with the
library name followed by a dash, e.g. `shell-app-system.c` . Create a
`-private.h` header when you want to share code internally in the
library. These headers are not installed, distributed or introspected.
2018-05-23 17:55:43 +02:00
## Imports
2011-10-07 19:46:19 -04:00
Use UpperCamelCase when importing modules to distinguish them from ordinary
variables, e.g.
2018-05-23 17:55:43 +02:00
```javascript
2023-06-07 23:22:00 -07:00
import GLib from 'gi://GLib';
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
Imports should be categorized into one of two places. The top-most import block
should contain only "environment imports". These are either modules from
gobject-introspection or modules added by gjs itself.
The second block of imports should contain only "application imports". These
are the JS code that is in the gnome-shell codebase,
2023-08-07 17:05:46 +02:00
e.g. `'./popupMenu.js'` .
2011-10-07 19:46:19 -04:00
Each import block should be sorted alphabetically. Don't import modules you
don't use.
2018-05-23 17:55:43 +02:00
```javascript
2023-08-07 17:05:46 +02:00
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import St from 'gi://St';
2011-10-07 19:46:19 -04:00
2023-08-07 17:05:46 +02:00
import * as Main from './main.js';
import * as Params from '../misc/params.js';
import * as Util from '../misc/util.js';
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
The alphabetical ordering should be done independently of the location of the
location. Never reference `imports` in actual code.
2018-05-23 17:55:43 +02:00
## Constants
2011-10-07 19:46:19 -04:00
We use CONSTANTS_CASE to define constants. All constants should be directly
under the imports:
2018-05-23 17:55:43 +02:00
```javascript
2011-10-07 19:46:19 -04:00
const MY_DBUS_INTERFACE = 'org.my.Interface';
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## Variable declaration
2011-10-07 19:46:19 -04:00
Always use either `const` or `let` when defining a variable.
2018-05-23 17:55:43 +02:00
```javascript
2011-10-07 19:46:19 -04:00
// Iterating over an array
2024-01-26 00:30:25 +01:00
for (let i = 0; i < arr.length ; + + i ) {
const item = arr[i];
}
2011-10-07 19:46:19 -04:00
// Iterating over an object's properties
2024-01-26 00:30:25 +01:00
for (const prop in someobj) {
const val = someobj[prop];
2011-10-07 19:46:19 -04:00
}
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
If you use "var" then the variable is added to function scope, not block scope.
See [What's new in JavaScript 1.7 ](https://developer.mozilla.org/en/JavaScript/New_in_JavaScript/1.7#Block_scope_with_let_%28Merge_into_let_Statement%29 )
2018-05-23 17:55:43 +02:00
## Classes
2011-10-07 19:46:19 -04:00
2018-11-27 23:03:07 +01:00
There are many approaches to classes in JavaScript. We use standard ES6 classes
whenever possible, that is when not inheriting from GObjects.
2018-05-23 17:55:43 +02:00
```javascript
2023-06-07 23:22:00 -07:00
export class IconLabelMenuItem extends PopupMenu.PopupMenuBaseItem {
2018-11-27 23:03:07 +01:00
constructor(icon, label) {
2024-01-25 23:15:42 +01:00
super({reactive: false});
2013-07-15 20:00:41 -04:00
this.actor.add_child(icon);
this.actor.add_child(label);
2018-11-27 23:03:07 +01:00
}
2011-10-07 19:46:19 -04:00
2017-10-31 01:03:21 +01:00
open() {
2024-01-25 23:15:42 +01:00
log('menu opened!');
2011-10-07 19:46:19 -04:00
}
2024-01-25 23:15:42 +01:00
}
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
2020-08-19 11:26:11 +02:00
For GObject inheritance, we use the GObject.registerClass() function provided
2018-11-27 23:03:07 +01:00
by gjs.
```javascript
2023-06-07 23:22:00 -07:00
export const MyActor = GObject.registerClass(
2018-11-27 23:03:07 +01:00
class MyActor extends Clutter.Actor {
2024-01-25 23:15:42 +01:00
constructor(params) {
super(params);
2011-10-07 19:46:19 -04:00
2018-11-27 23:03:07 +01:00
this.name = 'MyCustomActor';
}
});
```
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## GObject Introspection
2011-10-07 19:46:19 -04:00
GObject Introspection is a powerful feature that allows us to have native
bindings for almost any library built around GObject. If a library requires
you to inherit from a type to use it, you can do so:
2018-05-23 17:55:43 +02:00
```javascript
2023-06-07 23:22:00 -07:00
export const MyClutterActor = GObject.registerClass(
2018-11-27 23:03:07 +01:00
class MyClutterActor extends Clutter.Actor {
2019-01-31 22:07:38 +01:00
vfunc_get_preferred_width(forHeight) {
2024-01-25 23:15:42 +01:00
return [100, 100];
2018-11-27 23:03:07 +01:00
}
2011-10-07 19:46:19 -04:00
2019-01-31 22:07:38 +01:00
vfunc_get_preferred_height(forWidth) {
2024-01-25 23:15:42 +01:00
return [100, 100];
2018-11-27 23:03:07 +01:00
}
2011-10-07 19:46:19 -04:00
2019-11-28 11:19:27 +01:00
vfunc_paint(paintContext) {
2024-01-25 23:15:42 +01:00
let framebuffer = paintContext.get_framebuffer();
let coglContext = framebuffer.get_context();
let alloc = this.get_allocation_box();
2019-11-28 11:19:27 +01:00
2024-01-25 23:15:42 +01:00
let pipeline = Cogl.Pipeline.new(coglContext);
pipeline.set_color4ub(255, 0, 0, 255);
2019-11-28 11:19:27 +01:00
2024-01-25 23:15:42 +01:00
framebuffer.draw_rectangle(pipeline,
alloc.x1, alloc.y1,
alloc.x2, alloc.y2);
2011-10-07 19:46:19 -04:00
}
});
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## Translatable strings, `environment.js`
2011-10-07 19:46:19 -04:00
We use gettext to translate the GNOME Shell into all the languages that GNOME
supports. The `gettext` function is aliased globally as `_` , you do not need to
explicitly import it. This is done through some magic in the
[environment.js ](http://git.gnome.org/browse/gnome-shell/tree/js/ui/environment.js )
file. If you can't find a method that's used, it's probably either in gjs itself
or installed on the global object from the Environment.
2019-05-28 08:30:10 +02:00
## `actor` (deprecated) and `_delegate`
2011-10-07 19:46:19 -04:00
gjs allows us to set so-called "expando properties" on introspected objects,
allowing us to treat them like any other. Because the Shell was built before
2019-05-28 08:30:10 +02:00
you could inherit from GTypes natively in JS, in some cases we have a wrapper
class that has a property called `actor` (now deprecated). We call this
wrapper class the "delegate".
2011-10-07 19:46:19 -04:00
We sometimes use expando properties to set a property called `_delegate` on
the actor itself:
2019-05-28 08:30:10 +02:00
```javascript
2023-06-07 23:22:00 -07:00
export const MyActor = GObject.registerClass(
2019-05-28 08:30:10 +02:00
class MyActor extends Clutter.Actor {
2024-01-25 23:15:42 +01:00
constructor(params) {
super(params);
2019-05-28 08:30:10 +02:00
this._delegate = this;
}
});
```
Or using the deprecated `actor` :
2018-05-23 17:55:43 +02:00
```javascript
2023-06-07 23:22:00 -07:00
export class MyClass {
2018-11-27 23:03:07 +01:00
constructor() {
2024-01-25 23:15:42 +01:00
this.actor = new St.Button({text: 'This is a button'});
2011-10-07 19:46:19 -04:00
this.actor._delegate = this;
2017-12-02 01:27:35 +01:00
this.actor.connect('clicked', this._onClicked.bind(this));
2018-11-27 23:03:07 +01:00
}
2011-10-07 19:46:19 -04:00
2017-10-31 01:03:21 +01:00
_onClicked(actor) {
2024-01-25 23:15:42 +01:00
actor.set_label('You clicked the button!');
2011-10-07 19:46:19 -04:00
}
2024-01-25 23:15:42 +01:00
}
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
The 'delegate' property is important for anything which trying to get the
delegate object from an associated actor. For instance, the drag and drop
system calls the `handleDragOver` function on the delegate of a "drop target"
when the user drags an item over it. If you do not set the `_delegate`
property, your actor will not be able to be dropped onto.
2019-05-28 08:30:10 +02:00
In case the class is an actor itself, the `_delegate` can be just set to `this` .
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## Functional style
2011-10-07 19:46:19 -04:00
JavaScript Array objects offer a lot of common functional programming
capabilities such as forEach, map, filter and so on. You can use these when
they make sense, but please don't have a spaghetti mess of function programming
messed in a procedural style. Use your best judgment.
2018-05-23 17:55:43 +02:00
## Closures
2011-10-07 19:46:19 -04:00
`this` will not be captured in a closure, it is relative to how the closure is
invoked, not to the value of this where the closure is created, because "this"
is a keyword with a value passed in at function invocation time, it is not a
variable that can be captured in closures.
2017-12-02 01:27:35 +01:00
All closures should be wrapped with Function.prototype.bind or use arrow
notation.
2018-05-23 17:55:43 +02:00
```javascript
2019-11-25 20:17:38 +01:00
let closure1 = () => this._fnorbate();
2017-12-02 01:27:35 +01:00
let closure2 = this._fnorbate.bind(this);
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
A more realistic example would be connecting to a signal on a method of a
prototype:
2018-05-23 17:55:43 +02:00
```javascript
2023-08-07 17:05:46 +02:00
import * as FnorbLib from './fborbLib.js';
2011-10-07 19:46:19 -04:00
2023-06-07 23:22:00 -07:00
export class MyClass {
constructor() {
2011-10-07 19:46:19 -04:00
let fnorb = new FnorbLib.Fnorb();
2017-12-02 01:27:35 +01:00
fnorb.connect('frobate', this._onFnorbFrobate.bind(this));
2018-11-27 23:03:07 +01:00
}
2011-10-07 19:46:19 -04:00
2017-10-31 01:03:21 +01:00
_onFnorbFrobate(fnorb) {
2011-10-07 19:46:19 -04:00
this._updateFnorb();
}
2024-01-25 23:15:42 +01:00
}
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
## Object literal syntax
2011-10-07 19:46:19 -04:00
In JavaScript, these are equivalent:
2018-05-23 17:55:43 +02:00
```javascript
2024-01-25 23:15:42 +01:00
foo = {'bar': 42};
foo = {bar: 42};
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
and so are these:
2018-05-23 17:55:43 +02:00
```javascript
2024-01-25 23:15:42 +01:00
b = foo['bar'];
b = foo.bar;
2018-05-23 17:55:43 +02:00
```
2011-10-07 19:46:19 -04:00
If your usage of an object is like an object, then you're defining "member
2024-01-25 23:15:42 +01:00
variables." For member variables, use the no-quotes no-brackets syntax:
`{bar: 42}` `foo.bar` .
2011-10-07 19:46:19 -04:00
If your usage of an object is like a hash table (and thus conceptually the keys
2024-01-25 23:15:42 +01:00
can have special chars in them), don't use quotes, but use brackets:
`{bar: 42}` , `foo['bar']` .
2011-10-07 19:46:19 -04:00
2018-07-21 03:18:29 +02:00
## Animations
Most objects that are animated are actors, and most properties used in animations
are animatable, which means they can use implicit animations:
2011-10-07 19:46:19 -04:00
2018-05-23 17:55:43 +02:00
```javascript
2018-07-21 03:18:29 +02:00
moveActor(actor, x, y) {
actor.ease({
x,
y,
duration: 500, // ms
2024-01-25 23:15:42 +01:00
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
2018-07-21 03:18:29 +02:00
});
}
```
2011-10-07 19:46:19 -04:00
2018-07-21 03:18:29 +02:00
The above is a convenience wrapper around the actual Clutter API, and should generally
be preferred over the more verbose:
2011-10-07 19:46:19 -04:00
2018-07-21 03:18:29 +02:00
```javascript
moveActor(actor, x, y) {
actor.save_easing_state();
2011-10-07 19:46:19 -04:00
2018-07-21 03:18:29 +02:00
actor.set_easing_duration(500);
actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
actor.set({
x,
2024-01-25 23:15:42 +01:00
y,
2018-07-21 03:18:29 +02:00
});
actor.restore_easing_state();
}
```
There is a similar convenience API around Clutter.PropertyTransition to animate
actor (or actor meta) properties that cannot use implicit animations:
2011-10-07 19:46:19 -04:00
2018-07-21 03:18:29 +02:00
```javascript
desaturateActor(actor, desaturate) {
let factor = desaturate ? 1.0 : 0.0;
actor.ease_property('@effects .desaturate.factor', factor, {
duration: 500, // ms
2024-01-25 23:15:42 +01:00
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
2018-07-21 03:18:29 +02:00
});
}
2018-05-23 17:55:43 +02:00
```