2025-04-27 07:49:33 -04:00

1644 lines
50 KiB
Plaintext

<PUBLIC:HTC URN="shellctls">
//------------------------------------------------------------------------
// Public methods
//------------------------------------------------------------------------
<METHOD name="ResetSelection" />
<METHOD name="EnableTemplate" />
<METHOD name="Refresh" />
<METHOD name="focus" />
<METHOD name="blur" />
//------------------------------------------------------------------------
// Events
//------------------------------------------------------------------------
// The onSelectItem event has the following attributes:
// srcIndex - the row index
// srcRow - the row element itself (the TR)
// srcChild - the first child element in the row (inside the TD)
// selState - the state of the selection ("selected", "deselected")
// recordset - the recordset bound to this list
//
<EVENT id=onSelectItem name="onSelectItem" />
// The onCustomDraw event has the following attributes:
// srcIndex - the row index
// srcRow - the row element itself (the TR)
// srcChild - the first child element in the row (inside the TD)
// drawStage - 'prepaint', 'postpaint'
// bSelected - selection state (true/false)
// bFocus - listbox has focus (true/false)
// recordset - the recordset bound to this list
//
<EVENT id=onCustomDraw name="onCustomDraw" />
// The onSetFocus event has the following attributes:
// srcIndex - the row index
// srcRow - the row element itself (the TR)
// srcChild - the first child element in the row (inside the TD)
// bFocus - listbox has focus (true/false)
//
<EVENT id=onSetFocus name="onSetFocus" />
// The onListComplete event has the following attributes:
// tableList - the table element
//
<EVENT id=onListComplete name="onListComplete" />
//------------------------------------------------------------------------
// Attach to element events
//------------------------------------------------------------------------
<ATTACH event="oncontentready" handler=_OnContentReady />
//------------------------------------------------------------------------
// The code...
//------------------------------------------------------------------------
<SCRIPT language="javascript">
var _bLoading = true; // true if the behavior is still loading
var _tblList = null; // the table treated as the list
var _idDisplayName; // id for the display name element (drawn differently)
var _divScroll = null; // the div used to scroll the table
var _iSelCur = -1; // selection index
var _iSelDefault = -1; // default selection when the table is refreshed
var _nItemsPerPage = 0; // used for page-up/page-down
var _idDataSource; // the dataSource object (id)
var _ctlDataSource = null; // the dataSource object (the control)
var _szRecordSetName = ""; // named DSO recordset name
var _idTemplateSource; // the template source span
var _cRowsExpand = 0; // count of rows to expand the selection's layout table
var _cRowsAdded = 0; // count of template rows added on the current selection
var _bAutoExpand = false; // true if the listbox should autoexpand the selection
var _rgTemplateRows = new Array();
var _htmlRow; // html that makes up the standard row item
var _bHasFocus = false; // true if the element has focus
var _bFreeze = false; // false in order to build and refresh the listbox
var _bTableComplete = false; // true if the databound table is readystate complete
var _bInternalFocusChange = false; // true if the focus-change is caused by the behavior itself
var _szFeedBack = ""; // text for the feedback we display
var _dvFeedBack = null; // the feedback DIV
var c_szStyle_Background =
'style = "' +
'position:relative; ' +
'width:100%; ' +
'height:100%; ' +
'overflow-y:scroll; ' +
'border: 1px solid buttonface; '+
'"';
var c_szStyle_Table =
'style = "' +
'table-layout:fixed; ' +
'width:100%; ' +
'height:auto; ' +
'"';
var c_szStyle_Feedback =
'style = "' +
'position:relative; ' +
'width:100%; ' +
'height:100%; ' +
'border: 1px solid buttonface; '+
'"';
// This code is run when the behavior is instantiated...
element.attachEvent("onkeydown", _OnKeyDown);
element.attachEvent("onerror", _OnError);
_GetPropertyDefaults();
// **********************************************************************
// PROPERTY GET/SET FUNCTIONS
// **********************************************************************
// Property: selectionIndex = i
//
// Sets the selection to the i'th item (row) in the list.
//
function get_selIndex() { return _iSelCur; }
function put_selIndex(iSel)
{
if (_bLoading)
return;
_SelectItem(parseInt(iSel), true);
}
// Property: defaultSelection = i
//
// Sets the default selection to the i'th item (row) in the list. The
// default selection is used when the databound table is refreshed and
// there is no initial selection.
//
function get_defaultSel() { return _iSelDefault; }
function put_defaultSel(iSel)
{
if (_bLoading)
return;
_iSelDefault = parseInt(iSel);
}
// Property: dataSource = ctlDataSrc
//
// Specifies the listbox contents is generated by a databound table.
//
function get_dataSource() { return _idDataSource; }
function put_dataSource(szDatasrc)
{
if (_bLoading)
return;
// Detach events from previous datasource
if (_ctlDataSource)
{
_ctlDataSource.detachEvent("onrowsdelete", _OnRowsDelete);
if (_tblList)
_tblList.detachEvent("onreadystatechange", _OnTableReady);
}
// Set the datasource
if ("" == szDatasrc)
{
_idDataSource = null;
_ctlDataSource = null;
}
else
{
_idDataSource = szDatasrc;
_SetDataSourceObject(szDatasrc);
}
// Attach events for new datasource
if (_ctlDataSource)
{
_ctlDataSource.attachEvent("onrowsdelete", _OnRowsDelete);
if (_tblList)
_tblList.attachEvent("onreadystatechange", _OnTableReady);
}
}
// Property: autoExpand = true | false
//
// The listbox will expand the selected item's layout table by the number of rows
// as specified by the expandNumRows property.
//
function get_autoExpand() { return _bAutoExpand; }
function put_autoExpand(bAutoExpand)
{
if (_bLoading)
return;
_bAutoExpand = (bAutoExpand) ? true : false;
}
// Property: freeze = true | false
//
// The listbox will be constructed/refreshed only if freeze == false. The behavior
// defaults to false.
//
function get_freeze() { return _bFreeze; }
function put_freeze(bFreeze)
{
if (_bLoading)
return;
_bFreeze = (bFreeze) ? true : false;
}
// Property: templateSource = id
//
// Specifies the id of the SPAN containing the row templates. If this is not
// given the behavior defaults to the interior contents of the behavior element.
// The span must contain a TABLE of TRs.
//
function get_templateSource() { return _idTemplateSource; }
function put_templateSource(szId)
{
if (_bLoading)
return;
_idTemplateSource = szId;
_ResetTemplateStore();
}
// Property: feedBack = Text
//
// Specifies the feedback text of the DIV for our listbox behavior
// This will show/hide the table/feedback DIV as necessary.
//
function get_feedBack() { return _szFeedBack; }
function put_feedBack(szText)
{
_szFeedBack = szText;
// Do we have feed back?
if (_szFeedBack == "")
{
// No, hide the feedback DIV and show the table
if (_dvFeedBack != null)
_dvFeedBack.style.display = 'none';
// Don't show the _tblList here, it might cause a flash,
// _OnTableReady will show it depending on the _szFeedBack value
}
else
{
// Yes, turn on the feedback DIV and turn off the table
if (_dvFeedBack != null)
{
_dvFeedBack.style.display = '';
_dvFeedBack.innerHTML = _szFeedBack;
}
// Hide the table now.
if (_tblList != null)
{
_tblList.style.display = 'none';
}
}
}
// **********************************************************************
// EVENT HANDLERS
// **********************************************************************
/*-------------------------------------------------------------------------
Purpose: When the behavior is completely loaded, set the loading flag to false.
To improve load time, we don't want the put methods on the properties
to be called. We also need to keep events from getting fired while
the behavior is loading.
*/
function _OnContentReady()
{
_bLoading = false;
_PrepHTML();
}
/*-------------------------------------------------------------------------
Purpose: Fired when the databound table fires onreadystatechange
*/
function _OnTableReady()
{
var tblElem = window.event.srcElement;
// Is the table generated?
if ("complete" == tblElem.readyState)
{
if ((null != _tblList) && (_szFeedBack == "") && _tblList.style.display == 'none')
{
// Show the table list when there is no feed back.
_tblList.style.display = '';
}
var recordset = _GetRecordset();
if (recordset && recordset.state != 0)
{
// Fire a custom event for the benefit of the page
evt = createEventObject();
evt.tableList = tblElem;
onListComplete.fire(evt);
// Is there a selection to make?
if (-1 != _iSelDefault && _iSelDefault < _tblList.rows.length)
{
// Yes
_SelectItem(_iSelDefault, true);
}
_bTableComplete = true;
_CalcItemsPerPage();
}
}
else if ("loading" == tblElem.readyState)
{
// Are we downgrading the readystate?
if (_bTableComplete)
{
// Yes; this most likely means the dataset has changed and the
// table is rebuilding.
_InternalResetSelection();
}
else
{
// No; first time
// Trick:
//
// Now display the behavior's element. Waiting until the "loading" state
// prevents the flashing caused by the databinding agent removing the template
// row (we eliminate the "now you see it, now you don't" effect).
_tblList.style.display = '';
}
}
else if ("interactive" == tblElem.readyState)
{
_tblList.style.display = '';
}
}
/*-------------------------------------------------------------------------
Purpose: Fired when the document is loaded. We must refrain from altering
any TABLE element until after the document is loaded.
*/
function _OnDocumentLoad()
{
_ScanSpecialElements();
_CreateHTML();
}
// **********************************************************************
// HELPER FUNCTIONS
// **********************************************************************
/*-------------------------------------------------------------------------
Purpose: Reset the contents of the template rows.
*/
function _ResetTemplateStore()
{
_cRowsExpand = 0;
_rgTemplateRows = new Array();
}
/*-------------------------------------------------------------------------
Purpose: Called when the behavior is instantiated. Sets up the internal
property values.
*/
function _GetPropertyDefaults()
{
if (element.dataSource)
{
_idDataSource = element.dataSource;
_SetDataSourceObject(_idDataSource);
if (_ctlDataSource)
_ctlDataSource.attachEvent("onrowsdelete", _OnRowsDelete);
}
if (element.selectionIndex)
_iSelCur = parseInt(element.selectionIndex);
if (element.defaultSelection)
_iSelDefault = parseInt(element.defaultSelection);
if (element.autoExpand)
_bAutoExpand = ('false' != element.autoExpand) ? true : false;
if (element.freeze)
_bFreeze = ('false' != element.freeze) ? true : false;
if (element.feedBack)
_szFeedBack = element.feedBack;
if (element.templateSource)
_idTemplateSource = element.templateSource;
}
/*-------------------------------------------------------------------------
Purpose: Set the datasource object and (optional) the recordset name.
*/
function _SetDataSourceObject(szDatasrc)
{
var rgszDatasrc = szDatasrc.split('.');
_ctlDataSource = window.document.applets(rgszDatasrc[0])
if (rgszDatasrc.length > 1)
{
_szRecordSetName = rgszDatasrc[1];
}
else
_szRecordSetName = "";
}
/*-------------------------------------------------------------------------
Purpose: Returns the recordset if there is a datasource, otherwise return null.
*/
function _GetRecordset()
{
var recordset = null;
if (_ctlDataSource)
recordset = _ctlDataSource.namedRecordset(_szRecordSetName);
return recordset;
}
/*-------------------------------------------------------------------------
Purpose: Calculate the number of items per page.
*/
function _CalcItemsPerPage()
{
if (null == _tblList || 0 == _tblList.rows.length)
{
_nItemsPerPage = 0;
return;
}
// Determine how many items to jump over by dividing the height of the listbox
// (in pixels) by the height of an unselected (unexpanded) item (in pixels).
// Pick whatever item isn't selected. Choose first or last to make it easy.
var iItem = (_iSelCur != 0) ? 0 : _tblList.rows.length - 1;
var dySelItem = 0;
if (-1 != _iSelCur)
dySelItem = _tblList.rows(_iSelCur).offsetHeight;
if (0 <= iItem)
{
var dyItem = _tblList.rows(iItem).offsetHeight;
if (0 != dyItem)
{
// Since the selection expands, exclude that from the height of
// the listbox, so we'll jump the right number of items.
_nItemsPerPage = Math.round((_divScroll.offsetHeight - dySelItem) / dyItem);
}
}
}
/*-------------------------------------------------------------------------
Purpose: Walk thru the element's html and find the specially-tagged spans.
The special spans can be tagged as follows:
lbDisplayName - marks the element as the display name, which is
drawn differently when selected.
lbExpandedRow - the innerHTML of this span is used to fill the iRow'th
row in the layout table. (Only works if the autoExpand
property is set.)
lbDisable - start off by having this particular template row disabled
*/
function _ScanSpecialElements()
{
var i;
var elem;
// Was a templateSource provided?
if (_idTemplateSource)
{
// Yes; use that as the source for the template rows
elem = window.document.all(_idTemplateSource);
}
else
{
// No; default to the behavior's element
elem = element;
}
// Scan the element for special spans
var rgspan = elem.all.tags("SPAN");
var cspan = rgspan.length;
for (i = 0; i < cspan; i++)
{
var span = rgspan[i];
if (null != span.lbDisplayName)
_idDisplayName = span.id; // Display name span
}
// Scan the element for special rows
var rgtr = elem.all.tags("TR");
var ctr = rgtr.length;
for (i = ctr - 1; i >= 0; i--)
{
var trElem = rgtr[i];
if (null != trElem.lbExpandedRow)
{
var bEnable = (null != trElem.lbDisable) ? false : true;
// This is a template row used when expanding a selection.
// Remember the number of TD cells and their contents, so we
// can reapply them when expanding a selection.
_StoreTemplateRow(trElem, _cRowsExpand++, bEnable);
// Once it is stored, remove the expansion template
trElem.parentElement.deleteRow(trElem.rowIndex);
}
}
// Save the element's orginal innerHTML since it will be overwritten later
_htmlRow = elem.innerHTML;
if (_idTemplateSource)
{
// Clear out the template source
// BUGBUG (scotth): is this really the right thing to do here?
elem.innerHTML = "";
}
}
/*-------------------------------------------------------------------------
Purpose: Prepare the behavior for adding the needed html elements.
*/
function _PrepHTML()
{
if (false == _bFreeze)
{
// We have code that munges the format table. We must wait for
// the document to be loaded before we can do that, so most of
// the work is deferred to then.
window.attachEvent("onload", _OnDocumentLoad);
// In the meantime, make the behavior hidden to reduce the flashing
element.style.display = 'none';
}
}
/*-------------------------------------------------------------------------
Purpose: Adds the HTML code to the main document to display the listbox. The
listbox is composed of a table. The table may be databound.
If the table is databound, the behavior element is treated as the template
row for the databound table.
If the table is not databound, the first table in the behavior element
is considered the skeleton of the list. I.e., each row in the table is a
row in the listbox.
*/
function _CreateHTML()
{
// Is this list databound?
if (_idDataSource)
{
// Yes; the behavior element is considered the template row for the
// databound table's repeating agent.
// The DIV is for the scrollbar. The TABLE inside the DIV is the databound
// table.
element.innerHTML =
'<DIV ' + c_szStyle_Background + '> ' +
' <DIV ' + c_szStyle_Feedback + '> ' +
_szFeedBack +
' </DIV>' +
' <TABLE id=idTbl_' + uniqueID + ' ' + c_szStyle_Table + ' datasrc=#' + _idDataSource + ' cellpadding=0 cellspacing=0 style="display:none"> ' +
' <TBODY> ' +
' <TR> ' +
' <TD width=100%> ' +
_htmlRow +
' </TD> ' +
' </TR> ' +
' </TBODY> ' +
' </TABLE> ' +
'</DIV';
_dvFeedBack = element.children[0].children[0];
_tblList = element.children[0].children[1];
if (_szFeedBack == "")
_dvFeedBack.style.display = 'none';
else
_tblList.style.display = 'none';
// Wait on the 'onreadystatechange' so we know when the table is ready
_tblList.attachEvent("onreadystatechange", _OnTableReady);
}
else
{
// No; the behavior element uses the first table as the model of the list
// Is there any html in the element?
if ("" == _htmlRow)
{
// No; make up a simple table so we will keep working
_htmlRow = '<TABLE></TABLE>';
}
element.innerHTML =
'<DIV ' + c_szStyle_Background + '> ' +
_htmlRow +
'</DIV';
_tblList = element.children[0].children[0];
// Now display the behavior's element
element.style.display = '';
}
_divScroll = element.children[0];
_divScroll.attachEvent("onresize", _OnResize);
_tblList.attachEvent("onclick", _OnClick);
}
/*-------------------------------------------------------------------------
Purpose: Copies certain properties from one element to another
*/
function InheritProperties(elemTo, elemFrom)
{
elemTo['id'] = elemFrom['id'];
elemTo['className'] = elemFrom['className'];
if (elemTo._bStore)
elemTo['tagName'] = elemFrom['tagName'];
// The innerHTML is readonly for TR's
if ('TR' != elemFrom['tagName'])
elemTo['innerHTML'] = elemFrom['innerHTML'];
elemTo['colSpan'] = elemFrom['colSpan'];
}
/*-------------------------------------------------------------------------
Purpose: Take the trTemplate and store it in the table store at iRowStore
*/
function _StoreTemplateRow(trTemplate, iRowStore, bEnable)
{
var ctd = trTemplate.cells.length;
var i;
var rowStore = new Array(ctd);
rowStore._ctd = ctd;
rowStore._bStore = true;
rowStore._bEnable = bEnable;
InheritProperties(rowStore, trTemplate);
// Walk thru the cells and remember their contents
for (i = 0; i < ctd; i++)
{
rowStore[i] = new Object();
rowStore[i]._bStore = true;
InheritProperties(rowStore[i], trTemplate.cells(i));
}
// Save this row
_rgTemplateRows[iRowStore] = rowStore;
}
/*-------------------------------------------------------------------------
Purpose: Deletes the expansion rows to collapse the selection
*/
function _CollapseRow(tblElem)
{
// Collapse the item by removing the expansion rows from
// the format table
var cRow = _cRowsAdded;
for (; 0 < cRow; cRow--)
{
tblElem.deleteRow();
}
_cRowsAdded = 0;
}
/*-------------------------------------------------------------------------
Purpose: Inserts the expansion rows to expand the selection
*/
function _ExpandRow(tblElem)
{
// Collapse the item by removing the expansion rows from
// the format table
var iRow;
_cRowsAdded = 0;
// The templates were added backwards, so insert them backwards so
// they come out correct.
for (iRow = _cRowsExpand - 1; iRow >= 0; iRow--)
{
var rowTemplate = _rgTemplateRows[iRow];
// Is this template enabled?
if (rowTemplate._bEnable)
{
// Yes
var trNew = tblElem.insertRow();
if (trNew)
{
var cCol = rowTemplate._ctd;
var iCol;
InheritProperties(trNew, rowTemplate);
for (iCol = 0; iCol < cCol; iCol++)
{
var tdNew = trNew.insertCell();
if (tdNew)
InheritProperties(tdNew, rowTemplate[iCol]);
}
_cRowsAdded++;
}
}
}
}
// ApplySelectionStyle states
var ASF_SELECT = 0x01;
var ASF_FOCUS = 0x02;
/*-------------------------------------------------------------------------
Purpose: Helper to apply the right styles to the selection
depending on the focus state.
*/
function _ApplySelectionStyle(tblElem, state)
{
// Determine the different elements to change
var elemDisplayName = tblElem.all(_idDisplayName);
var trDisplayName = _GetParentElem(elemDisplayName, "TR");
if (state & ASF_SELECT)
{
// Select; apply the correct class names
if (state & ASF_FOCUS)
{
trDisplayName.style.backgroundColor = 'highlight';
trDisplayName.style.color = 'highlighttext';
elemDisplayName.style.fontWeight = 'bold';
tblElem.style.backgroundColor = 'highlight';
tblElem.style.color = 'highlighttext';
}
else
{
trDisplayName.style.backgroundColor = 'buttonface';
trDisplayName.style.color = '';
elemDisplayName.style.fontWeight = 'bold';
tblElem.style.backgroundColor = 'buttonface';
tblElem.style.color = '';
}
}
else
{
// Deselect; remove the class names and revert to the
// default styles
trDisplayName.style.backgroundColor = '';
trDisplayName.style.color = '';
elemDisplayName.style.fontWeight = '';
tblElem.style.backgroundColor = '';
tblElem.style.color = '';
}
}
/*-------------------------------------------------------------------------
Purpose: Adjusts the scroll position to assure the given TR row
(typically the selection) is visible.
*/
function _MakeRowVisible(trSel)
{
var yTop = trSel.offsetTop - _divScroll.scrollTop;
var yBottom = trSel.offsetTop + trSel.offsetHeight - _divScroll.scrollTop;
// Is the bottom of the row below the listbox?
if (yBottom > _divScroll.offsetHeight)
{
// Yes; adjust
_divScroll.scrollTop += yBottom - _divScroll.offsetHeight;
}
// Is the top of the row above the listbox?
else if (yTop < 0)
{
// Yes; adjust
_divScroll.scrollTop += yTop;
}
}
/*-------------------------------------------------------------------------
Purpose: Sets the tabIndex so this element can receive the focus.
It is only useful to set the tabIndex so the element can receive
the focus in the future (like when the user tabs to it). Setting
the tabIndex does not change the focus in and of itself.
*/
function _SetTabIndex(elem, bSet, bFireFocus)
{
if (elem)
{
if (bSet)
{
// TraceMsg('_SetTabIndex( ' + bSet + ', ' + bFireFocus + ') _bHasFocus = ' + _bHasFocus);
// We're setting the tabIndex on the SPAN in the TD. We could set
// the tabIndex on the TD, but Trident's spec says that
// the MSAA aides get the Name of a TD from its title. But setting
// the title means we see a tooltip. No good.
//
// Alternatively, we can set the tabIndex on the span that shows the
// display name. This is what we'll do.
var tabIndexOriginal = elem.tabIndex;
elem.tabIndex = 0;
// We're checking for a couple of things here:
//
// 1) We only want to set the focus if we think the listbox already
// has the focus. It isn't friendly to steal the focus from another
// control on the page!
// 2) It is possible that the focus event was already fired on this
// element just prior to us getting here, in which case firing
// another focus will do nothing (and mess us up because we set
// _bInternalFocusChange...with nothing to reset it!)
//
if (bFireFocus && _bHasFocus && !elem._bFocus)
{
// Set the focus so the MSAA event is fired
// TraceMsg('_SetTabIndex firing focus');
var focusChangeOriginal = _bInternalFocusChange;
_bInternalFocusChange = true;
try
{
//
// Without this error handler it's possible to set focus
// to an element that is invisible. mshtml produces an error
// when you try to set focus to an invisible item. Therefore,
// we intercept the error and restore object state to
// original values.
//
elem.focus();
}
catch(e)
{
//
// Restore any state changed.
//
elem.tabIndex = tabIndexOriginal;
_bInternalFocusChange = focusChangeOriginal;
}
}
}
else
elem.tabIndex = -1;
}
}
/*-------------------------------------------------------------------------
Purpose: Attach to some events for the given item.
*/
function _AttachItemEvents(elem)
{
// Because the selected item can get the focus, we want to
// attach to some events for it.
//
// Events are inexpensive to be bound to, but they can be
// expensive when initially binding. So we will bind to them
// on demand (ie, as the item is selected), and keep them bound
// even if another item is selected. This is okay.
if (elem)
{
// Have we bound to the events for this item yet?
if (null == elem._lbEventsBound)
{
// No; do it now
elem._lbEventsBound = true;
elem.attachEvent("onkeydown", _OnKeyDownItem);
elem.attachEvent("onblur", _OnBlurItem);
elem.attachEvent("onfocus", _OnFocusItem);
}
}
}
/*-------------------------------------------------------------------------
Purpose: Returns the root child of the given row. This is first child
of the first TD of the row. Typically this is where the row's
content begins.
*/
function _GetItemSrcChild(trElem)
{
if (trElem)
{
return trElem.children[0].children[0];
}
return null;
}
/*-------------------------------------------------------------------------
Purpose: Returns the display name element of the given row.
*/
function _GetItemDisplayNameElem(trElem)
{
var srcChild = _GetItemSrcChild(trElem);
if (srcChild)
return srcChild.all(_idDisplayName);
return null;
}
/*-------------------------------------------------------------------------
Purpose: Select an item. The order of events that are fired is:
Special autoExpand case:
The contents of an item may be anything (pretty much). One exception
is if autoExpand property is set. In this case, the content must be another
table (called the layout table here), which is used to format the layout
of each item. When selected, the listbox will add any number of rows to
the layout table, as specified by the expandNumRows property.
Returns true if the selection was set.
*/
function _SelectItem(iSelNew, bFireFocus)
{
var bRet = false;
var trElem;
var srcChild;
var elemDisplayName;
var stateFocus = _bHasFocus ? ASF_FOCUS : 0;
// Is a row already selected?
if (-1 != _iSelCur)
{
// Yes; is it the same one being selected?
if (iSelNew == _iSelCur)
{
// Yes; ignore it and return
return false;
}
trElem = _tblList.rows(_iSelCur);
srcChild = _GetItemSrcChild(trElem);
elemDisplayName = srcChild.all(_idDisplayName);
_FireCustomDraw(trElem, srcChild, false, 'prepaint');
// Fire event to deselect it
_FireSelectItem(trElem, srcChild, 'deselected');
// Draw to deselect it
_ApplySelectionStyle(srcChild, stateFocus);
if (_bAutoExpand)
_CollapseRow(srcChild);
// Fire event
_FireCustomDraw(trElem, srcChild, false, 'postpaint');
// Remove the tabIndex since this item is no longer selected
_SetTabIndex(elemDisplayName, false, false);
// We don't detach the events, because we don't need to
}
// Check boundaries
if (0 == _tblList.rows.length)
iSelNew = -1;
else if (iSelNew >= _tblList.rows.length)
iSelNew = _tblList.rows.length - 1;
// Remember it as the currently open item
_iSelCur = iSelNew;
if (-1 != _iSelCur)
{
var recordset = _GetRecordset();
// Select the item
trElem = _tblList.rows(_iSelCur);
srcChild = _GetItemSrcChild(trElem);
elemDisplayName = srcChild.all(_idDisplayName);
// If this list is databound, update the record position
if (recordset && recordset.state != 0)
recordset.AbsolutePosition = trElem.recordNumber;
_FireCustomDraw(trElem, srcChild, true, 'prepaint');
// Draw to select it
_ApplySelectionStyle(srcChild, ASF_SELECT | stateFocus);
if (_bAutoExpand)
_ExpandRow(srcChild);
_MakeRowVisible(trElem);
_FireCustomDraw(trElem, srcChild, true, 'postpaint');
// Fire event to select it
_FireSelectItem(trElem, srcChild,'selected');
// Set the tabIndex. The tabIndex is set so MSAA can receive
// notifications of 'focus' change as each item is selected.
// Also, don't fire the focus event if this selection is due
// to a mouse action -- the focus event will automatically
// be fired.
_SetTabIndex(elemDisplayName, true, bFireFocus);
_AttachItemEvents(elemDisplayName);
bRet = true;
}
return bRet;
}
/*-------------------------------------------------------------------------
Purpose: Set the focus state of the selected item
*/
function _PaintSelectedItem(stateFocus)
{
if (-1 < _iSelCur)
{
// Select the item
var trElem = _tblList.rows(_iSelCur);
if (trElem)
{
var srcChild = _GetItemSrcChild(trElem);
if (srcChild)
{
var elemDisplayName = srcChild.all(_idDisplayName);
_ApplySelectionStyle(srcChild, ASF_SELECT | stateFocus);
_FireSetFocus(trElem, srcChild, stateFocus & ASF_FOCUS);
}
}
}
}
/*-------------------------------------------------------------------------
Purpose: Fires 'onSelectItem' to the document
*/
function _FireSelectItem(trElem, srcChild, szState)
{
var evt = createEventObject();
evt.srcIndex = _iSelCur;
evt.srcRow = trElem;
evt.selState = szState;
evt.srcChild = srcChild;
evt.Recordset = _GetRecordset();
onSelectItem.fire(evt);
}
/*-------------------------------------------------------------------------
Purpose: Fires 'onCustomDraw' to the document
*/
function _FireCustomDraw(trElem, srcChild, bSelected, drawStage)
{
var evt = createEventObject();
evt.srcIndex = _iSelCur;
evt.srcRow = trElem;
evt.srcChild = srcChild;
evt.Recordset = _GetRecordset();
evt.drawStage = drawStage; // 'prepaint' or 'postpaint'
evt.bSelected = bSelected;
evt.bFocus = _bHasFocus;
onCustomDraw.fire(evt);
}
/*-------------------------------------------------------------------------
Purpose: Fires 'onSetFocus' to the document
*/
function _FireSetFocus(trElem, srcChild, bFocus)
{
var evt = createEventObject();
evt.srcIndex = _iSelCur;
evt.srcRow = trElem;
evt.srcChild = srcChild;
evt.bFocus = bFocus;
onSetFocus.fire(evt);
}
function _FindTemplateByID(id)
{
var i;
for (i = 0; i < _cRowsExpand; i++)
{
if (_rgTemplateRows[i].id == id)
return _rgTemplateRows[i];
}
return null;
}
/*-------------------------------------------------------------------------
Purpose: Enable/disable a given expanded template
*/
function EnableTemplate(idTemplate, bEnable)
{
var rowTemplate = _FindTemplateByID(idTemplate);
if (rowTemplate)
rowTemplate._bEnable = bEnable ? true : false;
}
/*-------------------------------------------------------------------------
Purpose: Cause the listbox to be reconstructed based upon the current
properties
*/
function Refresh()
{
_ScanSpecialElements();
_CreateHTML();
}
/*-------------------------------------------------------------------------
Purpose: Overridden method so we handle focus properly
*/
function focus()
{
if (_tblList && -1 != _iSelCur)
{
var trElem = _tblList.rows(_iSelCur);
var elem = _GetItemDisplayNameElem(trElem);
if (elem)
{
try
{
//
// Without this error handler it's possible to set focus
// to an element that is invisible. mshtml produces an error
// when you try to set focus to an invisible item. Therefore,
// we intercept the error and ignore it.
//
elem.focus();
}
catch(e)
{
// ignore
}
}
}
else
{
// We failed to really set the focus, but let's maintain
// our state correctly, anyway.
_bHasFocus = true;
}
}
/*-------------------------------------------------------------------------
Purpose: Overridden method so we handle blur properly
*/
function blur()
{
if (_tblList && -1 != _iSelCur)
{
var trElem = _tblList.rows(_iSelCur);
var elem = _GetItemDisplayNameElem(trElem);
if (elem)
elem.blur();
}
else
{
// We failed to really remove the focus, but let's maintain
// our state correctly, anyway.
_bHasFocus = false;
}
}
/*-------------------------------------------------------------------------
Purpose: Should be bound to the 'onrowsdelete' event of the datasource object.
*/
function _OnRowsDelete()
{
// Only handle events for my recordset!
if (window.event.qualifier == _szRecordSetName)
{
if (-1 != _iSelCur)
{
var trSelCur = _tblList.rows(_iSelCur);
var recordset = _GetRecordset();
var rsClone = recordset.Clone();
if (null != rsClone)
{
// (These bookmarks refer to record(s) that were deleted)
var rgbkmk = window.event.bookmarks;
var cbkmk = rgbkmk.length;
var i;
// For each bookmark that was deleted, see if it was the selected
// one.
for (i = 0; i < cbkmk; i++)
{
// Navigate to the record (in the cloned recordset) that was
// deleted in the real recordset.
rsClone.Bookmark = rgbkmk(i);
// Is the record that was deleted the same as what is currently
// selected?
if (rsClone.AbsolutePosition == trSelCur.recordNumber)
{
// Yes; select the row below this one. Note the databinding
// agent hasn't deleted the row yet, so we need to choose the
// next one, but then adjust the selection index to accomodate
// for the deletion
var iSelT = _iSelCur + 1;
var bDec = true;
if (_iSelCur == _tblList.rows.length - 1)
{
iSelT = _iSelCur - 1; // choose the one above, -1 is okay
bDec = false;
}
_SelectItem(iSelT, true);
if (bDec)
{
// Adjust the selection index since we know the row above will
// be deleted
_iSelCur--;
}
}
}
}
}
}
}
/*-------------------------------------------------------------------------
Purpose: Called to reset the selection state. The table may be rebuilding,
so there are no elements in the DOM to actually modify.
*/
function _InternalResetSelection()
{
_iSelCur = -1;
_cRowsAdded = 0;
}
/*-------------------------------------------------------------------------
Purpose: Called to reset the selection. Typically this happens when the dataset
has changed in the databound table.
*/
function ResetSelection()
{
_SelectItem(-1, true);
}
// Key codes
var KC_UP = 38;
var KC_DOWN = 40;
var KC_END = 35;
var KC_HOME = 36;
var KC_PGUP = 33;
var KC_PGDOWN = 34;
/*-------------------------------------------------------------------------
Purpose: Handle the onKeyDown events on the behavior
*/
function _OnKeyDown()
{
var keyCode = window.event.keyCode;
switch (keyCode)
{
case KC_UP:
case KC_DOWN:
case KC_END:
case KC_HOME:
case KC_PGUP:
case KC_PGDOWN:
// Cancel event so div doesn't scroll independently.
window.event.returnValue = false;
break;
}
}
/*-------------------------------------------------------------------------
Purpose: Handle the onKeyDown events on the item
*/
function _OnKeyDownItem()
{
var keyCode = window.event.keyCode;
var iSelT = _iSelCur;
var bDoSomething = false;
switch (keyCode)
{
case KC_UP:
if (iSelT > 0)
iSelT--;
break;
case KC_DOWN:
if (iSelT < _tblList.rows.length - 1)
iSelT++;
break;
case KC_HOME:
iSelT = 0;
break;
case KC_END:
iSelT = _tblList.rows.length - 1;
break;
case KC_PGUP:
iSelT = _iSelCur - _nItemsPerPage;
if (iSelT < 0)
iSelT = 0;
break;
case KC_PGDOWN:
iSelT = _iSelCur + _nItemsPerPage;
if (iSelT > _tblList.rows.length - 1)
iSelT = _tblList.rows.length - 1;
break;
}
if (iSelT != _iSelCur)
{
_SelectItem(iSelT, true);
// Cancel event so div doesn't scroll independently
window.event.returnValue = false;
}
}
/*-------------------------------------------------------------------------
Purpose: Handle the onResize event for the scrolling div
*/
function _OnResize()
{
_CalcItemsPerPage();
}
/*-------------------------------------------------------------------------
Purpose: Debug spew
*/
/* Fake version
function TraceFocusMsg(sz, elem)
{
var trTopParent = _GetTopmostElem(elem, "TR");
var iRow = "??";
var szFocus = " ";
var szInternal = " ";
if (trTopParent)
iRow = trTopParent.rowIndex;
if (_bHasFocus)
szFocus = " bHasFocus";
if (_bInternalFocusChange)
szInternal = " internal";
// TraceMsg(sz + ' ' + iRow + szFocus + szInternal);
}
*/
/*-------------------------------------------------------------------------
Purpose: Handle the onClick event
*/
function _OnClick()
{
var elem = window.event.srcElement;
// TraceFocusMsg('onclick', elem);
// A lot of this work is done because of Trident's inane view of focus.
// Trident doesn't have the concept that a group of elements can make up
// a single, logical entity, and then treat one as a part of the whole.
//
// Because of this, we have a bunch of elements, and we designate one of
// them (the display name span) to be the representative element for focus
// issues. That is, if the listbox item is selected and focus goes to the
// listbox, we set the display name span of that item to have the focus.
//
// This presents wierd behavior whenever other elements (which make up that
// item) are clicked: the focus goes away from the display name span. Not
// what we want. So we must re-set the focus back to the span in this case.
// Walk up the chain of elements until we find the _tblList's row that was clicked
var trTopParent = _GetTopmostElem(elem, "TR");
// Did we find a row at all (we wouldn't in an empty table)?
if (trTopParent)
{
// Yes; select the item
var elemDisplayName = trTopParent.all(_idDisplayName);
var iRow = trTopParent.rowIndex;
var bFireFocus = true;
var bOldFocus = _bHasFocus;
// Was the display name itself clicked and will it automatically get
// the focus (because it already has tabIndex set)?
if (elem == elemDisplayName && iRow == _iSelCur)
{
// Yes; then don't bother explicitly setting the focus to it.
// TraceMsg(elemDisplayName.innerText + ' ' + elem._bFocus + ' don't fire focus');
bFireFocus = false;
}
_bHasFocus = true;
// Do we need to repaint the selection according to the state change?
if (false == _SelectItem(iRow, bFireFocus) &&
bOldFocus != _bHasFocus)
{
// Yes
_PaintSelectedItem(ASF_FOCUS);
if (bFireFocus)
{
// Set the focus so the MSAA event is fired
// TraceMsg('_onClick invoking _SetTabIndex');
_SetTabIndex(elemDisplayName, true, true);
}
}
}
}
/*-------------------------------------------------------------------------
Purpose: Handle the onBlur event. Change the selection color so it is clear
the list no longer has the focus.
*/
function _OnBlurItem()
{
var elem = window.event.srcElement;
elem._bFocus = false;
// TraceFocusMsg('onblur', elem);
if (true == _bHasFocus && false == _bInternalFocusChange)
{
_bHasFocus = false;
// Redraw the current selection to indicate this control
// has lost the focus
_PaintSelectedItem(0);
}
_bInternalFocusChange = false;
}
/*-------------------------------------------------------------------------
Purpose: Handle the onFocus event. Change the selection color.
The currently selected item has the tabIndex property == 0. This
handles the case when the user tabs around the page.
The second case is if the user uses the mouse to select one
of the listbox entries. In this case, the anchor will not
receive focus, but we have to pretend as if it did. This is
why _bHasFocus keeps track of this.
*/
function _OnFocusItem()
{
var elem = window.event.srcElement;
elem._bFocus = true;
// TraceFocusMsg('onfocus', elem);
if (false == _bHasFocus)
{
_bHasFocus = true;
// Redraw the current selection to indicate this control
// has focus now
_PaintSelectedItem(ASF_FOCUS);
}
_bInternalFocusChange = false;
}
/*-------------------------------------------------------------------------
Purpose: Handle the onerror event
*/
function _OnError(szMsg, szUrl, iLine)
{
// Prevent scripting errors from displaying ugly messages
alert("An unexpected error occurred.\n\n" + szMsg + "\n" + szUrl + "\nLine: " + iLine);
return true; // Suppress IE error messaging
}
/*-------------------------------------------------------------------------
Purpose: Finds and returns the topmost element of type tagFind, or null if
not found
*/
function _GetTopmostElem(elem, tagFind)
{
// Walk up the chain of elements until we find the immediate parent row element
var elemLast = null;
while (elem != element)
{
if (tagFind == elem.tagName.toUpperCase())
elemLast = elem;
elem = elem.parentElement;
}
return elemLast;
}
/*-------------------------------------------------------------------------
Purpose: Finds and returns the immediate parent element of type tagFind, or
null if not found
*/
function _GetParentElem(elem, tagFind)
{
// Walk up the chain of elements until we find the immediate parent row element
while (elem != element)
{
if (tagFind == elem.tagName.toUpperCase())
return elem;
elem = elem.parentElement;
}
return null;
}
</SCRIPT>
//------------------------------------------------------------------------
// Properties
//------------------------------------------------------------------------
<PROPERTY name="dataSource" get=get_dataSource put=put_dataSource />
<PROPERTY name="selectionIndex" get=get_selIndex put=put_selIndex />
<PROPERTY name="defaultSelection" get=get_defaultSel put=put_defaultSel />
<PROPERTY name="autoExpand" get=get_autoExpand put=put_autoExpand />
<PROPERTY name="freeze" get=get_freeze put=put_freeze />
<PROPERTY name="feedBack" get=get_feedBack put=put_feedBack />
<PROPERTY name="templateSource" get=get_templateSource put=put_templateSource />
</PUBLIC:HTC>