Skip to content

Advanced jQuery Terminal Tutorial

Jakub T. Jankiewicz edited this page Sep 8, 2024 · 39 revisions
  1. Interpreter
  2. Canceling Ajax Requests
  3. Displaying the content on the terminal
  4. Events
  5. Authentication
  6. Webpack and Babel
  7. Shortcuts
  8. Saving State
  9. Mobile Web Terminal and Responsive Text
  10. Scrolling
  11. Extensions
  12. CSS Style
  13. Strings

NOTE: There is a task to create a proper tutorial that will show you step-by-step how to create a Terminal Based Resume. It's tracked in #801 the plan is to pack all advanced features + more ideas into one big article.

Interpreter

The interpreter is the first argument of jQuery plugin invocation:

$('body').terminal(<interpreter>, { <options> });

The interpreter can be created in few different ways, the first argument is overloaded and allow passing function, object (including nested object), string, or Array of function object and strings.

Function

The function is a more low-level, but versatile version of the interpreter. It accepts the whole command as a string. It's useful if you want to parse the command yourself. You can use some interpreter like SQL or JavaScript that passes the whole expression to that interpreter. You can also parse the commands with tools provided by jQuery Terminal like:

  • $.terminal.parse_command
  • $.terminal.split_command

Those two functions are very similar except the second one that converts arguments into values. parse_command will convert numbers like strings into numbers and regex like strings into regular expression objects. Both functions remove quotes from strings.

The function return object:

{
   "name": "command",
   "args": [<arguments>],
   "args_quotes": [<Array of quote characters from string>],
   "rest": "<arguments as string>",
}

There are also functions for converting arguments into an array:

  • $.terminal.parse_arguments
  • $.terminal.split_arguments

Both pairs of functions handle spaces inside strings, escaped spaces and escaped quotes, So they return proper parsed output like bash would do it.

Example:

$('body').terminal(function(command) {
   var cmd = $.terminal.parse_command(command);
   if (cmd.name === 'add') {
      this.echo(cmd.args.reduce((a, b) => a + b));
   }
});

when called with: add 1 2 3 4 5 it will return 15.

More about parsing in Parsing commands.

String

You can use string as an interpreter (also in Array and Object) the string need to be a URL that points to a valid JSON-RPC service. If the service provide system.describe (defined in 1.1 draft of JSON-RPC) you can use auto-completion without any extra work. If you don't want to implement non-standard system.describe, you can provide a normal method (with the same name) that returns standard JSON-RPC response, but then you will need to use the option that tells jQuery Terminal where he can find the definition of procedures that are provided by the JSON-RPC service. To do that you can use:

{
   describe: "result.procs"
}

if the JSON-RPC method system.describe return object {procs: ...} as described by JSON-RPC 1.1.

If you don't want to use auto-discovery you can use the option:

{
   ignoreSystemDescribe: true
}

NOTE: There is an issue #619 to support Open-RPC.

Object

The object can have functions that will be executed when a command with a specific name is invoked, it can be another object that creates a nested interpreter (for example MySQL command, that accept SQL commands) or it can be a string that creates a nested interpreter from JSON-RPC service.

By default when using objects the arguments are parsed, so a number like strings are real numbers, regular expressions are regexes and true/false are real values. To disable this behavior use option: processArguments: false.

var stack = [];
$('body').terminal({
  stack: {
    push: function(arg) {
      stack.push(arg);
    },
    pop: function() {
      return stack.pop();
    }
  },
  sudo: function(command, ...args) {
    switch(command) {
      case 'ls':
        list_directory(...args);
        break;
      case 'rm':
        remove_file(...args);
        break;
      case 'mv':
        move_file(...args);
        break;
      default:
        this.error('Sudo what?');
    }
  },
  rpc: "service.py"
}, {
  checkArity: false
});

This example creates three commands

  • sudo that allows executing a command with arguments, variadic functions require to add option checkArity that is set to true by default and don't allow to execute functions with more than the required number of arguments.
  • stack that creates simple nested command, when you type stack it will change the prompt to stack> and allow executing two commands push and pop that is an interface to stack data structure created from Array.
  • rpc command will change the prompt to rpc> and allow to execute commands that are provided by the given URL here it expects that in the same directory as the HTML file there is service.py that is a server-side script that is the implementation of JSON-RPC.

Array

With an array, there is always created single object, created by merging explicit object or string that point to JSON-RPC service that have system.describe, that allows creating object-based on procedures in that request. Besides objects created from Array, there can only be a single function that is executed as a fallback if no commands were found. Also if JSON-RPC doesn't support system.describe method it's handled like a single function.

Completion

If an object is created from at least one object (or JSON-RPC service that provide system.describe) then you can use automatic completion by adding the option

{
  completion: true
}

Details can be found in Tab-completion page.

Canceling AJAX Requests

Since early stage the library canceled AJAX requests (using CTRL+D shortcut) when using jQuery $.ajax or $.get, but since modern way is to use fetch API, in version 2.43.0 the library added Abort signals API.

There are two new methods that return signals cancelable by CTRL+D or with terminal::abort()

  • terminal::timeout()
  • terminal::signal()

Here is an example how to use timeout signal, that you can cancel before the timeout with CTRL+D:

$('body').terminal(async () => {
   const signal = this.timeout(500);
   const res = await fetch('./api?command=' + encodeURIComponent(command), { signal });
   const json = await res.json();
   this.echo(json.result);
});

When signal is aborted, you will get a red message displayed:

[Command] Signal timed out

If you want to hide this error message, you can use settings: errorOnAbort: false. As with other strings you can also change it, there are two new strings:

  • abortError: 'Abort with CTRL+D'
  • timeoutError: 'Signal timed out'

With terminal::abort() you can also provide your own reason to cancel the requests:

term.abort("I want to cancel");

Cancel Custom Jobs

If you do any expensive processing, you can also cancel it with signals. For this, you need to implement your code that utilize signals:

function myCoolPromiseAPI(/* …, */ { signal }) {
  return new Promise((resolve, reject) => {
    // If the signal is already aborted, immediately throw in order to reject the promise.
    if (signal.aborted) {
      reject(signal.reason);
    }

    // Perform the main purpose of the API
    // Call resolve(result) when done.

    // Watch for 'abort' signals
    signal.addEventListener("abort", () => {
      // Stop the main operation
      // Reject the promise with the abort reason.
      reject(signal.reason);
    });
  });
}

The above example is taken from MDN.

You can use this code similar to fetch:

$('body').terminal(async () => {
   const signal = this.signal();
   const response = await myCoolPromiseAPI(/* …, */ { signal });
   this.echo(response);
});

Displaying the content on the terminal

The main function for displaying the content of the terminal is the echo method. It has a lot of advanced functions.

TODO: