MultiselectJS API

Table of Contents

1 Introduction

This document describes the API of the MultiselectJS library. The document assumes understanding of the following concepts: selection geometry, selection mapping, selection domain, active set, selection path, anchor, active end, and keyboard cursor. Please read the MultiselectJS tutorial or/and the paper One Way to Select Many if these concepts are not familiar to you.

The library components are defined as CommonJS modules. They are multiselect.js, default_geometry.js, dom_geometries.js, and ordered_geometries.js. The main module is multiselect.js. The other modules define selection geometry classes, intended to be used as base classes for client-defined selection geometries. The main module depends on (requires) default_geometry.js, but the latter is still defined as separate module. This is so that modules that define selection geometries do not need to require multiselect.js; requiring default_geometry.js suffices.

2 multiselect.js

The names exported by multiselect.js are:

SelectionState 2.1
DefaultGeometry 2.2.1
anchor, activeEnd 2.3.1
makeEmptyMap 2.3.1
UP, DOWN, LEFT, RIGHT, NO_DIRECTION 2.3.2
modifierKeys 2.3.3
NONE, SHIFT, CMD, SHIFT_CMD, OPT, SHIFT_OPT 2.3.3

2.1 The SelectionState class

The SelectionState class maintains all of the state of the selection, which includes the selection status of each element, the current selection path, undo and redo stacks, and keyboard cursor.

2.1.1 Construction

The SelectionState class has one constructor:

SelectionState(geometry, refresh, tracking, maxundo)
  • geometry is the selection geometry. This object must satisfy the requirements laid out in Section 2.2.
  • refresh(s) is a callback function that visualizes the current selection state of the elements. With what kind of argument the library invokes the callback depends on the value of tracking:
    • if tracking === false, s is a selection mapping: a function from element indices to booleans. For all element indices i, s(i) is true if i is selected in s, otherwise false.

      Assuming the element indices are indices to an array named elements and that the function visualize(e, b) shows the element e as selected or not, depending on whether b is true or false. A simple refresh implementation could then be:

      function refresh(s) {
        for (var i=0; i<elements.length; ++i) visualize(elements[i], s(i));
      }
      
    • if tracking === true, s is a built-in Map (ECMAScript 6) whose keys are the selectable elements’ indices and values are booleans. The map has entries for exactly those elements that were changed since the previous time any of the SelectionState object’s methods called refresh (or since the object’s construction).

      With the same assumptions as above, a simple refresh implementation could be:

      function refresh(s) {
        s.forEach(function (value, i) { visualize(elements[i], value); });
      }
      
  • tracking controls whether change tracking should be used or not (the default is false: no tracking)
  • maxundo is the maximum number of undo operations. The default is 10, the minimum is 1 (smaller values are ignored).

2.1.2 Reset

SelectionState.reset();
  • Resets the SelectionState object to the same state in which it was immediately after construction.

2.1.3 Accessing the selection state of elements

Access to elements’ selection state is provided by three functions: isSelected, selected, and onSelected.

SelectionState.isSelected(i);
  • The parameter i is an index to an element. Returns true if the element is selected, false if not.
SelectionState.selected();
  • Returns the indices of the selected elements as a Set (ECMAScript 6).
SelectionState.onSelected(vpoint);
  • Returns true if vpoint is on exactly one element and that element is selected. Concretely, the result is computed as follows. A variable path is set to [], geometry().extendPath(path, vpoint) is invoked for its effect on path, and then the result of geometry().selectionDomain(path) is observed. If that result has exactly one element and that element is selected, then onSelected(vpoint) returns true, otherwise false.

2.1.4 Accessing selection geometry

SelectionState.geometry();
  • Returns a reference to the current selection geometry.
SelectionState.setGeometry(geometry);
  • Replaces the current selection geometry with geometry. Prior to setting the new geometry, the current active set or filter is committed.

2.1.5 Accessing selection path and cursor

The selection path and cursor can be queried:

SelectionState.selectionPath();
SelectionState.cursor();
  • These functions return a reference to the current, respectively, selection path and keyboard cursor.

The current selection path can be reset to the empty path. This function needs to be called if the locations of elements change and thus impact the result of the selectionDomain method.

SelectionState.resetPath();
  • Commits the current active set (whether it was constructed by one of the clicks or by a filter). Resets the current selection path to [] and keyboard cursor to undefined.

2.1.6 Click functions

All click functions take a vpoint parameter, which is a selection space coordinate. The click and cmdClick are quite similar:

SelectionState.click(vpoint);
SelectionState.cmdClick(vpoint, selmode);
  • The current active set is committed.
  • click unselects all currently selected elements; cmdClick does not.
  • The current selection path _spath is set to []
  • geometry().extendPath(_spath, vpoint) is called.
  • If extendPath returns anything other than null, the keyboard cursor is set to vpoint.
  • The new selection domain is computed with the call selectionPath(_spath, undefined).
  • The refresh callback is called (if there are no elements whose selection state changes, the call might not be made).
  • The selmode parameter is a boolean and it determines whether cmdClick should select or deselect elements. Typically it is undefined, in which case cmdClick function selects if vpoint is on a selected element, and deselects if not.
SelectionState.shiftClick(vpoint);
  • geometry().extendPath(_spath, vpoint) is called.
  • If extendPath returns null, there is no effect (other than the possible modifying of _spath).
  • Otherwise, the keyboard cursor is set to the last element of (the modified) _spath.
  • The new selection domain is computed with the call selectionDomain(_spath, J), where J is the current selection path.
  • The refresh callback is called (if there are no elements whose selection state changes, the call might not be made).

The shiftClick function does not execute atomically. After it has has modified the selection path, it schedules the rest of its tasks as a separate function to be executed later (at timeout 0)—unless such a function has already been scheduled. The selection path can thus be extended (or otherwise modified) by extendPath calls several times in between two calls to selectionDomain().

2.1.7 Undo and redo

The undo and redo functionality is provided by undo and redo methods.

SelectionState.undo();
  • The effect of the most recent click, keyboard command, or committed filter operation is undone.
  • The selection path is set to [].
  • The refresh function is invoked.

The undo method does not modify the keyboard cursor.

SelectionState.undo();
  • The effect of the most recent call to undo is undone.
  • The selection path is set to [].
  • The refresh function is invoked.

The redo method does not modify the keyboard cursor.

2.1.8 Filtering

The filter function computes a selection domain based on a predicate over the element indices.

SelectionState.filter(predicate, state);
  • The selection path is set to [].
  • If state is defined and false, then the filter is set to deselect, otherwise to select.
  • A new active set is computed with geometry().filter(predicate).
  • If another filter is already active, and the new filter and the current filter are both set to select or both to deselect, then the current active set is replaced with the new one. Otherwise the current active set is committed before setting the new active set.
SelectionState.commit();
  • The current active set is committed, which creates an undoable state.

2.1.9 Functions for keyboard commands

The library provides three functions that select elements based on the current keyboard cursor position (space, cmdSpace, and shiftSpace), and three functions that alter the cursor position, and/or select elements (arrow, shiftArrow, and cmdArrow).

SelectionState.space();
SelectionState.cmdSpace();
SelectionState.shiftSpace();
  • If the cursor is not defined, these functions attempt to establish a cursor from the geometry’s default, using the call geometry().defaultCursor(NO_DIRECTION).
  • If the cursor is or becomes defined, the corresponding click(cursor), cmdClick(cursor), or shiftClick(cursor) function is called; otherwise there is no effect.
  • The cmdSpace and shiftSpace functions take a parameter (direction) that is used internally by the library; client calls to these functions should have no arguments.
SelectionState.arrow(dir);
SelectionState.shiftArrow(dir);
SelectionState.cmdArrow(dir);
  • The dir parameter must be one of the library’s constants UP, DOWN, LEFT, or RIGHT.
  • If the cursor is defined, a new cursor is computed by geometry().step(dir, c), where c is the current cursor. The shiftArrow function invokes cmdSpace prior to updating the cursor whereas the cmdArrow function invokes cmdSpace after updating the cursor.
  • If the cursor is not defined, these functions try to establish a cursor with the call geometry().defaultCursor(dir). If a cursor can be established (defaultCursor(dir) returns something other than undefined), then shiftArrow calls shiftSpace(dir) and cmdArrow calls cmdSpace(dir). If a cursor cannot be established, there is no effect.

2.2 Selection geometry objects

Typically classes that define selection geometries inherit from the defaultGeometry class discussed in Section 2.2.1.

A selection geometry object must define the following methods, conforming to the requirements listed below. All of the methods, except m2v, are callbacks for the library and not intended to be called by the client.

m2v(mpoint)
  • Transforms mpoint, a point in mouse coordinates, to a point in selection space.
  • This function is never called by any of the SelectionState’s methods, rather it should be called by the client in the event handlers of the various click events, prior to invoking click, cmdClick, of shiftClick functions. We make m2v a method of the selection geometry and insist that m2v function is used to perform the coordinate transformation so that event handling code could be reused across selection geometries.
extendPath(spath, vpoint)
  • Extends the selection path spath with the selection space point vpoint.
  • To communicate results to the caller, extendPath(spath, vp) modifies the spath parameter. If spath is not modified at all, extendPath(spath, vp) should return null.
  • The spath parameter is an array of selection state points. Typically vpoint is added at the end of spath, but this is not a requirement: extendPath is allowed to modify spath array in arbitrary ways. For example, certain values of vpoint could be interpreted as commands that remove or move points in spath.
  • Hint: spath can be non-empty only when invoked from shiftClick. Two consecutive calls to extendPath where spath is non-empty can only come from two consecutive calls to shiftClick.
selectionDomain(spath, J)
  • This function should compute the selection domain that spath gives rise to.
  • If J is not undefined, it is the current selection domain (computed by the previous call to selectionDomain). The object referenced to by J can be used as such, and modified and returned as the result. The purpose of making the previous selection domain available is so that the selection geometry can compute the new selection domain faster. For example, in a “snake” geometry, the selection geometry could cache the index of the last element of the selection path, so that on the next call to selectionDomain it suffices to inspect points from that index forward.
  • The selectionDomain function is called from click, cmdClick, shiftClick, and onSelected functions. The J parameter is defined only when called from shiftClick.
  • Two consecutive calls to selectionDomain where J is defined can only come from two consecutive calls to shiftClick that operate on the same active set. If J is not defined, cached values should be cleared.
step(dir, vpoint)
  • The function should compute a new point that is one “step” to the given direction from vpoint. The function defines the effect of each arrow key on the cursor position.
  • The dir parameter’s value is one of the library’s constants UP, DOWN, LEFT, or RIGHT. If movement to some direction is not supported, step should return vpoint.
  • The library never calls step with undefined value for vpoint.
defaultCursor(dir)
  • This function should return the default location of the keyboard cursor for each dir value. If there is no default for some direction, the return value must be undefined.
  • dir is one of UP, DOWN, LEFT, or RIGHT, NO_DIRECTION. The NO_DIRECTION value indicates that defaultCursor was called from one of space, shiftSpace, or cmdSpace functions. The other four values indicate that it was called from one of the arrow functions.
filter(pred)
  • This function should return a Map whose keys are the indices i for which pred(i) is true. The value for each such index in the map should be true.

2.2.1 DefaultGeometry

To help the client in defining selection geometries, MultiselectJS defines the DefaultGeometry class as follows:

DefaultGeometry.prototype = {
  m2v : function (mp) { return mp; },
  extendPath : function (spath, vp) { spath.push(vp); },
  step : function (dir, vp) { return undefined; },
  selectionDomain : function(spath, J) { 
    var m = makeEmptyMap();
    for (var i of spath) m.set(i, true); 
    return m;
  },
  defaultCursor : function(dir) { return undefined; },
  filter : undefined
};

2.3 Helper definitions

2.3.1 Helpers for selection paths and selection domains

function anchor(path)
  • The anchor function returns the first element of the path array, or undefined if path is empty.
function activeEnd(path)
  • The activeEnd function returns the last element of the path array, or undefined if path is empty.
function makeEmptyMap()
  • The selectionDomain(path, J) method in the selection geometry classes may need to construct a new selection domain. The makeEmptyMap function constructs the kind of object (a built-in Map) that the library expects.

2.3.2 Constants for arrow key directions

The constants that specify the directions of the four arrow keys are

UP;
DOWN;
LEFT;
RIGHT;
NO_DIRECTION;

The first four should be used in implementing the selection geometry’s step function and used as parameters to SelectionState’s arrow, shiftArrow, and cmdArrow functions when they are called from client’s event handlers. The fifth value NO_DIRECTION can be recognized in the defaultCursor function to give a default cursor position when the space functions are invoked with an undefined cursor.

2.3.3 Helpers for event handlers

To figure out which modifier keys were pressed during a shift-click or a keypress is a little tricky. MultiselectJS implements the logic in the modifierKeys(event) function.

modifierKeys(event)

The event is assumed to be JavaScript’s KeyboardEvent or MouseEvent. The table below shows how different key combinations are mapped to the result. The first matching combination is selected (so that, e.g., shift+cmd+opt is interpreted as shift+cmd).

Modifiers Return value
shift+cmd SHIFT_CMD
cmd CMD
shift+opt SHIFT_OPT
opt OPT
shift SHIFT
anything else NONE

The cmd modifier in OSX corresponds to the ctrl modifier in Windows/Linux, and the opt modifier corresponds to alt.

The constants NONE, SHIFT, CMD, SHIFT_CMD, OPT, and SHIFT_OPT are part of the public API of the library.