MultiselectJS API

Jaakko Järvi (jarvi@cse.tamu.edu), Sean Parent

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 domain, selection path, anchor, active end, keyboard cursor, and selection storage. Please read the MultiselectJS tutorial and/or the paper One Way to Select Many if these concepts are not familiar to you.

The library consists of just one file: multiselect.js.

2 multiselect.js

The multiselect.js library defines one variable: multiselect, which defines the following members:

SelectionState 2.1
DefaultGeometry 2.2.1
anchor, activeEnd 2.4.1
UP, DOWN, LEFT, RIGHT, NO_DIRECTION 2.4.2
modifierKeys 2.4.3
NONE, SHIFT, CMD, SHIFT_CMD, OPT, SHIFT_OPT 2.4.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 that takes five parameters. All but the first, geometry, has a default value and can be left undefined.

SelectionState(geometry, refresh, tracking, maxundo, storage)
  • geometry is the selection geometry. This object must satisfy the requirements laid out in Section 2.2.
  • refresh(s, c) is a callback function that visualizes the current selection state of the elements. The first argument s is the selection state object. The value of the second argument depends on the value of tracking:

    • if tracking === false, c is undefined.
    • if tracking === true, c indicates the elements that were changed by the issued selection command. The type of c is determined by the selection storage object. By default it 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. The value of each entry gives the new selection state of the element. In other words, if the element i was changed, then c.get(i) is the new value of that element.

    The default value of refresh is the empty function that does nothing.

  • 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).
  • storage is the selection storage. This object must satisfy the requirements laid out in Section 2.3. The default value is a selection storage that uses Map to present selection domains: the keys are the indices in the domain.

2.1.2 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 defined by the selection storage. By default, the return value is a Map whose keys are exactly the indices of the selected elements, and values are all true.
SelectionState.onSelected(vpoint);
  • This method computes a selection domain as if vpoint was clicked. Let J be that selection domain. Returns the result of calling onSelected(J) method of the selection storage object. For the default selection storage, returns true if J contains exactly one element and that element is selected.

2.1.3 Accessing and setting 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 selection domain or predicate is committed.

2.1.4 Commands to election storage

SelectionState.modifyStorage(cmd);
  • Delegates to the modifyStorage method of the selection storage. The accepted values of cmd are defined by the selection storage.

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.

2.1.6 Click functions

SelectionState.click(vpoint);
  • The vpoint parameter is a point in the selection space.
  • The current active domain is committed.
  • The currently selected elements are unselected.
  • The current selection cache is reset to {}, the current selection path is set to [] and then extended with the geometry’s extendPath function.
  • The cursor is set according to extendPath, or by default to the active end.
  • A new selection domain is computed with the selectionPath function.
  • The refresh callback is called (if there are no elements whose selection state changes, the call might not be made).
SelectionState.cmdClick(vpoint, selmode);
  • The vpoint parameter is a point in the selection space.
  • 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.
  • The current active domain is committed.
  • The current selection cache is reset to {}, the current selection path is set to [] and then extended with the geometry’s extendPath function.
  • The cursor is set according to extendPath, or by default to the active end.
  • A new selection domain is computed with the selectionPath function.
  • The refresh callback is called (if there are no elements whose selection state changes, the call might not be made).
SelectionState.shiftClick(vpoint);
  • The selection path is extended by vpoint according to extendPath.
  • If extendPath returns null, there is no effect (other than the possible modifying of _spath and _cursor).
  • The cursor is set according to extendPath, or by default to the active end.
  • The new selection domain is computed with the call selectionDomain(_spath, J, cache), where J is the current active domain and cache the current cache.
  • 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 Modify the selection path

SelectionState.modifyPath(vpoint);

This function is for updating the path and cursor, for example if the indexed family of elements has been changed so that the points on the selection path or the cursor need to be adjusted. The function always executes a shift-click that may be pending, so that the current selection domain is guaranteed to be computed based on the current selection path.

The modifyPath(vp) invokes extendPath in the same way as shiftClick. The difference is that modifyPath does not schedule a computation of a new active domain.

SelectionState.resetPath();

This function resets the current selection path to the empty path. This function needs to be called if the locations of elements change in a way that would impact the result of the selectionDomain method.

  • Commits the current active domain (whether it was computed based on a path or a predicate). Resets the current selection path to [] and keyboard cursor to undefined.

2.1.8 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 predicate selection operation is undone.
  • The selection path is set to [].
  • The refresh function is invoked.

The undo method does not modify the keyboard cursor.

SelectionState.redo();
  • 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.9 Selecting based on a predicate

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

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

2.1.10 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(path, vpoint, cache, cursor)
  • Extends path, an array representing the selection path, with the selection space point vpoint.
  • The cache argument is an object to which the selection geometry can store data between subsequent calls to extendPath and selectionDomain functions to optimize the selection domain calculations.
  • The results are conveyed to the caller as follows:
    • Modifications to path are visible to the caller.
    • To indicate to the caller that the path did not change in a way that would require recomputing the selection domain, return null or an object o where o.path is null.
    • To completely replace the current selection path with some new path array path2, return an object o for which o.path is path2.
    • To indicate that the cursor should be set to some new value cursor2, return an object o for which o.cursor is cursor2. (Otherwise, cursor will be set to the last element of the path.)
  • The extendPath function is allowed to change the path and cursor in arbitrary ways.
selectionDomain(path, J, cache)
  • This function should compute the selection domain that path gives rise to.
  • The type of the selection domain object is determined by what the selection storage requires. With the default selection storage, the object must be a Map whose keys are the indices in the domain.
  • 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 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.
  • 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 domain. Then the also the cache object is the same for both of those calls, and for calls to extendPath in between.
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 selection domain consisting of those indices i for which pred(i) is 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 (pc, vp) { pc.path.push(vp); pc.cursor = vp; },
  selectionDomain : function(spath, J) { 
    var m = new Map();
    for (var i of spath) m.set(i, true); 
    return m;
  },
  step : function (dir, vp) { return undefined; },
  defaultCursor : function(dir) { return undefined; },
  filter : undefined
};

2.3 Selection storage objects

This section describes the required functionality of a selection storage object. We assume that storage is a selection storage representing \(\textit{ops}(s_b)\), i an index to an element, \(T_J\) the type used for representing domains of primitive selection operations, \(J\) a selection domain of type \(T_J\), and op a primitive selection operation whose domain is represented using \(T_J\). The following expressions must be valid, and they must have the semantics as described below:

storage.at(i)
  • Returns \(\textit{ops}(s_b)(i)\).
storage.selected()
  • Returns the set of indices of the selected elements, that is \(\{i \in I\ |\ \textit{ops}(s_b)(i)\}\), as an object of type \(T_J\).
storage.push(op, changed)
  • Adds a new primitive selection operation op to the front of the op-composition. If storage represents \(\textit{ops}(s_b)\) before the call, after the call it represents \((\textit{op} \circ \textit{ops})(s_b)\). If changed is not undefined, changed.value must at exit have a value that represents the set of indices whose selection state changed (from true to false or vice versa). If changed.value is defined when entering the function, the indices it represents are considered to be the indices changed by a preceding call to push or pop, and the joint effect is tracked. If \(J_p\) are those indices and \(J_c\) the indices changed by the current push operation, then the resulting changed.value is \(J_c \setminus J_p\). How changed.value is represented is up to the selection storage.

    The push method may ignore changed altogether, if storage is never used in a selection state with change tracking on.

storage.pop(changed)
  • Precondition: storage.size() >= 1.
  • Removes a primitive selection operation from the front of the op-composition. If storage represents \((\textit{op} \circ \textit{ops})(s_b)\) before the call, after the the call it represents \(\textit{ops}(s_b)\). The meaning and requirements for the changed parameter are the same as in the push function.
  • Returns the removed primitive selection operation.
storage.top()
  • Precondition: storage.size() >= 1.
  • Returns a reference to the first (most recently pushed) primitive selection operation. That is, if storage represents \(\textit{op} \circ \textit{ops}(s_b)\), returns \(\textit{op}\).
storage.top2()
  • Precondition: storage.size() >= 2.
  • Returns a reference to the second (second most recently pushed) primitive selection operation. That is, if storage represents \(\textit{op}_a \circ \textit{op}_b \circ \textit{ops}(s_b)\), returns \(\textit{op}_b\).
storage.size()
  • Returns the number of primitive selection operations in the composition represented by \(\textit{ops}\). (If \(\textit{ops}\) is empty, all of the selection state is represented in the base selection mapping \(s_b\) portion of \(\textit{ops}(s_b)\).)
storage.bake()
  • Removes one primitive selection operation (the least recently pushed), applies it to the base selection mapping, and makes the result the new base. That is, if storage represents \((\textit{ops} \circ \textit{op})(s_b)\), it is modified to represent \(\textit{ops}(s'_b)\), where \(s'_b = \textit{op}(s_b))\). The function has no effect when storage.size() == 0.
storage.onSelected(J)
  • Returns true if the selection domain J is considered to indicate a selected element, false otherwise. A typical implementation would return storage.at(i) if i is the only element in J, otherwise false.
storage.modifyStorage(cmd)
  • The cmd parameter is a command that indicates how storage should be modified. What commands are accepted and what their effects are is defined by the client. Example functionality to provide through this method include reacting to removing indices, adding indices, and reordering indices (if they are stored in a data structure where ordering matters).
storage.equalDomains(J1, J2)
  • Returns true if J1 and J2 are equivalent sets of indices.
storage.isEmpty(J)
  • Returns true if J is an empty set of indices.

2.4 Helper definitions

2.4.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.

2.4.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.4.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 OS X 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.