Blog

introducing cudgets.widget.ListSelectionModel

Posted by:

A ListSelectionModel represents an interval of selections. A selection itself has two properties: index and selected. The index is a numeric value that points to a position in the interval and selected provides information about the state of the index in the selection-interval: true for selected, otherwise false.

Speaking of GUI, ListSelectionModels are needed when a set of items (a List) is available that allows a user to select/deselect any range of items.

A good programmer (who’s into design patterns) would split the GUI component into four different parts: Two objects that represent the data model and the selection model, the View (or user interface) and the Controller, responsible for delegating user interactions to its model objects and updating the view, i.e. translating the states of its models or properties into a visual presentation.

This is what we call a Model-View-Controller (MVC) pattern. More detailed, the controller can work as an observer for the ListSelectionModel, which would then be one of the controllers subjects: Selecting any list item in the view triggers an event that tells the controller to add the selected item’s index to the ListSelectionModel. The selection model changes it’s state (i.e. the range of selected iindicies) and informs its observers (one of them would be the controller) about the changes made in the model. The controller will then again interact with the view, telling it to repaint a specific area that will now show the items as selected/deselected depending on the state of the model.

ListSelectionModel and MVC (simplified)

Java’s Swing library has an interface for models managing the state of selections which implementation is used in components as the JList and the JTable. Here’s its ListSelectionModels method list:

  • getMinSelectionIndex
  • getMaxSelectionIndex
  • isSelectedIndex
  • getAnchorSelectionIndex
  • setAnchorSelectionIndex
  • getLeadSelectionIndex
  • setLeadSelectionIndex
  • clearSelection
  • isSelectionEmpty
  • insertIndexInterval
  • removeIndexInterval
  • setValueIsAdjusting
  • getValueIsAdjusting
  • setSelectionMode
  • getSelectionMode
  • addListSelectionListener
  • removeListSelectionListener

I have ported and implemented this interface in Javascript using a method I described here (it also supports deriving from abstract classes now). A working example can be found here (make sure you’re either using Firefox 2.0 or IE7.0, as I haven’t tested the source on any other browser, and possible won’t).

I’m using an Array for managing the selections, since they are dynamic in JS I don’t have to bother with out-of-bounds indicies (that comes quite handy). The example defines its own observer which updates the cells of a html table according to the indicies and actions that were selected in the command panel on the left of the screen. The right side holds a textarea which shows some informations about the events that were fired by the model. The source for both the interface and a concrete implementation called DefaultListSelectionModel can be found at the bottom of this post. Since this is a quick hack, the range [fromIndex..toIndex] (including toIndex) which translates to the set of changed indicies in the selection is as optimal as possible for the short time I had to write this bugger, at least the methods insertIndexInterval and setLeadSelectionIndex lack optimization.

I’m linking to other objects in the sources, so you may not be able to use this code in any of your projects out of the box. A beta version of the framework I’m working on (cudgets) will soon be available in a svn trunk, so make sure you check back as I’m also about to explain how to define and extend abstract classes/prototypes in Javascript.

(Note: In the working example, you can enable/disable the onmouseover-docComments by checking/unchecking the checkbox labeled “docs”.)

Source for cudgets.widget.ListSelectionModel

/**
* This interface provides functionality for implementing a state
* of selection for any widget that allows selection.
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de></ts@siteartwork.de>
*/
cudgets.widget.ListSelectionModel = cudgets.Class.interface();
 
cudgets.require('cudgets.exception.InvalidArgumentException');
cudgets.require('cudgets.widget.event.ListSelectionListener');
cudgets.require('cudgets.widget.event.ListSelectionEvent');
 
/**
* For reducing chaining we will use this temporary var that points to
* <code>cudgets.widget.ListSelectionModel.prototype</code>.
* It will be deleted after defining the prototype.
*/
var ListSelectionModel = cudgets.widget.ListSelectionModel.prototype;
 
// {{{ members
/**
* This property tells the model that only one index may be selected
* at a time.
* @var integer
* @see #setSelectionMode
*/
cudgets.widget.ListSelectionModel.SINGLE_SELECTION = 0;
 
/**
* This property tells the model that a continous range of indexes
* may be selected at a time.
* @var integer
* @see #setSelectionMode
*/
cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION = 1;
 
/**
* This property tells the model that one ore more continous ranges
* of indexes may be selected at a time.
*
* @var integer
* @see #setSelectionMode
*/
cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION = 2;
// }}}
 
// {{{methods
ListSelectionModel.__METHODS__ = [
 
/**
* Changes the models selection to be between index0 and index1
* (including both index0 and index1).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*
* @see #addListSelectionListener
*/
"setSelectionInterval", // (index0, index1)
 
/**
* Changes the models selection to be the union of the current selection
* AND the indexes between index0 and index1 (including).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*
* @see #addListSelectionListener
*/
"addSelectionInterval", //(index0, index1)
 
/**
* Changes the models selection to be the difference of the current selection
* AND the indexes between index0 and index1 (including).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*/
"removeSelectionInterval", //(index0, index1)
 
/**
* Returns the first selected index which will be &gt;= 0. If the selection is empty,
* -1 will be returned. The min index must be updated each time a selection was made
* to make sure the value is adjusted to the most minimum index that was selected.
*
* @return integer
*/
"getMinSelectionIndex",
 
/**
* Returns the last selected index which will be &gt;= 0. If the selection is empty,
* -1 will be returned. The max index must be updated each time a selection was made
* to make sure the value is adjusted to the maximum index that was selected.
*
* @return integer
*/
"getMaxSelectionIndex",
 
/**
* If the specified <code>index</code> is selected, return true, else false.
*
* Note, that a check if the index lies within minSelectionIndex and
* maxSelectionIndex does not work because indexes in between this
* range may have been deselected.
*
* @param integer The index to check if it is selected in this model.
*
* @return boolean <code>true</code> if <code>index</code> is selected
*/
"isSelectedIndex", //(index);
 
/**
* Returns the first index argument from the most recent call to either
* <code>setSelectionInterval()</code>, <code>addSelectionInterval()<code>
* or <code>removeSelectionInterval()</code>.
* The most recent index0 is considered to be the anchor in a selection model
* and index1 the lead.
* For example,in a table which allows selecting ranges of rows via holding
* the shift-key down and selecting rows with the mouse, the first clicked row
* on which the click holding shift down is the anchor, whereas the other last selected
* row is the lead.
*
* @return integer The anchor selection index which was set with index0 during a call to
* <code>setSelectionInterval()</code>, <code>addSelectionInterval()<code>
* or <code>removeSelectionInterval()</code>.
*
*
* @see #getLeadSelectionIndex
* @see #setSelectionInterval
* @see #addSelectionInterval
* @see #removeSelectionInterval
*/
"getAnchorSelectionIndex", </code></code></code></code><code><code><code><code>/**
* Sets the anchor selection index.
*
* @param integer The anchor selection index.
*
* @see #getAnchorSelectionIndex
*/
"setAnchorSelectionIndex", //(index);
 
/**
* Returns the second index argument from the most recent call to
* either <code>setSelectionInterval()</code>, <code>addSelectionInterval()</code>
* or <code>removeSelectionInterval()</code>.
*
* @return integer the current lead selection index
*
* @see #getAnchorSelectionIndex
* @see #setSelectionInterval
* @see #removeSelectionInterval
* @see #addSelectionInterval
*/
"getLeadSelectionIndex",
 
/**
* Sets the lead selection index.
*
* @param integer index The lead selection index for this selection model.
*
* @see #getLeadSelectionIndex
* @see #setAnchorSelectionIndex
* @see #getAnchorSelectionIndex
* @see #setSelectionInterval
* @see #removeSelectionInterval
* @see #addSelectionInterval
*/
"setLeadSelectionIndex", //(index);
 
/**
* Clears the current selection so that no indexes are selected.
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* @see #addListSelectionListener
*/
"clearSelection",
 
/**
* Returns <code>true</code> if no indexes are selected,
* otherwise <code>false</code>.
*
* @see #getMinSelectionIndex
* @see #getMaxSelectionIndex
*/
"isSelectionEmpty",
 
/**
* Inserts <code>length</code> indexes before or after the begining
* of <code>index</code>. This is typically made to sync this model with
* the data model of an object that uses this selection model.
*
* @param integer index The index to insert <code>length</code> indexes
* before or after
* @param integer length The number of indexes to insert
* @param boolean before Wether to insert the indexes before or after <code>index</code>
*
* @see #removeIndexInterval
*/
"insertIndexInterval", //(index, length, before);
 
/**
* Removes the indexes in the interval index0 to index1 (including).
*
* Remove the indices in the interval index0,index1 (inclusive) from
* the selection model. This is typically made to sync this model with
* the data model of an object that uses this selection model.
*
* @param integer index0 The first index to remove
* @param integer index1 The last index to remove (inclusive)
*/
"removeIndexInterval",//(index0, index1);
 
/**
* This value should be set to true if the upcoming changes
* to the selection model is considered as a single event,
* so any event associated with the changes that gets fired
* is taking into account by the listeners until setValueIsAdjusting
* is set to false.
 
* @param boolean valueIsAdjusting <code>true</code> if the value is adjusting,
* otherwise <code>false</code>.
* @see #getValueIsAdjusting
*/
"setValueIsAdjusting", //(valueIsAdjusting);
 
/**
* Returns <code>true</code> if the selection model is undergoing a series
* of changes, otherwise <code>false</code>.
 
* @return boolean <code>true</code> if the selection model is currently adjusting,
* otherwise <code>false</code>
* @see #setValueIsAdjusting
*/
"getValueIsAdjusting",
 
/**
* Set the selection mode for an instance of this model.
* Following modes are valid:
*
<ul>*
	<li>* <code>cudgets.widget.ListSelectionModel.SINGLE_SELECTION</code>
* Only one index can be selected at a time. Using this mode
* the <code>setSelectionInterval()</code> and
* <code>addSelectionInterval()</code> will be treated equally
* and only the second argument <code>index1</code> (the lead index)
* is used.
*</li>
*
	<li>* <code>cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION</code>
* Only one continous interval can be selected at a time.
* Using this mode the <code>setSelectionInterval()</code> and
* <code>addSelectionInterval()</code> will behave the same.
*</li>
*
	<li>* <code>cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
* Anything can be selected.
*</li>
*</ul>
*
* @param integer selectionMode one of <code>cudgets.widget.ListSelectionModel.SINGLE_SELECTION</code>,
* <code>cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION</code>
* or <code>cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
*
* @throws cudgets.exception.InvalidArgumentException if selectionMode is unknown to this model
* @see #getSelectionMode
*/
"setSelectionMode", //(selectionMode);
 
/**
* Returns the current selection mode for this model.
*
* @return integer The current selection mode.
* @see #setSelectionMode
*/
"getSelectionMode",
 
/**
* Adds a listener to the list of this models listeners that will
* be notified each time a change in the selection is made.
*
* @param cudgets.widget.event.ListSelectionListener listener The ListSelectionListener
*
* @see #removeListSelectionListener
* @see #setSelectionInterval
* @see #addSelectionInterval
* @see #removeSelectionInterval
* @see #clearSelection
* @see #insertIndexInterval
* @see #removeIndexInterval
*
* @throws cudgets.util.InvalidArgumentException if <code>listener</code> is null,
* undefined or if a check
* using cudgets.instanceOf(listener,
* cudgets.widget.event.ListSelectionListener)
* equals to false.
*/
"addListSelectionListener", //(/*cudgets.widget.event.ListSelectionListener*/ listener)
 
/**
* Removes a listener from this models listener list.
*
* @param cudgets.widget.event.ListSelectionListener listener The listener to remove.
*
* @see #addListSelectionListener
*/
"removeListSelectionListener" //(/*cudgets.widget.event.ListSelectionListener*/ listener);
 
];
 
// }}}methods
 
// !CLEANUP!
delete ListSelectionModel;
[/javascript]
 
Source for <strong>cudgets.widget.DefaultListSelectionModel</strong>
 
[javascript]
/**
* Default implementation of a cudgets.widget.ListSelectionModel
*
* The implementation takes care of sending minimum ranges of indexes
* to the listeners where appropriate.
*
* @author Thorsten Suckow-Homberg <ts@siteartwork.de></ts@siteartwork.de>
*/
cudgets.widget.DefaultListSelectionModel = cudgets.Class.prepare();
 
cudgets.require('cudgets.exception.InvalidArgumentException');
cudgets.require('cudgets.widget.event.ListSelectionEvent');
 
/**
* This class implements the cudgets.widget.ListSelectionModel interface
*/
cudgets.require('cudgets.widget.ListSelectionModel');
 
/**
* This class implements the cudgets.widget.ListSelectionModel interface
*/
cudgets.__implements(cudgets.widget.DefaultListSelectionModel, cudgets.widget.ListSelectionModel);
 
/**
* For reducing chaining we will use this temporary var that points to
* <code>cudgets.widget.DefaultListSelectionModel.prototype</code>.
* It will be deleted after defining the prototype.
*/
var DefaultListSelectionModel = cudgets.widget.DefaultListSelectionModel.prototype;
 
// {{{ members
/**
* The list of observers that will be notified each time a change to
* the selection model has been made.
* Initialized in the constructr.
* @var array
*/
DefaultListSelectionModel.listeners = null;
 
/**
* An array representing the selection in the model. The keys are the indexes,
* set to <code>true</code> if the index is selected, otherwise <code>false</code>.
* Initialized in the constructr.
* @var array
*/
DefaultListSelectionModel.selections = null;
 
/**
* The anchor selection index
* @var integer
*/
DefaultListSelectionModel.anchorSelectionIndex = -1;
 
/**
* The lead selection index
* @var integer
*/
DefaultListSelectionModel.leadSelectionIndex = -1;
 
/**
* The max selection index
* @var integer
*/
DefaultListSelectionModel.maxSelectionIndex = -1;
 
/**
* The min selection index
* @var integer
*/
DefaultListSelectionModel.minSelectionIndex = cudgets.Integer.MAX_VALUE;
 
/**
* Wether the selection is adjusting
* @var boolean
*/
DefaultListSelectionModel.isAdjusting = false;
 
/**
* The selection mode for this model. Default is
* <code>cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
* @var int
*/
DefaultListSelectionModel.selectionMode = cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION;
// }}}
 
// {{{methods
 
/**
* Constructor.
*/
DefaultListSelectionModel.construct = function()
{
this.selections = new Array();
this.listeners = new Array();
}
 
/**
* Changes the models selection to be between index0 and index1
* (including both index0 and index1).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* Does nothing if either index0 or index1 is less than 0.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*
* @see #addListSelectionListener
*/
DefaultListSelectionModel.setSelectionInterval = function(index0, index1)
{
if (index0 == -1 || index1 == -1) {
return;
}
 
switch (this.getSelectionMode()) {
case(cudgets.widget.ListSelectionModel.SINGLE_SELECTION):
// delete anything within the range [minSelectionIndex, maxSelectionIndex]
// and mark it as isValueAjusting
this.isAdjusting = true;
this.removeSelectionInterval(this.minSelectionIndex, this.maxSelectionIndex);
this.isAdjusting = false;
index0 = index1;
break;
}
 
this.anchorSelectionIndex = index0;
this.leadSelectionIndex = index1;
 
var oldMin = this.minSelectionIndex;
var oldMax = this.maxSelectionIndex;
 
this.minSelectionIndex = Math.min(index0, index1);
this.maxSelectionIndex = Math.max(index0, index1);
 
var loopStart = Math.min(this.minSelectionIndex, oldMin);
var loopEnd = Math.max(this.maxSelectionIndex, oldMax);
 
var selStart = Math.min(index0, index1);
var selEnd = Math.max(index0, index1);
 
var fromIndex = -1;
var toIndex = -1;
 
for (var i = loopStart; i &lt;= loopEnd; i++) {
fromIndex = (fromIndex == -1 &amp;&amp; ((this.selections[i] == false || this.selections[i] == undefined) || (this.selections[i] &amp;&amp; i &lt; this.minSelectionIndex))) ? i : fromIndex;
toIndex = ((this.selections[i] == false || this.selections[i] == undefined) || (this.selections[i] &amp;&amp; i &gt; this.maxSelectionIndex)) ? i : toIndex;
this.selections[i] = (i &gt;= selStart &amp;&amp; i &lt;= selEnd) ? true : false;
}
 
if (fromIndex == -1 &amp;&amp; toIndex != -1) {
fromIndex = loopStart;// return;
}
 
if (fromIndex != -1 &amp;&amp; toIndex == -1) {
toIndex = loopEnd;
}
 
this.fireValueChanged(fromIndex, toIndex, this.isAdjusting);
}
 
/**
* Changes the models selection to be the union of the current selection
* AND the indexes between index0 and index1 (including).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* Does nothing if either index0 or index1 is less than 0.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*
* @see #addListSelectionListener
*/
DefaultListSelectionModel.addSelectionInterval = function(index0, index1)
{
if (index0 == -1 || index1 == -1) {
return;
}
 
if (this.getSelectionMode() ==
cudgets.widget.ListSelectionModel.SINGLE_SELECTION) {
this.setSelectionInterval(index0, index1);
return;
 
}
 
var lead = Math.min(this.leadSelectionIndex, this.anchorSelectionIndex);
var anchor = Math.max(this.leadSelectionIndex, this.anchorSelectionIndex);
 
this.anchorSelectionIndex = index0;
this.leadSelectionIndex = index1;
 
this.minSelectionIndex = Math.min(this.minSelectionIndex, Math.min(index0, index1));
this.maxSelectionIndex = Math.max(this.maxSelectionIndex, Math.max(index0, index1));
 
var selStart = Math.min(index0, index1);
var selEnd = Math.max(index0, index1);
 
var fromIndex = -1;
var toIndex = -1;
 
for (var i = selStart; i &lt;= selEnd; i++) {
fromIndex = (fromIndex == -1 &amp;&amp; (this.selections[i] == undefined || this.selections[i] == false)) ? i : fromIndex;
toIndex = (this.selections[i] == undefined || this.selections[i] == false) ? i : toIndex;
this.selections[i] = true;
}
 
// no changes to the selection where made, dont fire the event
if (fromIndex == -1 || toIndex == -1) {
// return;
}
 
this.fireValueChanged(fromIndex, toIndex, this.isAdjusting);
}
 
/**
* Changes the models selection to be the difference of the current selection
* AND the indexes between index0 and index1 (including).
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
* It will also take care of recomputing minSelectionIndex and maxSelectionIndex if
* neccessary.
* Does nothing if either index0 or index1 is less than 0 or the selection is already
* empty.
*
* @param integer index0 start or end of interval
* @param integer index1 start or end of the other interval
*/
DefaultListSelectionModel.removeSelectionInterval = function(index0, index1)
{
if (index0 == -1 || index1 == -1) {
return;
}
 
this.anchorSelectionIndex = index0;
this.leadSelectionIndex = index1;
 
if (this.isSelectionEmpty()) {
this.fireValueChanged(-1, -1, this.isAdjusting);
return;
}
 
var newMin = Math.min(index0, index1);
var newMax = Math.max(index0, index1);
 
// now we know min and max we can precheck and leave if there is nothing
// to deselect
if (newMax &lt; this.minSelectionIndex || newMin &gt; this.maxSelectionIndex) {
this.fireValueChanged(-1, -1, this.isAdjusting);
return;
}
 
// clear the selection if index0 and index1 overrange the current min and
// max index
if (newMin &lt;= this.minSelectionIndex &amp;&amp; newMax &gt;= this.maxSelectionIndex) {
this.clearSelection();
return;
}
 
var fromIndex = newMin;
var toIndex = newMax;
 
// compute the range we have to work on
// finer grains in the loop at the en of the
// method
if (newMin &lt; this.minSelectionIndex) {
fromIndex = this.minSelectionIndex;
}
if (newMax &gt; this.maxSelectionIndex) {
toIndex = this.maxSelectionIndex;
}
 
// adjust the minSelectionIndex if neccessary
if (newMin &lt;= this.minSelectionIndex) {
this.minSelectionIndex = newMax+1;
}
 
// adjust the maxSelectionIndex if neccessary
if (newMax &gt;= this.maxSelectionIndex) {
this.maxSelectionIndex = newMin-1;
}
 
var fI = -1;
var tI = -1;
 
for (var i = fromIndex; i &lt;= toIndex; i++) {
fI = (fI == -1 &amp;&amp; this.selections[i] === true) ? i : fI;
tI = (this.selections[i] === true) ? i : tI;
this.selections[i] = false;
}
 
// do nothing
if (fI == -1 || tI == -1) {
//return;
}
 
this.fireValueChanged(fromIndex, toIndex, this.isAdjusting);
}
 
/**
* Returns the first selected index which will be &gt;= 0. If the selection is empty,
* -1 will be returned. The min index must be updated each time a selection was made
* to make sure the value is adjusted to the most minimum index that was selected.
*
* @return integer
*/
DefaultListSelectionModel.getMinSelectionIndex = function()
{
if (this.isSelectionEmpty()) {
return -1;
}
 
return this.minSelectionIndex;
}
 
/**
* Returns the last selected index which will be &gt;= 0. If the selection is empty,
* -1 will be returned. The max index must be updated each time a selection was made
* to make sure the value is adjusted to the maximum index that was selected.
*
* @return integer
*/
DefaultListSelectionModel.getMaxSelectionIndex = function()
{
if (this.isSelectionEmpty()) {
return -1;
}
 
return this.maxSelectionIndex;
}
 
/**
* If the specified <code>index</code> is selected, return true, else false.
*
* Note, that a check if the index lies within minSelectionIndex and
* maxSelectionIndex does not work because indexes in between this
* range may have been deselected.
*
* @param integer The index to check if it is selected in this model.
*
* @return boolean <code>true</code> if <code>index</code> is selected
*/
DefaultListSelectionModel.isSelectedIndex = function(index)
{
return ((this.selections[index] == undefined || this.selections[index] == false) ? false : true);
}
 
/**
* Returns the first index argument from the most recent call to either
* <code>setSelectionInterval()</code>, <code>addSelectionInterval()<code>
* or <code>removeSelectionInterval()</code>.
* The most recent index0 is considered to be the anchor in a selection model
* and index1 the lead.
* For example,in a table which allows selecting ranges of rows via holding
* the shift-key down and selecting rows with the mouse, the first clicked row
* on which the click holding shift down is the anchor, whereas the other last selected
* row is the lead.
*
* @return integer The anchor selection index which was set with index0 during a call to
* <code>setSelectionInterval()</code>, <code>addSelectionInterval()<code>
* or <code>removeSelectionInterval()</code>.
*
*
* @see #getLeadSelectionIndex
* @see #setSelectionInterval
* @see #addSelectionInterval
* @see #removeSelectionInterval
*/
DefaultListSelectionModel.getAnchorSelectionIndex = function()
{
return this.anchorSelectionIndex;
} </code></code></code></code><code><code><code><code>/**
* Sets the anchor selection index.
* Changing this property will not fire an event.
*
* @param integer The anchor selection index.
*
* @see #getAnchorSelectionIndex
*/
DefaultListSelectionModel.setAnchorSelectionIndex = function(index)
{
this.anchorSelectionIndex = index;
}
 
/**
* Returns the second index argument from the most recent call to
* either <code>setSelectionInterval()</code>, <code>addSelectionInterval()</code>
* or <code>removeSelectionInterval()</code>.
*
* @return integer the current lead selection index
*
* @see #getAnchorSelectionIndex
* @see #setSelectionInterval
* @see #removeSelectionInterval
* @see #addSelectionInterval
*/
DefaultListSelectionModel.getLeadSelectionIndex = function()
{
return this.leadSelectionIndex;
}
 
/**
	<li><code>isSelectedIndex(A) == true</code>: set <code>[A,OL]</code>
* to <em>deselected</em>, then set <code>[A,NL]</code> to
* <em>selected</em>.</li>
*
*
	<li><code>isSelectedIndex(A) == false</code>: set <code>[A,OL]</code>
* to <em>selected</em>, then set <code>[A,NL]</code> to
* <em>deselected&lt;*//**
* Sets the lead selection index.
*
* The lead selection is the most recent index that was selected.
* Let ASI be the current anchorSelectionIndex, LSI the current
* leadSelectionIndex and NLSI the new leadSelection index.
*
* Then, if ASI is selected, deselect all indicies within the range
* [ASI.. LSI] and set the indicies in the range [ASI..NLSI] to true.
*
* Otherwise, if ASI is not selected, select all indicies in the range
* [ASI..LSI] and deselect all indicies in the range [ASI..NLSI]
*
* Intepended on the selections tate of the anchor selection index,
*
* @param integer index The lead selection index for this selection model.
*
* @see #getLeadSelectionIndex
* @see #setAnchorSelectionIndex
* @see #getAnchorSelectionIndex
* @see #setSelectionInterval
* @see #removeSelectionInterval
* @see #addSelectionInterval
*/
DefaultListSelectionModel.setLeadSelectionIndex = function(index)
{
if (index &lt; 0 || this.anchorSelectionIndex &lt; 0 || this.leadSelectionInterval == -1) {
return;
}
 
var oldAnchor = this.anchorSelectionIndex;
var oldLead = this.leadSelectionIndex;
var newLead = index;
 
this.leadSelectionIndex = index;
 
var oldMin = this.minSelectionIndex;
 
R1 = Math.min(this.anchorSelectionIndex, oldLead);
R2 = Math.max(this.anchorSelectionIndex, oldLead);
S1 = Math.min(this.anchorSelectionIndex, index);
S2 = Math.max(this.anchorSelectionIndex, index);
 
lo = Math.min(R1, S1);
hi = Math.max(R2, S2);
 
if (this.isSelectedIndex(this.anchorSelectionIndex)) {
for (var i = R1; i &lt;= R2; i++ ){this.selections[i] = false;}
for (var i = S1; i &lt;= S2; i++ ){this.selections[i] = true;}
this.minSelectionIndex = S1;//Math.min(this.anchorSelectionIndex, S1);
this.maxSelectionIndex = S2;//Math.max(this.anchorSelectionIndex, S2);
} else {
for (var i = R1; i &lt;= R2; i++ ){this.selections[i] = true;}
for (var i = S1; i &lt;= S2; i++ ){this.selections[i] = false;}
var min = -1;
var max = -1;
 
for (var i = 0; i &lt;= Math.max(hi, this.maxSelectionIndex); i++) {
min = this.selections[i] &amp;&amp; min == -1 ? i : min;
max = this.selections[i] ? i : max;
}
this.minSelectionIndex = min;
this.maxSelectionIndex = max;
 
}
 
this.fireValueChanged(lo, hi, this.isAdjusting);
}
 
/**
* Clears the current selection so that no indexes are selected.
* If this represents changes to the current selection, notify each
* <code>cudgets.widget.event.ListSelectionListener</code> that was added
* to this models listener list via a <code>ListSelectionEvent</code>.
*
* @see #addListSelectionListener
*/
DefaultListSelectionModel.clearSelection = function()
{
// don't fire any event if there is curently no index selected
if (this.isSelectionEmpty()) {
return;
}
 
this.anchorSelectionIndex = this.minSelectionIndex;
this.leadSelectionIndex = this.maxSelectionIndex;
 
var oldMin = this.minSelectionIndex;
var oldMax = this.maxSelectionIndex;
 
this.minSelectionIndex = cudgets.Integer.MAX_VALUE; ;
this.maxSelectionIndex = -1;
 
this.selections = new Array();
 
this.fireValueChanged(oldMin, oldMax, this.isAdjusting);
}
 
/**
* Returns <code>true</code> if no indexes are selected,
* otherwise <code>false</code>.
*
* @see #getMinSelectionIndex
* @see #getMaxSelectionIndex
*/
DefaultListSelectionModel.isSelectionEmpty = function()
{
return (this.minSelectionIndex == cudgets.Integer.MAX_VALUE &amp;&amp; this.maxSelectionIndex == -1);
}
 
/**
* Inserts <code>length</code> indexes before or after the begining
* of <code>index</code>. This is typically made to sync this model with
* the data model of an object that uses this selection model.
*
* minSelectionIndex and maxSelectionIndex will be adjusted according to
* the shifted interval ranges.
* leadSelectionIndex and anchorSelection will not be updated.
*
* @param integer index The index to insert <code>length</code> indexes
* before or after
* @param integer length The number of indexes to insert
* @param boolean before Wether to insert the indexes before or after <code>index</code>
*
* @see #removeIndexInterval
*/
DefaultListSelectionModel.insertIndexInterval = function(index, length, before)
{
var stPos = before ? index : index+1;
var end = Math.max(stPos+length, length+this.maxSelectionIndex);
 
var tmpSelections = new Array();
var m = 0;
for (var i = stPos; i &lt; end; i++) {
tmpSelections[m] = this.selections[i];
this.selections[i] = false;
m++;
}
 
for (var i = 0; i &lt; m; i++) {
this.selections[i+stPos+length] = tmpSelections[i];
}
 
if (this.minSelectionIndex &gt;= stPos) {
this.minSelectionIndex = this.minSelectionIndex+length;
}
if (this.maxSelectionIndex &gt;= stPos) {
this.maxSelectionIndex = this.maxSelectionIndex+length;
}
 
this.fireValueChanged(stPos, this.maxSelectionIndex, this.isAdjusting);
}
 
/**
* Removes the indexes in the interval index0 to index1 (including).
*
* Remove the indices in the interval index0,index1 (inclusive) from
* the selection model. This is typically made to sync this model with
* the data model of an object that uses this selection model.
*
* This will update the minSelectionIndex and maxSelectionIndex.
* lead and anchorSelectionIndex wil not be updated
*
* @param integer index0 The first index to remove
* @param integer index1 The last index to remove (inclusive)
*/
DefaultListSelectionModel.removeIndexInterval = function(index0, index1)
{
if (this.isSelectionEmpty()) {
this.fireValueChanged(-1, -1, this.isAdjusting);
return;
}
 
var start = Math.min(index0, index1);
var end = Math.max(index0, index1);
 
var oldMin = this.minSelectionIndex;
var oldMax = this.maxSelectionIndex;
 
var newSelections = new Array();
 
var gap = 0;
 
var loopStart = start &gt;= this.minSelectionIndex ? this.minSelectionIndex : start;
 
var loopEnd = end &lt;= this.maxSelectionIndex ? this.maxSelectionIndex : end;
var endIndex = -1;
var startIndex = -1;
 
// update min and max selection index
if (start &lt;= oldMin &amp;&amp; end &gt;= oldMax) {
this.minSelectionIndex = -1;
this.maxSelectionIndex = -1;
} else {
if (start &lt;= this.minSelectionIndex &amp;&amp; end &lt;= this.minSelectionIndex) {
this.minSelectionIndex = this.minSelectionIndex - (end-start+1) &lt; 0 ? -1 : this.minSelectionIndex - (end-start+1);
} else if (start &lt; this.minSelectionIndex &amp;&amp; end &gt; this.minSelectionIndex) {
this.minSelectionIndex = start;
}
 
if (end &lt;= this.maxSelectionIndex) {
this.maxSelectionIndex = this.maxSelectionIndex - (end-start+1) &lt; 0 ? -1 : this.maxSelectionIndex - (end-start+1);
}
}
 
//this.leadSelectionIndex = this.maxSelectionIndex;
//this.anchorSelectionIndex = this.minSelectionIndex;
 
for (var i = start; i &lt;= loopEnd; i++) {
if (i == start) {
gap = (end-start)+1;
}
this.selections[i] = this.selections[i+gap];
this.selections[i+gap] = false;
}
 
//this.selections = newSelections;
this.fireValueChanged(start, oldMax, this.isAdjusting);
}
 
/**
* This value should be set to true if the upcoming changes
* to the selection model is considered as a single event,
* so any event associated with the changes that gets fired
* is taking into account by the listeners until setValueIsAdjusting
* is set to false.
 
* @param boolean valueIsAdjusting <code>true</code> if the value is adjusting,
* otherwise <code>false</code>.
* @see #getValueIsAdjusting
*/
DefaultListSelectionModel.setValueIsAdjusting = function(valueIsAdjusting)
{
this.isAdjusting = valueIsAdjusting;
}
 
/**
* Returns <code>true</code> if the selection model is undergoing a series
* of changes, otherwise <code>false</code>.
 
* @return boolean <code>true</code> if the selection model is currently adjusting,
* otherwise <code>false</code>
* @see #setValueIsAdjusting
*/
DefaultListSelectionModel.getValueIsAdjusting = function()
{
return this.isAdjusting;
}
 
/**
* Set the selection mode for an instance of this model.
* Following modes are valid:
*
<ul>*
	<li>* <code>cudgets.widget.ListSelectionModel.SINGLE_SELECTION</code>
* Only one index can be selected at a time. Using this mode
* the <code>setSelectionInterval()</code> and
* <code>addSelectionInterval()</code> will be treated equally
* and only the second argument <code>index1</code> (the lead index)
* is used.
*</li>
*
	<li>* <code>cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION</code>
* Only one continous interval can be selected at a time.
* Using this mode the <code>setSelectionInterval()</code> and
* <code>addSelectionInterval()</code> will behave the same.
*</li>
*
	<li>* <code>cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
* Anything can be selected.
*</li>
*</ul>
*
* Changing the selection mode will trigger a value changed event in form of a
* cleared selection.
*
* @param integer selectionMode one of <code>cudgets.widget.ListSelectionModel.SINGLE_SELECTION</code>,
* <code>cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION</code>
* or <code>cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION</code>
*
* @throws cudgets.exception.InvalidArgumentException if selectionMode is unknown to this model
* @see #getSelectionMode
*/
DefaultListSelectionModel.setSelectionMode = function(selectionMode)
{
switch (selectionMode) {
case cudgets.widget.ListSelectionModel.SINGLE_SELECTION:
case cudgets.widget.ListSelectionModel.SINGLE_INTERVAL_SELECTION:
case cudgets.widget.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:
this.selectionMode = selectionMode;
this.clearSelection();
break;
 
default:
throw new cudgets.exception.InvalidArgumentException("[cudgets.widget.DefaultListSelectionModel.setSelectionMode] "+
"invalid selectionMode ""+selectionMode+""");
}
}
 
/**
* Returns the current selection mode for this model.
*
* @return integer The current selection mode.
* @see #setSelectionMode
*/
DefaultListSelectionModel.getSelectionMode = function()
{
return this.selectionMode;
}
 
/**
* Adds a listener to the list of this models listeners that will
* be notified each time a change in the selection is made.
*
* @param cudgets.widget.event.ListSelectionListener listener The ListSelectionListener
*
* @see #removeListSelectionListener
* @see #setSelectionInterval
* @see #addSelectionInterval
* @see #removeSelectionInterval
* @see #clearSelection
* @see #insertIndexInterval
* @see #removeIndexInterval
*
* @throws cudgets.util.InvalidArgumentException if <code>listener</code> is null,
* undefined or if a check
* using cudgets.instanceOf(listener,
* cudgets.widget.event.ListSelectionListener)
* equals to false.
*/
DefaultListSelectionModel.addListSelectionListener = function(/*cudgets.widget.event.ListSelectionListener*/ listener)
{
if (listener == null || listener == undefined ||
!cudgets.instanceOf(listener, cudgets.widget.event.ListSelectionListener)) {
throw new cudgets.exception.InvalidArgumentException("[cudgets.widget.DefaultListSelectionModel.addListSelectionListener] "+
"invalid listener ""+(typeof(listener))+""");
}
 
// check if listener is already in list
for (var i = 0, max_i = this.listeners.length; i &lt; max_i; i++) {
if (this.listeners[i] == listener) {
return;
}
}
 
this.listeners.push(listener);
}
 
/**
* Removes a listener from this models listener list.
*
* @param cudgets.widget.event.ListSelectionListener listener The listener to remove.
*
* @see #addListSelectionListener
*/
DefaultListSelectionModel.removeListSelectionListener = function(/*cudgets.widget.event.ListSelectionListener*/ listener)
{
var index = -1;
// check if listener is already in list
for (var i = 0, max_i = this.listeners.length; i &lt; max_i; i++) {
if (this.listeners[i] == listener) {
index = i;
}
}
 
if (index == -1) {
return;
}
 
this.listeners.splice(index, 1);
}
 
/**
* Notifies all listeners about changes in this selection model
*
* @param integer firstIndex The first index of the selections that is associated with the changes
* @param integer lastIndex The last index of the selections that is associated with the changes (including)
* @param boolean isAdjusting Wether the changes are in a series of ongoing changes
*/
DefaultListSelectionModel.fireValueChanged = function(firstIndex, lastIndex, isAdjusting)
{
var max_i = this.listeners.length;
 
// list empty?
if (max_i == 0) {
return;
}
 
var e = new cudgets.widget.event.ListSelectionEvent(this, firstIndex, lastIndex, isAdjusting);
 
for (var i = 0; i &lt; max_i; i++) {
this.listeners[i].valueChanged(e);
}
}
 
// }}}methods
 
// !CLEANUP!
delete DefaultListSelectionModel;

0


About the Author

Thorsten is the author of the conjoon open source project and the Ext.ux.Livegrid component. In this blog he writes more or less frequently about his current projects and web development in general.

Add a Comment


Refresh