//============================================================================== // Copyright 2010-2025 Moraware, Inc. // // Purpose: Supports a search popup window for assisted selection list // population. Allows you to type in the textbox and // (asynchronously) bring up a selection list of available options. // //============================================================================== /* eslint no-unused-vars: "off" */ /* eslint no-eval: "off" */ /*global getObj trim htmlEncode checkResponseForError doPositionPicker setFocus // ConsoleLogging.js ConsoleLogging // Found in ShopSharedFunctions.js cancelEvent mjtElemData // Found in ShopSharedFullFunctions.js checkAttribute stringStartsWith // StringificationUtils.js StringificationUtils // Found in ClientFunctions.js setFocusIfFocusable scrollElementIntoViewWithinContainerElement */ var g_typeAheadTextChangeTimer = null; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function isTypeaheadDivShowing(objTypeAheadDiv_) { return objTypeAheadDiv_.style.display !== 'none'; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function setInvalidTextClass(objTargetField_, isInvalid_) { var strClassName = objTargetField_.className, strInvalidClassName = 'invalidTextField'; if (isInvalid_) { if (strClassName.indexOf(strInvalidClassName) < 0) { strClassName = trim(strClassName + ' ' + strInvalidClassName); } } else { strClassName = trim(strClassName.replace(strInvalidClassName, '')); } objTargetField_.className = strClassName; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function updateTypeaheadTextboxValidation(params_) { var objTypeAheadDiv = params_.typeaheadDiv; if (objTypeAheadDiv.typeAheadConfigParams && objTypeAheadDiv.typeAheadConfigParams.forceMatchItem) { // If the field doesn't match an item, mark it as invalid... var theForm = getObj(objTypeAheadDiv.typeAheadConfigParams.formId) || 0, objTextField = theForm[objTypeAheadDiv.typeAheadConfigParams.textFieldName] || 0, objIdField = theForm[objTypeAheadDiv.typeAheadConfigParams.idFieldName] || 0, strTextFieldValue = objTextField.value || ''; if (objIdField.value || !strTextFieldValue.length) { // Either there is a matching selection or the field is empty. // Regardless, clear the invalid marker. setInvalidTextClass( objTextField, 0); // isInvalid_ } else if (objTextField.form && objTextField.form.ignoreFieldValue && objTextField.form.ignoreFieldValue.value === strTextFieldValue) { setInvalidTextClass( objTextField, 0); // isInvalid_ } else { setInvalidTextClass( objTextField, 1); // isInvalid_ } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function hideTypeAheadDiv() { var objTypeAheadDiv = getObj('typeAheadDiv'), rc = 0; if (objTypeAheadDiv) { //Before hiding the typeahead div //update the validation status if necessary updateTypeaheadTextboxValidation({ typeaheadDiv: objTypeAheadDiv }); rc = isTypeaheadDivShowing(objTypeAheadDiv); objTypeAheadDiv.scrollTop = 0; objTypeAheadDiv.ignoreBlur = false; objTypeAheadDiv.typeAheadConfigParams = null; objTypeAheadDiv.innerHTML = ''; objTypeAheadDiv.style.display = 'none'; } return rc; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function hideTypeAheadDivIfNotTypeAheadDiv(clickedElement_) { var objTypeAheadDiv = getObj('typeAheadDiv'), objTypeAheadConfigParams = (objTypeAheadDiv || {}).typeAheadConfigParams; if (objTypeAheadConfigParams) { var doHideTypeAhead = true, theForm = getObj(objTypeAheadConfigParams.formId), objTextField = (theForm || {})[objTypeAheadConfigParams.textFieldName]; if (objTextField) { doHideTypeAhead = clickedElement_ !== objTextField; } if (doHideTypeAhead) { hideTypeAheadDiv(); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function fireTypeAheadOnChangeIfNecessary(params_) { var objTextControl = params_.textField, tmpUseAsynchForOnChange = params_.useAsynchForOnChange, strOnChangeFunction = objTextControl.getAttribute('mjttypeaheadonchange'); if (strOnChangeFunction) { if (tmpUseAsynchForOnChange) { setTimeout(function () { eval(unescape(strOnChangeFunction)); }, 0); } else { eval(unescape(strOnChangeFunction)); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function handleExplicitTypeAheadSelection(params_) { params_ = params_ || {}; var objResultOptions = params_.resultOptions, rc = false, objTypeAheadDiv = getObj('typeAheadDiv'); if (objTypeAheadDiv) { var typeAheadConfigParams = objTypeAheadDiv.typeAheadConfigParams, fnHandleExplicitSelection = typeAheadConfigParams ? StringificationUtils.getOrResolveFunction({ functionNameOrReference: typeAheadConfigParams.fnHandleExplicitSelection, descriptionOfFunctionBeingResolved: 'typeAheadConfigParams.fnHandleExplicitSelection', suppressErrorsOnMissingNameOrReference: 1 }) : null, fnHandleExplicitSelectionP = typeAheadConfigParams ? StringificationUtils.getOrResolveFunction({ functionNameOrReference: typeAheadConfigParams.fnHandleExplicitSelectionP, descriptionOfFunctionBeingResolved: 'typeAheadConfigParams.fnHandleExplicitSelectionP', suppressErrorsOnMissingNameOrReference: 1 }) : null, objExplicitSelectionResultOptions = { }; if (fnHandleExplicitSelection || fnHandleExplicitSelectionP) { if (typeof objTypeAheadDiv.selectedRowIndex !== 'undefined' && objTypeAheadDiv.selectedRowIndex !== null && objTypeAheadDiv.selectedRowIndex > -1) { var objSelectedRow = getObj('typeAheadRow' + objTypeAheadDiv.selectedRowIndex); if (objSelectedRow) { if (fnHandleExplicitSelectionP) { fnHandleExplicitSelectionP({ idxSelectedRow: objTypeAheadDiv.selectedRowIndex, selectedRow: objSelectedRow, formId: typeAheadConfigParams.formId, typeAheadConfigParams: typeAheadConfigParams, explicitSelectionFnParameter: typeAheadConfigParams.explicitSelectionFnParameter, arrTypeaheadRowVals: mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals') || null, explicitSelectionResultOptions: objExplicitSelectionResultOptions }); if (objResultOptions) { objResultOptions.suppressSetFocus = objExplicitSelectionResultOptions.suppressSetFocus; } } else { fnHandleExplicitSelection( objTypeAheadDiv.selectedRowIndex, objSelectedRow, typeAheadConfigParams.formId, typeAheadConfigParams, typeAheadConfigParams.explicitSelectionFnParameter, mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals') || null); } rc = true; } else { if (typeAheadConfigParams.fnHandleExplicitEmptySelection) { if (typeAheadConfigParams.fnHandleExplicitEmptySelection()) { rc = true; } } } } else { if (typeAheadConfigParams.fnHandleExplicitEmptySelection) { if (typeAheadConfigParams.fnHandleExplicitEmptySelection()) { rc = true; } } } } } return rc; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function scrollTypeaheadSelectionIntoViewIfNecessary() { var objTypeAheadDiv = getObj('typeAheadDiv'); if (!objTypeAheadDiv.typeAheadConfigParams) { // The type-ahead has already been disimissed. return; } if (-1 === objTypeAheadDiv.selectedRowIndex) { // No selection...no need to scroll... return; } var objSelectedRow = getObj('typeAheadRow' + objTypeAheadDiv.selectedRowIndex); if (objSelectedRow) { scrollElementIntoViewWithinContainerElement(objSelectedRow, objTypeAheadDiv); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function getLastTypeaheadDisplayValue(objTextField_) { return objTextField_.getAttribute('data-mjtLastTypeAheadDisplayValue'); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function setLastTypeaheadDisplayValue(objTextField_, displayValue_, dontSetFieldValue_) { objTextField_.setAttribute('data-mjtLastTypeAheadDisplayValue', displayValue_); if (!dontSetFieldValue_) { objTextField_.value = displayValue_; } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function checkIfTypeaheadDisplayValueChangedAndSetLastValue(objTextField_) { var strLastValue = getLastTypeaheadDisplayValue(objTextField_), strCurrValue = objTextField_.value, rc = strLastValue !== strCurrValue; if (rc) { setLastTypeaheadDisplayValue( objTextField_, strCurrValue, // displayValue_ 1); // dontSetFieldValue_ } return rc; } //------------------------------------------------------------------------------ // // TODO: Ideally, we'd simply be using element.classList to add and remove the // style classes we need. However, to keep differences simpler until such time // as we at least remove v1 support, this function will be used to set a class // while exclusively preserving the new "typeAheadPriorSelection" style class // and ignoring any others (as has always been done). // //------------------------------------------------------------------------------ function setClassWhileRetainingPriorSelectionInd(params_) { var objElement = params_.targetElement, strNewClassName = params_.newClassName, tmpWasMarkedPriorSelection; if (objElement) { tmpWasMarkedPriorSelection = objElement.classList.contains('typeAheadPriorSelection'); objElement.className = strNewClassName + (tmpWasMarkedPriorSelection ? ' typeAheadPriorSelection' : ''); objElement.setAttribute('mjtclass', objElement.className); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function selectTypeAheadItem(rowIndex_, activeSelection_, setFocus_, event) { var rc = false, objTypeAheadDiv = getObj('typeAheadDiv'); if (event && event.button && event.button !== 1) { return false; } if (!objTypeAheadDiv.typeAheadConfigParams) { // Already possible duplicate select call. // Regardless, there's no typeahead config, so there's nothing to do... return false; } objTypeAheadDiv.ignoreBlur = setFocus_ ? true : false; if (objTypeAheadDiv.selectedRowIndex !== rowIndex_) { setClassWhileRetainingPriorSelectionInd({ targetElement: getObj( 'typeAheadRow' + objTypeAheadDiv.selectedRowIndex), newClassName: 'typeAheadRow' }); } objTypeAheadDiv.selectedRowIndex = rowIndex_; var objSelectedRow = getObj('typeAheadRow' + rowIndex_), objTypeaheadConfigParams = objTypeAheadDiv.typeAheadConfigParams, strFormId = objTypeaheadConfigParams.formId, strTextFieldName = objTypeaheadConfigParams.textFieldName, strIdFieldName = objTypeaheadConfigParams.idFieldName, tmpAsyncFocusOnSelect = objTypeaheadConfigParams.asyncFocusOnSelect, theForm = getObj(strFormId), objCurrText = theForm[strTextFieldName], objExplicitSelectionResultOptions = {}; if (objSelectedRow) { rc = true; // Highlight the newly selected row... setClassWhileRetainingPriorSelectionInd({ targetElement: objSelectedRow, newClassName: 'typeAheadSelectedRow' }); // Now copy the value of the newly selected row into the textbox. var objCurrId = theForm[strIdFieldName]; // Got a valid selection, so clear the invalid indicator. setInvalidTextClass( objCurrText, 0); // isInvalid_ // Be sure to ignore changes here. // Don't wipe out the list simply because they've arrowed down... objTypeAheadDiv.ignoreTextChange = true; if (g_typeAheadTextChangeTimer) { clearTimeout(g_typeAheadTextChangeTimer); } setLastTypeaheadDisplayValue( objCurrText, // objTextField_ unescape(objSelectedRow.getAttribute('mjtvalue'))); // displayValue_, dontSetFieldValue_ objCurrId.value = unescape(objSelectedRow.getAttribute('mjtid')); objTypeAheadDiv.ignoreTextChange = false; scrollTypeaheadSelectionIntoViewIfNecessary(); fireTypeAheadOnChangeIfNecessary({ textField: objCurrText, useAsynchForOnChange: false }); if (activeSelection_) { handleExplicitTypeAheadSelection({ resultOptions: objExplicitSelectionResultOptions }); } } if (setFocus_) { if (!objExplicitSelectionResultOptions.suppressSetFocus) { if (tmpAsyncFocusOnSelect) { setTimeout( function () { setFocus(objCurrText); }, 0); } else { setFocus(objCurrText); } } if (activeSelection_) { // Hide the type-ahead since we're actively selecting an item // and the focus event might otherwise force it to re-display. hideTypeAheadDiv(); } } return rc; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function removeTypeAheadSelectionOrHoverHighlight(removeSelHightlight_) { var i, objRow, strTargetClass = removeSelHightlight_ ? 'typeAheadSelectedRow' : 'typeAheadHoverRow'; objRow = getObj('typeAheadRow0'); for (i = 0; objRow; ++i) { if (objRow.className === strTargetClass) { objRow.className = 'typeAheadRow'; if (!removeSelHightlight_) { var objTypeAheadDiv = getObj('typeAheadDiv'); objTypeAheadDiv.selectedRowIndex = i; } } objRow = getObj('typeAheadRow' + (i + 1)); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function mousedownOnTypeahead(event) { var objTypeAheadDiv = getObj('typeAheadDiv'); objTypeAheadDiv.ignoreBlur = true; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function blurTypeahead(event) { hideTypeAheadDiv(); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function setFocusToTextField(formId_, fieldName_) { var theForm = getObj(formId_); if (theForm) { var objField = theForm[fieldName_]; if (objField) { setFocus(objField); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function clearSelection() { var objTypeAheadDiv = getObj('typeAheadDiv'); if (objTypeAheadDiv) { var objSelectedRow = getObj('typeAheadRow' + objTypeAheadDiv.selectedRowIndex); if (objSelectedRow) { objSelectedRow.className = 'typeAheadRow'; objTypeAheadDiv.selectedRowIndex = -1; } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function getNoMatchFoundHtml( taConfig_, additionalLastRowHtml_, originalPayload_, fnNoMessagePostfix_) { var strNoDataMessageOverride = taConfig_.noDataMessage, strNoMessagePostfix = fnNoMessagePostfix_ ? fnNoMessagePostfix_( taConfig_.showAdditionalLastRow, originalPayload_) : '', strNoDataMessageToUse = strNoDataMessageOverride ? strNoDataMessageOverride + strNoMessagePostfix : 'No matches found', strNoMatchFoundAdditionalClasses = taConfig_.typeAheadTableAdditionalClasses, strClassAtr = strNoMatchFoundAdditionalClasses ? ' class="' + strNoMatchFoundAdditionalClasses + '" ' : '', strNoMatchFoundHTML = '' + '' + '' + ''; if (taConfig_) { if(additionalLastRowHtml_ && taConfig_.showAdditionalLastRow) { strNoMatchFoundHTML += additionalLastRowHtml_; } } strNoMatchFoundHTML += '
' + strNoDataMessageToUse + '
'; return strNoMatchFoundHTML; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function processTypeAheadSearchTextArray( arrEscapedValues_, forceFireTypeAheadOnChange_, areMoreItems_, idColumnIndex_, nameColumnIndex_, isInactiveColumnIndex_, extraHTMLColumnIndex_, inactiveMeansCompleteColumnIndex_, forceIgnoreCurrentTextForBold_) { var objTypeAheadDiv = getObj('typeAheadDiv'), objTAConfig = objTypeAheadDiv.typeAheadConfigParams; if (!objTAConfig) { // The type-ahead has already been dismissed. return; } // Make sure that clicking on the typeahead scrollbar doesn't cause the typeahead // to be hidden and also make sure that once the typeahead loses focus, blur handling // is turned back on again. objTypeAheadDiv.onmousedown = mousedownOnTypeahead; objTypeAheadDiv.onblur = blurTypeahead; objTypeAheadDiv.ignoreBlur = false; objTypeAheadDiv.selectedRowIndex = -1; var theForm = getObj(objTAConfig.formId); if (!theForm) { // Not sure what may have gone wrong here. Don't bother continuing. ConsoleLogging.logBlockClose('no Form! looking for formId=' + objTAConfig.formId); return; } var objCurrText = theForm[objTAConfig.textFieldName], objCurrId = theForm[objTAConfig.idFieldName], originalIdValue = objCurrId ? objCurrId.value : null, isText = objCurrText.value.length > 0, tmpIsExactMatch = false, tmpMatchedOriginalIdValue = false, doAutoMatch = objTAConfig.autoMatchOnKeyInput; if (typeof extraHTMLColumnIndex_ === 'undefined' || null === extraHTMLColumnIndex_ || extraHTMLColumnIndex_ < 0) { extraHTMLColumnIndex_ = -1; } if (!(arrEscapedValues_ && arrEscapedValues_.length)) { if(isText) { var strNoMatchFoundHtml = getNoMatchFoundHtml(objTAConfig); objTypeAheadDiv.style.display = 'none'; objTypeAheadDiv.style.zIndex = 501; objTypeAheadDiv.innerHTML = strNoMatchFoundHtml; setTimeout(doPositionPicker, 0); } else { mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals', null); hideTypeAheadDiv(); if (forceFireTypeAheadOnChange_) { fireTypeAheadOnChangeIfNecessary({ textField: objCurrText, useAsynchForOnChange: false }); } } } else { setInvalidTextClass( objCurrText, 0); // isInvalid_ var strColspanAttr = (extraHTMLColumnIndex_ > -1) ? ' colspan="2"' : '', strAdditionalClasses = (objTAConfig) ? (objTAConfig.typeAheadTableAdditionalClasses || '') : '', strHTML = '' + '', currTextRaw = objCurrText.value, currText = (forceIgnoreCurrentTextForBold_ || objTAConfig.ignoreCurrentTextForBold) ? '' : currTextRaw, rowId = 0, i, arrTypeaheadRowVals = []; objTypeAheadDiv.itemCount = arrEscapedValues_.length; mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals', arrTypeaheadRowVals); for (i = 0; i < arrEscapedValues_.length; ++i) { var strRowValue = arrEscapedValues_[i], strOnMouseDown = 'onmousedown="selectTypeAheadItem(' + rowId + ',' + 'true,' + // activeSelection_ 'true,' + // setFocus_ 'event)" ', strOnMouseUp = 'onmouseup="hideTypeAheadDiv();' + 'setFocusToTextField(' + '\'' + theForm.id + '\',' + '\'' + objCurrText.name + '\');"'; if (strRowValue === '-') { // It's a separator! --objTypeAheadDiv.itemCount; strHTML += '' + '' + ''; } else { var arrRowValues = strRowValue.split(':'); arrTypeaheadRowVals[i] = arrRowValues; var escapedId = arrRowValues[idColumnIndex_], escapedName = arrRowValues[nameColumnIndex_], name = unescape(escapedName); if ((!tmpMatchedOriginalIdValue) && name && name.toLowerCase() === currTextRaw.toLowerCase()) { // If there is more than one product with the same name, we want to select the first item // with that name, unless the previously selected item is also an identical match, but is NOT // the FIRST such match. var strUnescapedId = unescape(escapedId), tmpThisRowMatchesTheOriginalId = objCurrId && (strUnescapedId === originalIdValue); if (doAutoMatch && (tmpThisRowMatchesTheOriginalId || !tmpIsExactMatch)) { // Either this row is an exact match and its id matches the previously selected id, // or it is simply the first exact match. // // In the former case, we definitely want it to be the selected row (and definitely // won't want any other row to be selected). // // In the latter case, we want this to be the selected row unless there is a later // row that is also an exact match, and that is the same row as was previously selected. if (objCurrId) { objCurrId.value = strUnescapedId; } tmpMatchedOriginalIdValue = tmpMatchedOriginalIdValue || tmpThisRowMatchesTheOriginalId; tmpIsExactMatch = true; objTypeAheadDiv.selectedRowIndex = rowId; } } var tmpHasInactiveColumn = typeof isInactiveColumnIndex_ !== 'undefined' && isInactiveColumnIndex_ !== null && isInactiveColumnIndex_ > -1, isInactive = tmpHasInactiveColumn ? '1' === arrRowValues[isInactiveColumnIndex_] : false, tmpHasInactiveMeansCompleteColumn = typeof inactiveMeansCompleteColumnIndex_ !== 'undefined' && inactiveMeansCompleteColumnIndex_ !== null && inactiveMeansCompleteColumnIndex_ > -1, inactiveMeansComplete = tmpHasInactiveMeansCompleteColumn ? '1' === arrRowValues[inactiveMeansCompleteColumnIndex_] : false, strBolded = name.substring(0, currText.length), strPostBolded = name.substring(currText.length), strRow = '' + ''; if (extraHTMLColumnIndex_ > -1) { strRow += ''; } strRow += ''; strHTML += strRow; ++rowId; } } if (areMoreItems_) { var strMoreMsg = ' There are more items' + (currText ? ' starting with "' + htmlEncode(currText) + '"' : ''); strHTML += '' + '' + strMoreMsg + '' + ''; } strHTML += '' + '
'; if (isInactive) { strRow += ''; } strRow += '' + htmlEncode(strBolded) + '' + htmlEncode(strPostBolded); if (isInactive) { strRow += ''; } strRow += ''; if (extraHTMLColumnIndex_ < arrRowValues.length) { strRow += unescape(arrRowValues[extraHTMLColumnIndex_]); } strRow += '
'; objTypeAheadDiv.style.display = 'none'; objTypeAheadDiv.style.zIndex = 501; objTypeAheadDiv.innerHTML = strHTML; if (tmpIsExactMatch && doAutoMatch) { var objSelectedRow = getObj('typeAheadRow' + objTypeAheadDiv.selectedRowIndex); objSelectedRow.className = 'typeAheadSelectedRow'; objSelectedRow.setAttribute('mjtclass', 'typeAheadSelectedRow'); // We've found a match (and thus set the selected row and id value (if there is one) so // fire the onchange event (if there is one) fireTypeAheadOnChangeIfNecessary({ textField: objCurrText, useAsynchForOnChange: false }); } setTimeout(doPositionPicker, 0); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ // Processes Remote Scripting responses. function rsCallbackGetTypeAheadSearchText(responseString_, contextId_, processResultParams_) { var objTypeAheadDiv = getObj('typeAheadDiv'), objTAConfig = objTypeAheadDiv.typeAheadConfigParams; if (!objTAConfig) { // The type-ahead has already been dismissed. return; } //At this point we know that a type-ahead is currently open, but we can't be sure that //1) This callback is for this type-ahead (i.e. it could be a really slow response from a prior type-ahead) //2) This callback is the latest request from this type-ahead //To handle these cases //First check that the remote scripting context id stored on the type-ahead element //matches up with the context id of this callback. //If it doesn't match, then this is at the very least a stale request. //ContextIds look like mjtrs#, ex: mjtrs1 if (objTAConfig.expectedContextId && contextId_ && objTAConfig.expectedContextId !== contextId_) { ConsoleLogging.logMessage('TypeAheadPopup: expected to receive callback from ContextId=' + objTAConfig.expectedContextId + ', but instead received callback from ContextId=' + contextId_); return; } if (checkResponseForError(responseString_)) { var arrEscapedValues, tmpAreMoreItems; if (!trim(responseString_)) { // No items...process an empty array arrEscapedValues = []; } else { // The response should be prefixed by a "1;" or a "0;", // indicating whether or not there are more after what is listed... tmpAreMoreItems = responseString_.substring(0, 1) === '1'; responseString_ = responseString_.substring(2); arrEscapedValues = responseString_.split(','); } var tmpHasExtraColumnIndex = typeof processResultParams_.extraHTMLColumnIndex !== 'undefined' && processResultParams_.extraHTMLColumnIndex !== null, extraHTMLColumnIndex = tmpHasExtraColumnIndex ? processResultParams_.extraHTMLColumnIndex : -1, tmpHasIsInactiveColumnIndex = typeof processResultParams_.isInactiveColumnIndex !== 'undefined' && processResultParams_.isInactiveColumnIndex !== null, isInactiveColumnIndex = tmpHasIsInactiveColumnIndex ? processResultParams_.isInactiveColumnIndex : -1, tmpHasInactiveMeansCompleteColumnIndex = typeof processResultParams_.inactiveMeansCompleteColumnIndex !== 'undefined' && processResultParams_.inactiveMeansCompleteColumnIndex !== null, inactiveMeansCompleteColumnIndex = tmpHasInactiveMeansCompleteColumnIndex ? processResultParams_.inactiveMeansCompleteColumnIndex : -1; processTypeAheadSearchTextArray( arrEscapedValues, false, // Force fire type-ahead on change tmpAreMoreItems, // Are more items 0, // Id column index 1, // Name column index isInactiveColumnIndex, // Inactive column index extraHTMLColumnIndex, // Extra HTML Column Index inactiveMeansCompleteColumnIndex, // Index of value indicating whether Inactive items are simply "Complete". (processResultParams_.forceIgnoreCurrentTextForBold ? 1 : 0)); // forceIgnoreCurrentTextForBold_ } } //------------------------------------------------------------------------------ // // Given a child element of a row within the typeahead, walks up the dom to // find the row element and pull the "data-mjtTAIdx" attribute, which indicates // the index into the original data used to build the typeahead in the // "processTypeAheadSearchOfJSONArray()" function. // //------------------------------------------------------------------------------ function taGetItemIndexFromTypeAheadRowChildElem(taRowChildElement_) { var rc = null, objTrElem = checkAttribute(taRowChildElement_, "data-mjtTAIdx"); if (objTrElem) { rc = parseInt( objTrElem.getAttribute('data-mjtTAIdx'), 10); } return rc; } //------------------------------------------------------------------------------ // // params_: // { // extraHTMLPropertyName, // renderExtraOnTheLeftPropertyName, // arrObjects, // forceFireTypeAheadOnChange, // fnPostProcessTypeaheadDisplay // } // //------------------------------------------------------------------------------ function processTypeAheadSearchOfJSONArray(params_) { var objTypeAheadDiv = getObj('typeAheadDiv'), objTAConfig = objTypeAheadDiv.typeAheadConfigParams; if (!objTAConfig) { // The type-ahead has already been dismissed. return; } // Make sure that clicking on the typeahead scrollbar doesn't cause the typeahead // to be hidden and also make sure that once the typeahead loses focus, blur handling // is turned back on again. objTypeAheadDiv.onmousedown = mousedownOnTypeahead; objTypeAheadDiv.onblur = blurTypeahead; objTypeAheadDiv.ignoreBlur = false; objTypeAheadDiv.selectedRowIndex = -1; var theForm = getObj(objTAConfig.formId); if (!theForm) { // Not sure what may have gone wrong here. Don't bother continuing. ConsoleLogging.logBlockClose('No Form! looking for formId=' + objTAConfig.formId); return; } var objCurrText = theForm[objTAConfig.textFieldName], objCurrId = theForm[objTAConfig.idFieldName], originalIdValue = objCurrId ? objCurrId.value : null, isText = objCurrText.value.length > 0, tmpIsExactMatch = false, tmpMatchedOriginalIdValue = false, doAutoMatch = objTAConfig.autoMatchOnKeyInput, extraHTMLPropertyName = params_.extraHTMLPropertyName, strRenderExtraOnTheLeftPropertyName = params_.renderExtraOnTheLeftPropertyName, arrObjects = params_.arrObjects, forceFireTypeAheadOnChange_ = params_.forceFireTypeAheadOnChange; if (!(arrObjects && arrObjects.length)) { if(isText) { var strNoMatchFoundHtml = getNoMatchFoundHtml( objTAConfig, params_.additionalLastRowHtml, params_.originalPayload, params_.fnNoMessagePostfix); objTypeAheadDiv.style.display = 'none'; objTypeAheadDiv.style.zIndex = 501; objTypeAheadDiv.innerHTML = strNoMatchFoundHtml; setTimeout(doPositionPicker, 0); } else { mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals', null); hideTypeAheadDiv(); if (forceFireTypeAheadOnChange_) { fireTypeAheadOnChangeIfNecessary({ textField: objCurrText, useAsynchForOnChange: false }); } } } else { setInvalidTextClass( objCurrText, 0); // isInvalid_ var strColspanAttr = extraHTMLPropertyName ? ' colspan="2"' : '', strAdditionalClasses = (objTAConfig) ? (objTAConfig.typeAheadTableAdditionalClasses || '') : '', strHTML = '' + '', currTextRaw = objCurrText.value, currText = (params_.forceIgnoreCurrentTextForBold || objTAConfig.ignoreCurrentTextForBold) ? '' : currTextRaw, rowId = 0, i; objTypeAheadDiv.itemCount = arrObjects.length; var arrTypeaheadRowVals = []; mjtElemData(objTypeAheadDiv, 'arrTypeaheadRowVals', arrTypeaheadRowVals); if (params_.additionalFirstRowHtml) { strHTML += params_.additionalFirstRowHtml; } for (i = 0; i < arrObjects.length; ++i) { var objRow = arrObjects[i], // Include an attribute on each row of the typeahead that gives // the index into the original set of data used to build the // typeahead. strObjIndexDataAttr = ' data-mjtTAIdx="' + i + '"', strOnMouseDown = 'onmousedown="selectTypeAheadItem(' + rowId + ',' + 'true,' + // activeSelection_ 'true,' + // setFocus_ 'event)" ', strOnMouseUp = 'onmouseup="hideTypeAheadDiv();' + 'setFocusToTextField(' + '\'' + theForm.id + '\',' + '\'' + objCurrText.name + '\');"'; if (objRow.isSeparator) { // It's a separator! --objTypeAheadDiv.itemCount; strHTML += '' + '' + ''; } else { arrTypeaheadRowVals[rowId] = objRow; var strUnescapedId = objRow.id, name = objRow.name; if (objRow && !(strUnescapedId || name) && strUnescapedId !== '' && name !== '') { // Looks like the given rows are not objects, so just use // them directly as both the id and name... strUnescapedId = objRow; name = objRow; } if ((!tmpMatchedOriginalIdValue) && name && name.toLowerCase() === currTextRaw.toLowerCase()) { // If there is more than one product with the same name, we want to select the first item // with that name, unless the previously selected item is also an identical match, but is NOT // the FIRST such match. var tmpThisRowMatchesTheOriginalId = objCurrId && (strUnescapedId === originalIdValue); if (doAutoMatch && (tmpThisRowMatchesTheOriginalId || !tmpIsExactMatch)) { // Either this row is an exact match and its id matches the previously selected id, // or it is simply the first exact match. // // In the former case, we definitely want it to be the selected row (and definitely // won't want any other row to be selected). // // In the latter case, we want this to be the selected row unless there is a later // row that is also an exact match, and that is the same row as was previously selected. if (objCurrId) { objCurrId.value = strUnescapedId; } tmpMatchedOriginalIdValue = tmpMatchedOriginalIdValue || tmpThisRowMatchesTheOriginalId; tmpIsExactMatch = true; objTypeAheadDiv.selectedRowIndex = rowId; } } var isInactive = objRow.isInactive, inactiveMeansComplete = objRow.inactiveMeansComplete, strBolded = name.substring(0, currText.length), strPostBolded = name.substring(currText.length), strExistingSelectionText = objTAConfig.existingSelectionText, tmpMarkAsPriorSelection = strExistingSelectionText && name && (name.toLowerCase() === strExistingSelectionText.toLowerCase()), strAdditionalRowClass = tmpMarkAsPriorSelection ? ' typeAheadPriorSelection' : '', strRow = '', tmpRenderExtraOnTheLeft = strRenderExtraOnTheLeftPropertyName ? objRow[strRenderExtraOnTheLeftPropertyName] : false, strExtraHTML; strExtraHTML = extraHTMLPropertyName ? objRow[extraHTMLPropertyName] || '' : ''; if (extraHTMLPropertyName) { strExtraHTML = ''; } if (strExtraHTML && tmpRenderExtraOnTheLeft) { strRow += strExtraHTML; } strRow += ''; if (strExtraHTML && !tmpRenderExtraOnTheLeft) { strRow += strExtraHTML; } strRow += ''; strHTML += strRow; ++rowId; } } if (params_.areMoreItems) { var strMoreMsg = ' There are more items' + (currText ? ' starting with "' + htmlEncode(currText) + '"' : ''); strHTML += '' + '' + strMoreMsg + '' + ''; } if (objTAConfig) { if(params_.additionalLastRowHtml && objTAConfig.showAdditionalLastRow) { strHTML += params_.additionalLastRowHtml; } } strHTML += '' + '
' + (strExtraHTML ? strExtraHTML : '') + ''; if (isInactive) { strRow += ''; } strRow += '' + htmlEncode(strBolded) + '' + htmlEncode(strPostBolded); if (isInactive) { strRow += ' (' + (inactiveMeansComplete ? 'Complete' : 'Inactive') + ')' + ''; } strRow += '
'; objTypeAheadDiv.style.display = 'none'; objTypeAheadDiv.style.zIndex = 501; objTypeAheadDiv.innerHTML = strHTML; if (tmpIsExactMatch && doAutoMatch) { var objSelectedRow = getObj('typeAheadRow' + objTypeAheadDiv.selectedRowIndex); objSelectedRow.className = 'typeAheadSelectedRow'; objSelectedRow.setAttribute('mjtclass', 'typeAheadSelectedRow'); // We've found a match (and thus set the selected row and id value (if there is one) so // fire the onchange event (if there is one) fireTypeAheadOnChangeIfNecessary({ textField: objCurrText, useAsynchForOnChange: false }); } if (params_.fnPostProcessTypeaheadDisplay) { params_.fnPostProcessTypeaheadDisplay({ typeaheadDiv: objTypeAheadDiv, arrObjects: arrObjects }); } setTimeout(doPositionPicker, 0); } } //------------------------------------------------------------------------------ // // Processes callback data that has a JSON form. // //------------------------------------------------------------------------------ function rsCallbackGetTypeAheadSearchTextJSON(json_, optionsHolder_) { var objOriginalOptions = optionsHolder_.originalOptions, objOriginalPayload = optionsHolder_.originalPayload, tmpContextId = optionsHolder_.contextId, objTypeAheadDiv = getObj('typeAheadDiv'), objTAConfig = (objTypeAheadDiv || {}).typeAheadConfigParams, strArrayPropertyName = objOriginalOptions.arrayPropertyName ? objOriginalOptions.arrayPropertyName : 'array', arrObjects = json_[strArrayPropertyName], fnPreprocessArray = objOriginalOptions.fnPreprocessArray, fnPreprocessObject = objOriginalOptions.fnPreprocessObject, strSearchTerm = (objOriginalPayload || {}).term || '', tmpAreMoreItems = json_.isMore; if(!objTAConfig) { // The type-ahead has already been dismissed. return; } //At this point we know that a type-ahead is currently open, but we can't be sure that //1) This callback is for this type-ahead (i.e. it could be a really slow response from a prior type-ahead) //2) This callback is the latest request from this type-ahead //To handle these cases //First check that the remote scripting context id stored on the type-ahead element //matches up with the context id of this callback. //If it doesn't match, then this is at the very least a stale request. //ContextIds look like mjtrs#, ex: mjtrs1 if(objTAConfig.expectedContextId && tmpContextId && objTAConfig.expectedContextId !== tmpContextId) { ConsoleLogging.logMessage('TypeAheadPopup: expected to receive json-callback from ContextId=' + objTAConfig.expectedContextId + ', but instead received callback from ContextId=' + tmpContextId); return; } if(fnPreprocessArray) { fnPreprocessArray({ arrItems: arrObjects, searchTerm: strSearchTerm }); } if (fnPreprocessObject && arrObjects) { arrObjects.forEach( function (object_) { fnPreprocessObject(object_); }); } processTypeAheadSearchOfJSONArray( { extraHTMLPropertyName: objOriginalOptions.extraHTMLPropertyName, renderExtraOnTheLeftPropertyName: objOriginalOptions.renderExtraOnTheLeftPropertyName, forceIgnoreCurrentTextForBold: objOriginalOptions.forceIgnoreCurrentTextForBold, arrObjects: arrObjects, areMoreItems: tmpAreMoreItems, fnPostProcessTypeaheadDisplay: objOriginalOptions.fnPostProcessTypeaheadDisplay, additionalLastRowHtml: objOriginalOptions.additionalLastRowHtml, additionalFirstRowHtml: objOriginalOptions.additionalFirstRowHtml, originalPayload: optionsHolder_.originalPayload, fnNoMessagePostfix: objOriginalOptions.fnNoMessagePostfix }); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function updateTypeAheadBoxTimerFired() { var objTypeAheadDiv = getObj('typeAheadDiv'), configParams = objTypeAheadDiv.typeAheadConfigParams; if (configParams) { var theForm = getObj(configParams.formId); // Do a lookup using the current text... if (!theForm[configParams.textFieldName]) { // Debugging info! console.log( '\n' + '\n' + '!!!!\n' + 'Can not find text field for type-ahead.\n' + ' configParams.textFieldName="' + configParams.textFieldName + '"\n' + '!!!!'); } var currText = theForm[configParams.textFieldName].value.toLowerCase(), fnGetTypeAheadData; if(currText.length === 0 && !configParams.showTypeAheadWhenUnmatched) { processTypeAheadSearchTextArray( [], // Items (none) true, // Force handling of the type-ahead change. (yes, but doesn't really matter) false, // Are more items (no) 0, // Id column (doesn't really matter) 1); // Name column (doesn't really matter) } else { fnGetTypeAheadData = StringificationUtils.getOrResolveFunction({ functionNameOrReference: configParams.fnGetTypeAheadData, descriptionOfFunctionBeingResolved: 'configParams.fnGetTypeAheadData' }); if(fnGetTypeAheadData) { fnGetTypeAheadData( theForm, currText, configParams, configParams.getTypeAheadDataFnParameter); } } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function getSelectedTypeAheadRow(objTypeAheadDiv_) { var rc = -1; if (objTypeAheadDiv_) { if (typeof objTypeAheadDiv_.selectedRowIndex === 'undefined') { return objTypeAheadDiv_.selectedRowIndex; } } return rc; } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function handleTypeAheadTextBoxBlur(formId_, typeAheadConfigParams) { var objTypeAheadDiv = getObj('typeAheadDiv'); if (objTypeAheadDiv && !objTypeAheadDiv.ignoreBlur) { hideTypeAheadDiv(); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function fireAsynchHandleTypeAheadTextChange() { if (g_typeAheadTextChangeTimer) { clearTimeout(g_typeAheadTextChangeTimer); } var timeout = 300; // By default, wait 1/3 of a second before running off for data... if (getObj('typeAheadDiv').typeAheadConfigParams.forceImmediatePopup) { timeout = 0; } g_typeAheadTextChangeTimer = setTimeout(updateTypeAheadBoxTimerFired, timeout); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function handleTypeAheadTextBoxFocus(e_, formId_, typeAheadConfigParams_) { var objTypeAheadDiv = getObj('typeAheadDiv'); objTypeAheadDiv.ignoreBlur = false; if (!objTypeAheadDiv.ignoreFocus) { objTypeAheadDiv.typeAheadConfigParams = typeAheadConfigParams_; var theForm = getObj(typeAheadConfigParams_.formId), objIdField = theForm[typeAheadConfigParams_.idFieldName]; if (typeAheadConfigParams_.forceTypeAheadOnFocus || (typeAheadConfigParams_.showTypeAheadWhenUnmatched && !objIdField.value.length)) { fireAsynchHandleTypeAheadTextChange(); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function incrementTypeAheadSelectionIndex(moveDown_) { var objTypeAheadDiv = getObj('typeAheadDiv'), itemCount = objTypeAheadDiv.itemCount ? objTypeAheadDiv.itemCount : 0; removeTypeAheadSelectionOrHoverHighlight(false); if (typeof objTypeAheadDiv.selectedRowIndex === 'undefined') { // Nothing selected yet, just select the first one. selectTypeAheadItem(0, false); } else { var currSelIndex = parseInt(objTypeAheadDiv.selectedRowIndex, 10); if (moveDown_) { currSelIndex++; } else { --currSelIndex; } if (currSelIndex >= itemCount) { currSelIndex = itemCount - 1; } if (currSelIndex < 0) { currSelIndex = 0; } selectTypeAheadItem(currSelIndex, false); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function selectFirstLastInTypeahead(objTypeAheadDiv_, objTextField_, first_) { if (isTypeaheadDivShowing(objTypeAheadDiv_)) { var itemCount = objTypeAheadDiv_.itemCount ? objTypeAheadDiv_.itemCount : 0; if (itemCount) { selectTypeAheadItem( first_ ? 0 : itemCount - 1, // rowIndex_ false, // activeSelection false); // setFocus } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function pageTypeAheadIfNecessary(objTypeAheadDiv_, objTextField_, up_) { if (isTypeaheadDivShowing(objTypeAheadDiv_)) { var itemCount = objTypeAheadDiv_.itemCount ? objTypeAheadDiv_.itemCount : 0, tmpSelectedIndex = objTypeAheadDiv_.selectedRowIndex ? objTypeAheadDiv_.selectedRowIndex : 0, firstVisibleItem = 0, lastVisibleItem = itemCount - 1, topOfViewport = objTypeAheadDiv_.scrollTop, bottomOfViewport = topOfViewport + objTypeAheadDiv_.clientHeight - 1, i; for (i = 0; i < itemCount; ++i) { var objRow = getObj('typeAheadRow' + i); if (topOfViewport + objRow.offsetHeight / 2 >= objRow.offsetTop && topOfViewport + objRow.offsetHeight / 2 < objRow.offsetTop + objRow.offsetHeight) { // Found the first visible item... firstVisibleItem = i; } if (bottomOfViewport - objRow.offsetHeight / 2 >= objRow.offsetTop && bottomOfViewport - objRow.offsetHeight / 2 < objRow.offsetTop + objRow.offsetHeight) { // Found the last visible item... lastVisibleItem = i; } } var indexToSelect; if (up_ && tmpSelectedIndex > firstVisibleItem) { // Paging up, but we're not at the top of the viewport, // so simply select the top item in the viewport. indexToSelect = firstVisibleItem; } else if ((!up_) && tmpSelectedIndex < lastVisibleItem) { // Paging down, but we're not at the bottom of the viewport, // so simply select the bottom item in the viewport. indexToSelect = lastVisibleItem; } else { var itemsPerPage = lastVisibleItem - firstVisibleItem; if (itemsPerPage < 1) { itemsPerPage = 1; } if (up_) { // Paging to the top of the previous page... indexToSelect = firstVisibleItem - itemsPerPage; } else { // Paging to the bottom of the next page... indexToSelect = lastVisibleItem + itemsPerPage; } } if (indexToSelect >= itemCount) { indexToSelect = itemCount - 1; } if (indexToSelect < 0) { indexToSelect = 0; } selectTypeAheadItem( indexToSelect, false, // activeSelection false); // setFocus } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function clearForAndHandleTypeAheadChange(params_) { var objIdField = params_.idField, objTextField = params_.textField, tmpUseAsynchForOnChange = params_.useAsynchForOnChange; // Clear out the id pending selection... if (objIdField && objIdField.value.length) { objIdField.value = ''; fireTypeAheadOnChangeIfNecessary({ textField: objTextField, useAsynchForOnChange: tmpUseAsynchForOnChange }); } fireAsynchHandleTypeAheadTextChange(); } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function handleTypeAheadTextBoxOnInput(e_, formId_, typeAheadConfigParams_) { var objTypeAheadDiv = getObj('typeAheadDiv'); if (!objTypeAheadDiv.ignoreTextChange) { objTypeAheadDiv.typeAheadConfigParams = typeAheadConfigParams_; var theForm = getObj(typeAheadConfigParams_.formId), objTextField = theForm[typeAheadConfigParams_.textFieldName], objIdField = theForm[typeAheadConfigParams_.idFieldName]; if (checkIfTypeaheadDisplayValueChangedAndSetLastValue(objTextField)) { clearForAndHandleTypeAheadChange({ idField: objIdField, textField: objTextField, useAsynchForOnChange: false }); } } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ function handleTypeAheadTextBoxKeyDown(e_, formId_, typeAheadConfigParams_, handlingEnter_, skipHideDialogOnEnter_) { var objTypeAheadDiv = getObj('typeAheadDiv'); if (!objTypeAheadDiv.ignoreTextChange) { objTypeAheadDiv.typeAheadConfigParams = typeAheadConfigParams_; var theForm = getObj(typeAheadConfigParams_.formId), objTextField = theForm[typeAheadConfigParams_.textFieldName], objIdField = theForm[typeAheadConfigParams_.idFieldName]; switch (e_.keyCode) { case 38: // up incrementTypeAheadSelectionIndex(false); break; case 40: // down if (e_.altKey) { var strPickerActivation = objTextField.getAttribute('mjttypeaheadactivation'); if (strPickerActivation) { hideTypeAheadDiv(); eval(strPickerActivation); } } else { incrementTypeAheadSelectionIndex(true); } break; case 33: // pageup pageTypeAheadIfNecessary(objTypeAheadDiv, objTextField, true); return false; case 34: // pagedown pageTypeAheadIfNecessary(objTypeAheadDiv, objTextField, false); return false; case 27: // Escape if (hideTypeAheadDiv()) { // The dropdown was showing, so prevent the event from // propagating so that the only impact will be dismissal of // the typeahead. (e.g. if this is in a dialog, the dialog // won't also be dismissed.) cancelEvent(e_); return false; } break; case 13: if (handleExplicitTypeAheadSelection()) { return false; } else { if (!skipHideDialogOnEnter_) { hideTypeAheadDiv(); } if (!handlingEnter_) { // Not handling enter separately, so return false to prevent the beep on IE. return false; } } break; case 9: // Tab break; case 116: // F5 break; case 37: // Left break; case 39: // Right break; case 16: // Shift break; case 36: // Home if (e_.ctrlKey) { selectFirstLastInTypeahead(objTypeAheadDiv, objTextField, true); } break; case 35: // End if (e_.ctrlKey) { selectFirstLastInTypeahead(objTypeAheadDiv, objTextField, false); } break; case 17: // Ctrl break; case 18: // Alt break; default: if (e_.altKey) { break; } if (e_.ctrlKey && (65 === e_.keyCode || 82 === e_.keyCode)) { // Ctrl-A/Ctrl-R ... ignore these... break; } // Whatever key the user hit may invalidate the current selection so clear it now // (before we fetch new data) so that if the user hits 'enter' before the new data // arrives they aren't taken to any previously selected item which may not match // the last keys they entered. clearSelection(); clearForAndHandleTypeAheadChange({ idField: objIdField, textField: objTextField, //Trigger the onchange event async //Because it is coming from a text change //that has not yet completed. //If a synchronous event is fired, then the textbox //would still have stale text. useAsynchForOnChange: true }); break; } } } //------------------------------------------------------------------------------ // // Used as the "fnHandleExplicitSelection" parameter when you want to support // submitting a dialog on enter from a typeahead field, but you don't need to // take any sort of specific custom action when a typeahead option is explicitly // selected from the list by clicking or pressing enter. // // This function will simply dismiss the typeahead and try to set focus back to // the underlying text field. // // This is used to support the behaviour of hiding the typeahead dropdown if // is pressed while the dropdown is showing and an item is selected, // and of allowing the dialog to submit if the dropdown is NOT showing with an // item selected. (In order to actually see this behaviour, you need to also // set the "handlingEnter_" flag of the "handleTypeAheadTextBoxKeyDown()" // function. // //------------------------------------------------------------------------------ function typeaheadDivHidingExplicitTypeaheadSelectionHandlerV2( selectedRowIndex_, objSelectedRow_, formId_, typeAheadConfigParams_, explicitSelectionFnParameter_, arrTypeaheadRowVals_) { var theForm = getObj(formId_), objTextField = theForm ? theForm[typeAheadConfigParams_.textFieldName] : 0; hideTypeAheadDiv(); if (objTextField) { // Asynchronously set focus back to the text field... setTimeout( function () { setFocusIfFocusable(objTextField); }, 0); } } //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ var TypeAheadPopup = function () { var TYPEAHEAD_ITEM_TEXT_ALL = 'All', TYPEAHEAD_ITEM_ID_ALL = -1; //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- function pvSearchTermPermitsAllItem(searchTerm_) { var tmpSearchTermLen = searchTerm_.length, tmpAllLen = TYPEAHEAD_ITEM_TEXT_ALL.length, rc = 0; if (tmpSearchTermLen <= tmpAllLen) { // Don't even bother with a text comparison unless the length of the // search term is no longer than the text we're vetting. (If the // search term is longer than the value we're filtering, there's no // way it could be a match) if (!tmpSearchTermLen) { // Since there is no search term to filter values, the "All" // item is definitely not being filtered out. rc = 1; } else { rc = stringStartsWith( TYPEAHEAD_ITEM_TEXT_ALL, // string_ searchTerm_, // prefix_ 1); // ignoreCase_ } } return rc; } //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- function pbPrependAllItemToArray(params_) { var arrItems = params_.arrItems, strSearchTerm = params_.searchTerm; if (arrItems && pvSearchTermPermitsAllItem(strSearchTerm)) { var objAllItem = { id: TYPEAHEAD_ITEM_ID_ALL, name: TYPEAHEAD_ITEM_TEXT_ALL }; arrItems.unshift(objAllItem); } return arrItems; } //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- function pbBuildStringifiedTypeaheadInfo(params_) { var objStringifiableParams = params_.stringifiableParams, rcStringified = JSON.stringify(objStringifiableParams); return rcStringified; } //-------------------------------------------------------------------------- // TypeAheadPopup //-------------------------------------------------------------------------- return { prependAllItemToArray: pbPrependAllItemToArray, buildStringifiedTypeaheadInfo: pbBuildStringifiedTypeaheadInfo, PROTECTED: { } }; }();