/* Javascript for the identification page */ // https://www.site24x7.com/tools/javascript-validator.html /* GLOBAL CONSTANTS */ // Field indexes const StartX = 0; const ClassX = 1; const SubClassX = 2; const OrderX = 3; const FamilyX = 4; const SeriesX = 5; const TaxonomyFieldNames = ['', 'class_', 'sub_class', 'order_', 'family_']; const TaxonomyEnglishNames = ['', 'class', 'sub-class', 'order', 'family', 'component']; const HomeNodeCode = 'classes'; // More indexes const CompPassesFilterX = 1; // Strings const ANY_BRAND_STR = ' (any brand)'; // Defined so that the text editor doesn't complain about an unmatched parenthesis const OpenParen = '(' const CloseParen = ')' const OpenSquareBracket = '[' const CloseSquareBracket = ']' // Encoding decoding const EncChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~[]@!$()*,:'; // 76 characters //const EncChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~[]@!$()*,:;=?+&'; // 81 characters //const EncChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~'; // 66 characters //const EncChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; // 52 characters const EncNoOfChars = EncChars.length; // Cookies const CookieSettings = 'settings'; const CookieEditPermission = 'editPermisison'; const SetEditPermission = false; // Change to "true" if you want to install a cookie in the user's computer to let them edit the component data const CookieWireSizeUnits = 0; const CookieWidthUnits = 1; const CookiePitchTol = 2; const CookieEnabEditChkBox = 3; const CookieDefaultFilter = 4; const CookieDefaultList = 5; const CookieDefaultMainTab = 6; // Misc const AnyTypeStr = ', any type'; // Text at the end of an options group to select any item in the group const AnySpecifiedStr = 'Any specified'; // Text at the end of an options group to select any item in the group const FullAttrPostFix = 'Full'; // Each node has copies of its attributes in properties whose name ends with this postfix const ApplCodes = ['ind', 'env', 'prod', 'dev', 'wir']; // Codes for all applications in the Select by function page const AttrCodes = ['acc', 'topo', 'wire', 'term', 'ckt', 'struct', 'curr', 'hybr', 'vtg', 'spd', 'panl', 'mate']; // Codes for all attributes in the Select by function page const ManufListLength = 6; // Number of rows in the maunufacturer list const DbEditTtles = { 'add': 'Add a new component', 'edit': 'Edit this component', 'dup': 'Duplicate this comp.'}; const MaxOptionLen = 99; // Maximum number of characters const UnitsLabelWidth = 40; // Width of the label with the units at the end of a line in the filter const InvertChkBxWidth = 20; // Width of the invert checkbox before a select in the filter const WireSizeTol = 1.02; // Tolerance in the wire size values: 2 % const MaxDbRows = 100; // Maximum number of components from the database to improve responsiveness const MaxCompListItems = 100; // Maximum number of components shown in the componenst list before the "Show more" button appears const MaxAutoCompleteItems = 20; /// Maximum number of items in an autocomplete list const QuickPickComps = [ // List of most searched components // 1st item: 0 = node code, 1 = component name // 2nd item: 0 = component image, 1 = application image // 3rd item: node code or component name [0,0,'wire_ferrules'], [0,0,'lever_splices'], [0,1,'eurostyle_terminal_strips'], [0,1,'quick_sping_terminal_blocks'], [1,0,'Std pluggable terminal block 3.5 partitioned'], [1,0,'Std pluggable terminal block 3.5 solid'], [1,0,'Std pluggable terminal block 3.81'], [1,0,'Std pluggable terminal block 5'], [1,0,'Std pluggable terminal block 5.08'], [0,1,'board_in_crimp_plugs'], [0,1,'board_in_idt_plugs'], [0,1,'board_in_poke_in_headers'], [0,1,'ribbon_cable_sockets'], [0,1,'rast_2p5_card_edge_plugs'], [0,1,'ffc_fpc_sockets'], [0,0,'pcb_spring_leaves'], [0,0,'spring_leaf_hdrs'], [0,0,'pogo_pins'], [0,0,'pogo_pin_hdrs'], [0,0,'zebra_strips'], [0,1,'led_strip_splices_sockets'], [0,0,'shroudless_male_headers'], [0,0,'shroudless_fem_headers'], [0,0,'shroudless_plugs'], [0,0,'small_genderless_b2b_conn'], [0,0,'dual_beam_out_cont_mezz_conn'], [0,0,'dual_tongue_b2b_conn'], [0,0,'battery_blade_b2b_conn'], [0,0,'misc_prismatic_conn'], [0,1,'bump_idc_conn'], [0,1,'prismatic_latch_conn'], [0,1,'single_wall_conn'], [1,1,'VH'], [0,0,'pin_socket_conn'], [0,0,'round_partitioned_face_conn'], [0,1,'partitioned_micro_fit_conn'], [0,1,'partitioned_mini_fit_jr_conn'], [1,0,'SH'], [1,0,'Panelmate'], [1,0,'PicoBlade'], [1,0,'ZH + ZR'], [1,0,'PH + PHR'], [1,0,'Sherlock'], [1,0,'PHD'], [0,1,'parallel_mate_w2b_conn'], [1,0,'GH'], [0,1,'lvds_w2b_conn'], [0,1,'miniflex_conn'], [1,0,'SM'], [1,1,'Picoflex PF-50'], [0,0,'faston_boots'], [0,1,'rast_5_conn'], [0,0,'automotive_conn'], [1,0,'Automobile 2.8mm'], [0,0,'hot_pluggable_conn'], [1,0,'Deans micro'], [1,0,'Deans ultra'], [1,0,'EC5'], [1,0,'XT60'], [1,0,'HXT 4 mm'], [0,0,'quick_connect_disconnects'], [0,0,'bullet_disconnects'], [0,0,'banana_conn'], [0,0,'rc_bullets'], [0,0,'powerpole_conn'], [0,0,'phone_conn_2p5'], [0,0,'phone_conn_3p5'], [0,0,'phone_conn_6p35'], [0,0,'coax_barrel_power_conn'], [0,0,'cctv_power_barrel_conn'], [0,0,'phono_conn'], [0,0,'mini_rf_cable_to_board_conn'], [1,0,'FAKRA SMB'], [1,0,'SMA'], [1,0,'BNC'], [1,0,'TNC'], [1,0,'N-Type'], [1,0,'Belling-Lee'], [1,0,'F-Type'], [1,0,'UHF'], [1,0,'XLR'], [0,0,'mini_din_circ_conn'], [0,0,'mini_din_deriv_circ_conn'], [0,0,'power_din_circ_conn'], [0,0,'std_din_circ_conn'], [0,0,'mil_spec_circular_conn'], [0,0,'iec_circular_conn'], [0,0,'aviation_gx_circular_conn'], [0,0,'xs_circular_conn'], [0,0,'sp_type_circular_conn'], [0,0,'slim_plastic_circular_conn'], [0,0,'slim_metal_circular_conn'], [0,0,'x_plastic_circular_con'], [1,0,'CPC series 1 - 11 (shr male)'], [0,0,'led_lighting_circ_conn'], [0,0,'ebike_circ_conn'], [0,0,'d_sub_conn'], [0,0,'micro_ribbon_conn'], [0,0,'modular_conn'], [0,0,'usb_conn'], [0,0,'firewire_conn'], [0,0,'esata_conn'], [0,0,'video_conn'], [0,0,'displayport_conn'], [0,0,'dvi_conn'], [0,0,'hdmi_conn'], [0,0,'cee7_ac_power_conn'], [0,0,'nema_ac_power_conn'], [0,0,'misc_country_specific_ac_power_conn'], [0,0,'iec_60320_ac_power_conn'], [0,0,'nema_twist_lock_ac_power_conn'], [0,0,'iec_60309_pin_and_sleeve_conn'], [0,0,'dc_couplers'], [1,0,'SB120'], [1,0,'SAE J928'], [0,0,'glass_fiber_optic_conn'], ]; /* GLOBAL VARIABLES */ /* stage activeNodeCode activeNodePath ________ ______________ ________________________________________________ 0, StartX: classes classes/ 1, ClassX: terminals_ classes/terminals_/ 2, SubClassX: wire_terminals classes/terminals_/wire_terminals/ 3, OrderX: tongue_crimp_terminals classes/terminals_/wire_terminals/tongue_crimp_terminals 4, FamilyX: ring_terminals classes/terminals_/wire_terminals/tongue_crimp_terminals/ring_terminals stage: 0 1 2 3 4 * = defined, - = not defined x = may be defined name: activeNodeObj.nm * * * * * description: activeNodeObj.ds * * * * * traits text: activeNodeObj.tq * * * * - subNodeDict: activeNodeObj.ls * * * * - keywords: activeNodeObj.kw x x x x * */ // Created once and then never changed var nodeAncestorTable = {classes: []}; // <<< TO BE ELIMINATED, REPLACED BY nodeObj.ancestorList For each node in the tree, a list of its ancestors. E.g., aar_s_512_circ_conn: ['classes', 'round_conn', 'circular_conn', 'standard_metal_circular_conn'] var nodesDict = {}; // Dictionary with the object for each node var keywordsDict = {}; // Dictionary of keywords, with a list of nodes that include that keyword; used to create a list of keywords and links, but only if the user goes to browse it; this speeds up init considerably // Active class var activeClassCode = ''; // Code the class of the presently selected node // Active node var activeNodeCode = ''; // Code for the presently selected node var activeNodeObj = null; var activeNodePath = ''; // Path to the folder for the presently selected node var atFamily = false; // Set if the presently selected node is at the family level var atHome = true; // Set if at the classes level // Active component var activeComp = ''; // Name of the selected component; clear if no component is selected // Component database var allCompDataDict = {}; // Dictionary with one item per component var compDbFieldDict = {}; // Dictionary with one item per column in the component database var fltrRngFieldList = []; // List of the names of the columns in the filter ranges database var componentCopyList = ''; // List of components, to be copied to the clipboard var editCompMode = ''; // If editing the database, 'add', 'edit', or 'dup' var showAllDbEditControls = false; // Set when the user wants to show all the database edit controls // Brand database var brandDict = {}; // Dictionary with one item per brand (a manufacturer may have multiple brands) var selectedBrand = ''; // Book pages var bookPagesNodePath = ''; // Folder with the image of the book pages for this node (may be an ancestor node) // Navigation var urlFilterParams = ''; // Initial URL for the filter controls; saved because we can't use at init; we can only use it after the script is loaded var initKeyword = ''; // Initial URL for the search keyword var backNodesStack = []; // LIFO stack of visited nodes (or selected component family) that are revisited when clicking the "back' button var fwdNodesStack = []; // LIFO stack of visited nodes (or selected component family) that are revisited when clicking the "forward' button // Misc var openModalDialog; // Handle to an open modal dialog, so that the document can clos it when the user click outside of it var testShowAllFilters = false; // During testing, shows all the filter and datadase edit controls, even those that are not used in a given database var compListHTML = ''; // Full list of components, shown only if the user clicks the "show more components" button var fltrCompList = []; // Full list of components that pass the filter var oldestSelTopoBtn; // In the quick-filter pane, the topology button that was clicked first var newestSelTopoBtn; // "" that was clicked last var lastFltrSelect; // ID of the last filter select that the user used // ************ UTILITIES ************ var zz =console.log; // I'm tired of typing "console.log" over and over during testing var eval_redef = eval; // Redefined to avoid complaints from the javascript validator // *** Get the id of the selected radio button function getRadioBtnId( radioBtnsName) { var checkedRadioBtnId = ''; var btnList = document.getElementsByName(radioBtnsName); for (var btnNo=0; btnNo 1) { // This group has at least 2 items: add the "xxx, any type" option var optTitle = groupName + AnyTypeStr; addOption(optgroup, optTitle, optTitle, selectedVal); } for (var optNo in optValList) { addOption(optgroup, optValList[optNo], optTxtList[optNo], selectedVal); } theSelect.appendChild(optgroup); } // *** Add an option to a select function addOption( theSelect, optionValue, optionText, selectedVal) { var opt = document.createElement('option'); opt.value = optionValue; if ((selectedVal != '') && (optionValue == selectedVal)) { opt.setAttribute('selected', true); } if (typeof optionText == 'string') { optionText = optionText.substr(0,MaxOptionLen); } opt.innerHTML = optionText; theSelect.appendChild(opt); } // *** Add all options to a select function addAllOptions( theSelect, valueList, selectedVal, measUnits, fieldName) { // If the first element is empty, drop it var optionText = '', optionValue = ''; if ((valueList.length == 2) && (valueList[0] == 0) && (valueList[1] == 1)) { // Boolean addOption(theSelect, 0, 'No', selectedVal); // Add this option addOption(theSelect, 1, 'Yes', selectedVal); // Add this option } else if (isNumber(valueList[0])) { // Numeric fields for (optionValue of valueList) { // Prepare the text to be shown optionText = optionValue; // Assume the user prefers mm if (measUnits != '') { if (measUnits == 'inch') { optionText = (optionValue / 25.4).toFixed(2); } else { optionText = optionValue.toFixed(2); } } // Add this option addOption(theSelect, optionValue, optionText, selectedVal); // Add this option } } else if (valueList[0].includes(':')) { // This list has groups var groupName = ''; var optValList = []; var optTxtList = []; // If the desired selection is "xxx, any type", but there is only one item in that xxx group, then the "xxx, any type" is not in the list // Instead, we need to select that one and only item in the group // Start by finding the name of that group var anyTypeLoc = selectedVal.indexOf(', any type'); for (optionValue of valueList) { var optionParts = optionValue.split(':'); var newGroupName = optionParts[0]; if (newGroupName != groupName) { // Complete any previous group if (groupName != '') { addOptGroup(theSelect, groupName, optValList,optTxtList, selectedVal); } // Start a new group groupName = newGroupName; optValList = []; optTxtList = []; } // Add this option optValList.push(optionValue); var optTxt = optionValue.slice(groupName.length +1); /* if ((optTxt != '') && (typeof optTxt == 'string')) { if (typeof optTxt[0] == 'string') { optTxt = optTxt[0].toUpperCase() + optTxt.slice(1); // Capitalize the first letter } }*/ optTxtList.push(optTxt); } // Add the last group addOptGroup(theSelect, groupName, optValList, optTxtList, selectedVal); } else { // Text fields, no headers for (optionValue of valueList) { if (optionValue != '') { optionText = optionValue; // if (typeof optionValue == 'string') {optionText = optionValue[0].toUpperCase() + optionValue.slice(1); } // Capitalize the first letter addOption(theSelect, optionValue, optionText, selectedVal); // Add this option } } } } // *** Get the path to the folder for a node function getPathForNode( nodeCode){ var nodePath = ''; var nodeAncestorList = nodeAncestorTable[nodeCode]; if (nodeAncestorList) { for (var levelNo = 0; levelNo < nodeAncestorList.length; levelNo++) { nodePath += nodeAncestorList[levelNo] + '/'; } nodePath += nodeCode + '/'; } else { console.log('*** ',nodeCode, ' does not exist') } return nodePath; } // *** Show an image function showImage( imgPath, imgType, imageBox) { var imgSrc = 'img/dot.png'; // Default image if (imgPath !== '') { imgSrc = imgPath + imgType + '.jpg'; } document.getElementById(imageBox).src = imgSrc; } // *** Get the code for the parent of the given node, blank for the root node function getParentNodeCode( nodeCode){ var thisNodeAncestors = nodeAncestorTable[nodeCode]; return thisNodeAncestors[thisNodeAncestors.length -1]; } // *** LZW-compress a string /* function lzw_encode(s) { var dict = {}; var data = (s + "").split(""); var out = []; var currChar; var phrase = data[0]; var code = 256; for (var i=1; i 1 ? dict[phrase] : phrase.charCodeAt(0)); dict[phrase + currChar] = code; code++; phrase=currChar; } } out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0)); for (var i=0; i 0) { // AWG wireSizeStr = awg + ' AWG'; } else { // Ought wireSizeStr = (1-awg) + '/0'; } } else { // MCM var mcm = Math.round(2 * wireSize_mm2); wireSizeStr = mcm + ' MCM'; } return wireSizeStr; } // *** Format a string from the database into a nice list with commas and an "and" at the end function formatListNicely( listStr) { if (typeof listStr == 'string') { var theList = listStr.split(','); if (theList) { if (theList.length == 2) { listStr = theList[0] + ' and ' + theList[1]; } else if (theList.length > 2) { for (var i = 0; i < theList.length-1; i++) { listStr += theList[i] + ', '; } listStr += ' and ' + theList[theList.length-1]; } } } return listStr; } // *** Close any previously opened modal dialog and remember a new modal dialog (if any) so we can close it later // Called when the user opens a new modal dialog, makes a selection in a list, hits the escape key, or clicks outside the modal box function closePreviousModalDialog( newOpenModalDialog) { // Handle to the newly opened modal dialog, or null if none // Close the previously open dialog (if any) if (openModalDialog) { openModalDialog.parentNode.removeChild(openModalDialog); } // Save a handle to the new dialog (if any), so we can close it later openModalDialog = newOpenModalDialog; } // *** Set a cookie in the user's computer function setCookie(cookieName, cookieValue, expDays) { var d = new Date(); d.setTime(d.getTime() + (expDays * 86400000)); var expires = 'expires='+d.toUTCString(); document.cookie = cookieName + '=' + cookieValue + '; ' + expires; } // *** Get a cookie from the user's computer function getCookie(cookieName) { var cookieValue = ''; var cookieList = document.cookie.split(';'); // Returns all the cookies from this domain for(var cookieNo in cookieList) { var thisCookie = cookieList[cookieNo].trim(); var thisCookieParts = thisCookie.split('='); if (thisCookieParts[0] == cookieName) { cookieValue = thisCookieParts[1]; break; } } return cookieValue; } // *** Show a component description function showCompDescr( descrTxt) { generalDescrTxtBox.style.display = 'none'; compDescrTxtBox.style.display = 'block'; compDescrTxtBox.innerHTML = descrTxt; } // *** Show a general description function showGeneralDescr( descrTxt) { compDescrTxtBox.style.display = 'none'; generalDescrTxtBox.style.display = 'block'; generalDescrTxtBox.innerHTML = descrTxt; } // ************ UTILITIES USED BY NODE SCRIPTS ************ // *** Show help for the filter box function showFilterHelpImg( helpImgName, helpTxt) { console.log('showFilterHelpImg ' + helpImgName); if (helpImgName == '') { prodImgBox.src = applImgBox.src = '/helpimg/none.jpg'; } else { var imgSrc = '/helpimg/' + helpImgName + '1.jpg'; prodImgBox.src = imgSrc; imgSrc = '/helpimg/' + helpImgName + '2.jpg'; applImgBox.src = imgSrc; } if (helpTxt) { showGeneralDescr(helpTxt); } } // *** Get the value of the selected radio button function lookUpInDict( keyVal,theDict){ // The code and the dictionary with the translations var itemStr = ''; if (keyVal in theDict) { itemStr = theDict[keyVal]; } return itemStr; } // *** Return a string with all the values for each of the letters in a code function lookUpAllCodesInDict( codeStr,theDict, separStr){ // The code, the dictionary with the translations, and the separator string var itemsStr = ''; for (var ltrNo = 0; ltrNo < codeStr.length; ltrNo++) { var thisCode = codeStr[ltrNo]; if (thisCode in theDict) { if (itemsStr != ''){ itemsStr += separStr; } itemsStr += theDict[thisCode]; } } return itemsStr; } // *** Flag the unused Class Codes in a Class Code dictionary // Called by node scripts function flagUnusedClassCodes( classCodeList, classCodeDict){ for (var className in classCodeDict) { if (classCodeDict.hasOwnProperty(className)) { if (!classCodeList.includes(className)) { classCodeDict[className] = false; } } } } // ************ NODE FUNCTIONS ************ // *** Change node and component function goToNodeAndComp( nodeCode, compName) { console.log('goToNodeAndComp ', nodeCode, compName, arguments.callee.caller.name); activeComp = compName; var doStackNode = false; // Don't push this node into the stack var doUpdtURL = false; // Don't update the URL goToNode(nodeCode, doStackNode, doUpdtURL); } // *** Go to the Navigate mode and the specified node function goToNodeAndNavigMode( nodeCode) { // Go to the Navigate mode mainTab_N.checked = true; // Go to the specified node var doStackNode = true; // Push this node into the stack var doUpdtURL = true; // Ppdate the URL goToNode(nodeCode, doStackNode, doUpdtURL); } // *** Go to the specified node function goToNode( nodeCode, doStackNode, doUpdtURL) { // If coming from regular user navigation, doStackNode is undefined. If the user clicked the "back" button, it's true. Otherwise it's false console.log('goToNode ', nodeCode, doStackNode, doUpdtURL, arguments.callee.caller.name); // Except for the back button, stack the previous node or component, so we can come back to it if (typeof doStackNode == "undefined") { doStackNode = true; } var pushItem = activeNodeCode; if (atFamily && (activeComp != '')) { // We're exiting the family level with a selected component pushItem = [activeNodeCode, activeComp]; // Get ready to stack a list with this family node code and the name of the component activeComp = ''; // Clear the active component } if (doStackNode) { backNodesStack.push(pushItem); goBackBtn.src = 'img/goback.gif'; // Enable the navigation button } if (nodeCode != activeNodeCode) { // The node changed // Change the node and store global info about it activeNodeCode = nodeCode; activeNodeObj = nodesDict[activeNodeCode]; atFamily = (activeNodeObj.ancestorList.length == FamilyX); atHome = (activeNodeCode == HomeNodeCode); if (!atFamily) { // If this node has a single choice, jump directly to that choice var subNodeDict = activeNodeObj.ls; if (subNodeDict) { var subNodeList = Object.keys(subNodeDict); if (subNodeList.length == 1) { // Change the node and store global info about it activeNodeCode = subNodeList[0]; activeNodeObj = nodesDict[activeNodeCode]; atFamily = (activeNodeObj.ancestorList.length == FamilyX); } } // If there is an active component, that's wrong: clear it activeComp = ''; } // Finish storing global info about this node activeNodePath = getPathForNode(activeNodeCode); // If we we don't yet have the components for this node, request them from the database // After we receive them, process them asynchronously if (activeNodeObj.compLoaded) { // We have the components for this node // Flag components for other nodes, so we won't look at them when listing components flagCompForOtherNodes(); } else if (Object.keys(compDbFieldDict).length > 0) { // We already reeceived the field data from the components database // Request the components data from the components database requestCompData(); } // Save the code of this class activeClassCode = (activeNodeObj.ancestorList.length > ClassX)? activeNodeObj.ancestorList[ClassX] : activeNodeCode; // Blank for home, the class code otherwise // If we already have the field data for the filter ranges, request the filter ranges we need from the database // After we receive them, process them asynchronously // If we we don't yet have the filter ranges for this node and its subnodes, request them from the database // For this node if (activeNodeObj.fltrRngs) { // Load the filter controls with the filter ranges for the active node setUpFilter(); } else if (fltrRngFieldList.length > 0) { // We already have the field data for the filter ranges database // Request the filter ranges for this node and its subnodes from the database requestFltrRngData(); } // Show or hide the "identified" box //identifiedBox.style.display = atFamily? 'block' : 'none'; // "See also" list var showSeeAlso = false; var seeAlsoHTML = ''; var seeAlsoList = activeNodeObj.sa; if (seeAlsoList) { showSeeAlso = true; for (var itemNo = 0; itemNo < seeAlsoList.length; itemNo++) { var seeAlsoItem = seeAlsoList[itemNo]; var itemDef = eval_redef(seeAlsoItem); seeAlsoHTML += '
' + itemDef.nm + '
'; } } // Show or hide the See Also box seeAlsoListBox.innerHTML = seeAlsoHTML; seeAlsoBox.style.display = showSeeAlso? 'block' : 'none'; // See what this node uses for script and/or database // Parent This node // (any) 'none' This node has neither a script nor a database. Don't look at the ancestors // (any) 'script' This node has just a script, which we can use. Don't look at the ancestors // (any) 'db' This node has just a database, which we can use. Don't look at the ancestors. Do load the script, because it has the code to show the description // (any) 'both' This node has a script and database, which we can use. Don't look at the ancestors // '' '' This node has nothing and its parent doesn't either. Look at the parent' parent // 'none' '' This node has nothing and the parent has neither a script nor a database. Stop looking. This should never happen. // 'script' '' This node has nothing and the parent has just a script, which we can use. Stop looking // 'db' '' This node has nothing and the parent has just a database, which we can use. Stop looking activeNodeObj = nodesDict[activeNodeCode]; // Node with the image of book pages about this node bookPagesNodePath = ''; // Assume that there are no book pages about this node if (activeNodeObj.b) { // This node has the image of book pages about it bookPagesNodePath = activeNodePath; } else { // This node does not have the image of book pages about it // Look for it in ancestor nodes for (var ancestorNo = activeNodeObj.ancestorList.length -1; ancestorNo >= 0; ancestorNo--) { // Determine if this ancestor has the image of book pages about it var ancestorNodeCode = activeNodeObj.ancestorList[ancestorNo]; var ancestorNodeDef = eval_redef(ancestorNodeCode); if (ancestorNodeDef.b) { // This ancestor has the image of book pages bookPagesNodePath = getPathForNode(ancestorNodeCode); break; // Stop looking } } } // Show the info for this node showNodeInfo(activeNodeCode, -1); // -1 = show info on the present node // Close the database edit box (if open) if (filterColTitleSet.style.display == 'none') { editCompClose(); } } // Update the navigation tree and the selection tabs // Do it here, after activeNodeCode has been set for sure, // but before we disable any selectors should the filter be on (done by showCompList) updtNavigTree(); updtMainTabs(); // If an active component is selected, show it if (activeComp != '') { // There is an active component // Show it showActiveComp(); } else { // There is not active component // Show the component list if (activeNodeObj.compLoaded) { showCompList(); } // Close the edit window (if open) if (dbEditColTitleSet.style.display == 'block') { editCompClose(false);} // Show the filter filterColTitleSet.style.display = 'block'; // Show or hide the filter showHideFilter(); } // Hide the picture upload and database modify buttons compImgUploadDiv.style.display = 'none'; applImgUploadDiv.style.display = 'none'; dbModifyBtns.style.display = 'none'; dbModifyDisabBtns.style.display = 'inline'; // If a family and there is a database, show the add button var showDbAddBtn = (!atHome) && atFamily && (activeComp == ''); // Show or hide the Add component button addSeriesBtn.style.display = showDbAddBtn? 'inline' : 'none'; addSeriesDisabBtn.style.display = showDbAddBtn? 'none' : 'inline'; listBtns.style.visibility = 'visible'; // List buttons // Except for init, update the URL if (typeof doUpdtURL == "undefined") { doUpdtURL = true; } if (doUpdtURL) { updateURL(); } } // goToNode // *** Recursive function to flag that we received database records for this node and all its children function flagDbLoaded( nodeObj) { nodeObj.compLoaded = true; if (nodeObj.ls) { // If not a family node // Recursively do each of this node's descendants for (var childNodeCode of Object.keys(nodeObj.ls)) { flagDbLoaded(nodesDict[childNodeCode]); } } } // *** Get the field name from the ID or the filter row function getFieldNameFromRow( filterRow) { var fieldName = filterRow.id.slice(0,filterRow.id.indexOf('Row')); var classSpecifRow = filterRow.className.includes('classSpecRow'); if (classSpecifRow) { // Class specific row // Extract the field name and the node code var fieldNamePos = fieldName.indexOf('class_'); var fieldNameNode = fieldName.slice(0,fieldNamePos); fieldName = fieldName.slice(fieldNamePos); // If this row is for some other node, clear the field name if (fieldNameNode != activeClassCode) { fieldName= ''; } } return fieldName; } // *** Load the filter controls with the filter ranges for the active node function setUpFilter( ) { console.log('setUpFilter',arguments.callee.caller.name); // Length units var lengthUnitsSpans = document.getElementsByClassName('lengthUnits'); for (var lengthUnitsSpan of lengthUnitsSpans) { lengthUnitsSpan.innerHTML = widthUnits.value; } // Update the filter selectors based on the data in the filter ranges table in the database var fltrRngs = activeNodeObj.fltrRngs; var filterRows = document.getElementsByClassName('filterRow'); for (var filterRow of filterRows) { // <<<65 ms >>> Could reduce by creating options as text and doing innerHTML // Assume that this parameter is not shown var showParam = false; var classSpecifSel = false; // Get this field's name from the ID of the row in the filter table var fieldName = getFieldNameFromRow(filterRow); if (fieldName == 'comp_name') { // Show the row to filter the component name showParam = true; } else if ((fieldName != '') && (compDbFieldDict[fieldName]) && (fltrRngFieldList.includes(fieldName))) { // Get the range of values for this selector var thisFltrRng = fltrRngs[fieldName]; if (thisFltrRng) { var valueList = thisFltrRng.valueList; var hasBlank = thisFltrRng.hasBlank; var has2PlusValues = thisFltrRng.has2PlusValues; if (has2PlusValues) { // Prepare the controls showParam = true; // Standard select var filterSel = null; var selectorCell = filterRow.cells[1]; // The second cell has the selector var tdElements = selectorCell.childNodes; for (var dtElem of tdElements) { // Skip the text nodes that occur if the HTML has anything between the "TD" tag and the "SELECT" tag if (dtElem.nodeName == 'SELECT') { filterSel = dtElem; break; } } if (filterSel) { // This filter uses a select var minValue = ''; // Clear all options var prevSelection = filterSel.value; // Before we clear the options in this select, save the present selection, so we can tray to restore it filterSel.innerHTML = ''; // Add options to this select addOption(filterSel, '', 'Select...', ''); // No filtering // Fields with a list of values showParam = true; if (fieldName == 'wire_size_mm2_min') { // Wire size minValue = valueList[0] / WireSizeTol; // Reduce the minimum value a bit to avoid blocking a value that is really close maxValue = valueList[1] * WireSizeTol; // Increase the maximum value a bit to avoid blocking a value that is really close // Get the hidden select that lists all the wire sizes for the selected units (AWG or mm2) wireSizeUnitsSpan.innerHTML = wireSizeUnitsSel.value; var wireTemplateSel = document.getElementById('wireSize' + wireSizeUnitsSel.value + 'Sel'); // For each opion in the hidden template, if its value is within the range, create an option in the visible filter select for (var anOption of wireTemplateSel.options) { if ((anOption.value >= minValue) && (anOption.value <= maxValue)) { addOption(filterSel, anOption.value, anOption.text, activeNodeObj.fltrRngs); } } } else if (fieldName == 'manuf_name') { // Manufacturer list var manufList = []; var sameManuf = false; for (var manufNo = 0; manufNo < valueList.length; manufNo++) { // If multiple brands from the same manufacturer, add an item for all of them var thisManufName = valueList[manufNo]; var thisManufName1stWord = thisManufName.split(' ')[0]; var nextManufName1stWord = ''; if (manufNo < (valueList.length -1)) { nextManufName1stWord = valueList[manufNo +1].split(' ')[0]; } if (thisManufName1stWord == nextManufName1stWord) { if (!sameManuf) { manufList.push(thisManufName1stWord + ANY_BRAND_STR); } sameManuf = true; } else { sameManuf = false; } // Add an item for this manufacturer manufList.push(thisManufName); } addAllOptions(filterSel,manufList,prevSelection, '',fieldName); } else { // Regular select widthUnits.value var measUnits = ''; if (filterSel.className.includes('lengthSel')) { measUnits = widthUnits.value; } addAllOptions(filterSel,valueList,prevSelection, measUnits,fieldName); if (hasBlank) { addOption(filterSel, 'AnySpec', AnySpecifiedStr, ''); // Filter out all components whose value is defined } } // If unable to select the previous selection, try the next best guess if ((filterSel.selectedIndex == 0) && (prevSelection != '') ) { //zz('***',prevSelection,filterSel.options) } // If home, clear class specific selectors var classSpecifRow = filterRow.className.includes('classSpecRow'); if (atHome && classSpecifRow) { filterSel.selectedIndex = 0; } } } } } // During testing, show all filters, regardless if (testShowAllFilters) showParam = true; // Display or hide this filter row filterRow.style.display = showParam? 'table-row' : 'none'; } // Show only the filter selections with active selectors showSectWithActvSelectors(); // If we just started, and the URL specified filter settings, preset the filter controls if (urlFilterParams != '') { // We have filter presets // Turn on the filter homeFltrOffBox.style.display = 'none'; filtersDbEditBox.style.display = 'block'; closeFiltersBtn.style.display = 'block'; // The filter state is encoded into one to three parts separated by semicolons // Part 1: encodes the state of any selectors in the first half that are in use // Part 3: encodes the state of any selectors in the second half that are in use // Part 2: string in the "Comp name:" filter text box // For example: // 'ABCD': only the attribute selectors are used // ';EFG': only the component name filter is used // 'ABCD;EFG': the attribute selectors and the component name filter are used // ';;HIJK': only the application selectors are used // 'ABCD;EFG;HIJK': all are used // A filter section is open if any of its filters are used, otherwise it's closed var fltrCode1 = '', fltrCode2 = '', compNameStr = '', manufNameStr = ''; var urlFilterParts = urlFilterParams.split(';'); if (urlFilterParts.length > 0) { fltrCode1 = urlFilterParts[0];} if (urlFilterParts.length > 1) { fltrCode2 = urlFilterParts[1];} if (urlFilterParts.length > 2) { compNameStr = urlFilterParts[2];} if (urlFilterParts.length > 3) { manufNameStr = urlFilterParts[3];} // Flag that we have preset the filter, so we don't do it again urlFilterParams = ''; // See the notes in updateURL var filterSelects = filtersDbEditBox.getElementsByClassName('filterSel'); var filterInvertBoxes = filtersDbEditBox.getElementsByClassName('invSelChkBox'); // Preselect the filter selects // Open any filter sections that have a used filter // Decode each part of the URL for the filter selectors decodeFilterSel(filterSelects,filterInvertBoxes,0,fltrCode1); // First part decodeFilterSel(filterSelects,filterInvertBoxes,EncNoOfChars,fltrCode2); // Second part, after the semicolon, for selects past number 76 // Open any filter sections that have a used filter var filterSectionCheckBoxes = filtersDbEditBox.getElementsByClassName('trianglebtn'); var filterSections = document.getElementsByClassName('filterSection'); var clearSectFilterBtns = document.getElementsByClassName('clearSectFilterIcon'); for (var sectionNo = 0; sectionNo < filterSections.length; sectionNo++) { var filterSection = filterSections[sectionNo]; var sectionSelects = filterSection.getElementsByClassName('filterSel'); for (var sectionSelect of sectionSelects) { if (sectionSelect.selectedIndex > 0) { filterSectionCheckBoxes[sectionNo].checked = true; clearSectFilterBtns[sectionNo].style.visibility = 'visible'; break; } } } // Prefill the component name text field and the manufacturer name select if ((compNameStr != '') || (manufNameStr != '')) { filterCompNameTxtFld.value = compNameStr; manufFiltrSel.value = manufNameStr; // This overrides the selection based on part 0 of the filter code, which may have been created before more manufacturers were added to the database filterChkBox1.checked = true; compManufFiltrClrBtn.style.visibility = 'visible'; clearAttrFilterBtn.style.visibility = 'visible'; } } // Update the component list if (activeComp == '') { // There is no active component // Update the list of components based on the present filter parameters showCompList(); } else { // There is an active component // Show it showActiveComp(); } } // setUpFilter // *** Decode a part of the URL for the filter selectors function decodeFilterSel( filterSelects, filterInvertBoxes, selectNoOfst, fltrCode) { while (fltrCode != '') { // Get the select number from the first character var selectNo = selectNoOfst + Number(EncChars.indexOf(fltrCode[0])); // Get the selected index and whether the selection is inverted from the next 1 or 2 characters var selectedIndexCode = Number(EncChars.indexOf(fltrCode[1])); var twoChar = selectedIndexCode % 2; // If the code is odd (the LSb is set), it means that we're using two characters if (twoChar) { // The 2nd character is the least significant and the 3rd is the most significant selectedIndexCode += Number(EncChars.indexOf(fltrCode[2])) * EncNoOfChars; } // The 2nd least significant bit indicates whether the selection is inverted filterInvertBoxes[selectNo].checked = selectedIndexCode & 2; // Logic AND filterSelects[selectNo].selectedIndex = Math.floor(selectedIndexCode / 4) + 1; // Drop these two bits to get just the selected index // Drop these characters from the string fltrCode = fltrCode.substr(twoChar? 3 : 2); // Show the attribute clear filter button if (filterSelects[selectNo].className.includes('applFilterSel')) { clearApplFilterBtn.style.visibility = 'visible'; } else { clearAttrFilterBtn.style.visibility = 'visible'; } } } // *** Receive and process filter ranges from the database // Extract each node's filter ranges and save them in the node object, // and load the filter controls with the filter ranges for the active node function fltrRngDataReceived( responseTxt) { console.log('fltrRngDataReceived'); // Convert the response text to an object: a list of lists var fltrRngLists = JSON.parse(responseTxt); // if (fltrRngLists.length == 0) { console.log('*** no filter ranges data ***'); } else { // Extract and save the filter range for each node for (var fltrRngList of fltrRngLists) { // The first item is the node code (a unique key for the record) var nodeCode = fltrRngList[0]; // Create a dictionary for the filter ranges for this node, fill it with the data for each field var allFltrRngs = {}; var minWireSize = 0; var rawValue; for (var fieldNo = 1; fieldNo < fltrRngFieldList.length; fieldNo++) { // Skip the node code var fieldName = fltrRngFieldList[fieldNo]; var hasBlank = false; var fieldData = fltrRngList[fieldNo]; if (fieldData) { var valueList = []; if (fieldData == '') { // Blank field hasBlank = true; } else { // This field has data var rawValueList = fieldData.split(','); // Convert from a comma-delimited string to a list if (rawValueList.length == 0) { // Single value rawValue = rawValueList[0]; if (isNaN(rawValue)) { // Single string valie valueList = rawValue.trim(); } else { // Single number valueList = Number(rawValue); } } else { // Wire size Min/max if (fieldName == 'wire_size_mm2_min') { valueList.push(Number(rawValueList[0])); // Minimum wire size valueList.push(Number(rawValueList[1])); // Maximum wire size } else if (fieldName.includes('_lst')) { // List of numbers optionally with ranges for (rawValue of rawValueList) { rawValue = rawValue.trim(); if (rawValue.includes('-')) { // Range of integers var rangeMinMax = rawValue.split('-'); var startValue = Number(rangeMinMax[0]); var endValue = Number(rangeMinMax[1]); for (var aValue = startValue; aValue <= endValue; aValue++) { valueList.push(aValue); } } else { // Single number in a list valueList.push(Number(rawValue)); } } } else { // Regular list of values for (rawValue of rawValueList) { if (rawValue == '') { // Blank option hasBlank = true; } else if (isNaN(rawValue)) { // String valueList.push( rawValue.trim()); } else { // Number valueList.push(Number(rawValue)); } } } } } // See if there are at least two values in this field // hasBlank length // no values: no false 0 // blank only; no true 0 // one non-blank value: no false 1 // two non-blank values: yes false 2 // blank + one non-blank value: yes true 1 var thisFltrRng = {}; thisFltrRng.valueList = valueList; thisFltrRng.hasBlank = hasBlank; thisFltrRng.has2PlusValues = (valueList.length >= (hasBlank? 1 : 2)); // Add this range to the dictionary for all filters allFltrRngs[fieldName] = thisFltrRng; } } // Save the dictionary for all filters into the node object if (nodeCode in nodesDict) { nodesDict[nodeCode].fltrRngs = allFltrRngs; } else { debugText.innerHTML = nodeCode + ' in database but not in tree file'; } } // Load the filter controls with the filter ranges for the active node setUpFilter(); } } // fltrRngDataReceived // *** We received the data from the components table, asynchronously function compDataReceived( xmlHttpRequest) { console.log('compDataReceived'); // Store the fields definitions <<<40~50 ms>>> // Need to detect if the responseText is an error and put it to the console <<< var tableData = JSON.parse(xmlHttpRequest.responseText); var fieldNames = Object.keys(compDbFieldDict); for (var seriesData of tableData) { var compName = seriesData[0]; // The first field is the unique key for the record if (!(compName in allCompDataDict)) { // If we didn't already get this record previously // Create a dictionary for this component var compDict = {}; // Fill the dictionary with all the data from its database record for (var fieldNo = 1; fieldNo < fieldNames.length; fieldNo++) { // Skip the component name var fieldData = seriesData[fieldNo]; if (fieldData != '') { if (isNaN(fieldData)) { fieldData = fieldData.trim(); } else { fieldData = Number(fieldData); } } compDict[fieldNames[fieldNo]] = fieldData; } // Add this component dictionary to the dictionary of all components allCompDataDict[compName] = compDict; } } // Report that the database is loaded var noOfRecords = tableData.length; console.log('database loaded for ' + activeNodeCode + ', ' + noOfRecords + ' items'); // Flag that the records for this node and its chidren have been loaded, recursively flagDbLoaded(activeNodeObj); /* // Validate the values of the nodes for this component var missingNodeCodes = ''; for (var compName of Object.keys(allCompDataDict)) { var compRecord = allCompDataDict[compName]; for (var taxLvlNo = 3; taxLvlNo < TaxonomyFieldNames.length; taxLvlNo++) { var fieldName = TaxonomyFieldNames[taxLvlNo]; var nodeCode = compRecord[fieldName]; if (!nodeAncestorTable.hasOwnProperty(nodeCode)) { missingNodeCodes += '
' + compName + ':' + nodeCode; } } } if (missingNodeCodes != '') { debugText.innerHTML = 'Undefined node(s) in db: ' + missingNodeCodes; } */ // Link to let the user download the database // <<< downloadIcon.href = activeClassPath + '/db.csv'; // Update the filter input selectors <<<62 ms >>> // updateFilter(); // Flag components for other nodes, so we won't look at them when listing components flagCompForOtherNodes(); if (activeComp == '') { // There is no active component // Update the list of components based on the present filter parameters showCompList(); } else { // There is an active component // Show it showActiveComp(); } /* var manufOptions = manufFiltrSel.options var manufList = []; for (var manufOption of manufOptions) { manufList.push(manufOption.value); } zz(manufList) */ } // compDataReceived // *** Show the pictures and description for a node function showNodeInfo( nodeCode, itemNo) { // nodeCode is: // '' = activeNodeCode: presently selected node // not blank = code of node to show // itemNo is: // -1 = not hovering in a Navigate pane; show info on the present node // 0 = hovering in a Navigate pane but not in a menu: show the selection help image for this node's subnodes // 1 and above = hovering over a menu; that's the number at the left end of the menu item; show the info for the nide for that selector and include the selector number at the start of the description console.log('showNodeInfo', arguments.callee.caller.name, nodeCode, itemNo); // If the node is not specified, show the presently selected node if (nodeCode == '') { nodeCode = activeNodeCode; } showGeneralDescr('MISSING NODE DATA'); // Assume that this node is not defined var descrTxt = ''; var nodeObj = nodesDict[nodeCode]; if (nodeObj) { // Get a dictionary of the subnodes (none if this is a family node) var subNodeDict = nodeObj.ls; // Images var imgPath = getPathForNode(nodeCode); // Component picture showImage(imgPath, 'i', 'prodImgBox'); // Application picture var bottomImgName = 'a'; var applImgCursor = 'auto'; if (itemNo !== '') { // Hovering into a Navigate pane but not in a menu (!= doesn't work) if (itemNo == 0) { // Hovering into a Navigate pane but not in a menu if(subNodeDict) { // If a node other than family if (Object.keys(subNodeDict).length > 1) { // If it has multiple sub-nodes // The user is hovering in a Navigate pane but not in a menu // Show the image for the selections bottomImgName = 's'; } } } } applImgBox.style.cursor = applImgCursor; showImage(imgPath, bottomImgName, 'applImgBox'); // Hide the image upload forms compImgUploadDiv.style.display = 'none'; applImgUploadDiv.style.display = 'none'; // Description var itemNoStr = ''; if (itemNo) { if (itemNo > 0) { itemNoStr = itemNo + ': '; } } descrTxt = '' + itemNoStr + nodeObj.nm + '
' + nodeObj.ds; showGeneralDescr(descrTxt); } } // showNodeInfo // ************ FLOWCHART MODE FUNCTIONS ************ // INSTRUCTIONS: // 1. Find the flowchart document from a direct subfolder of the /connector/classes/ folder // 2. Open it in Draw.io // 3. Edit it // 4. Select just the flowchart of interest, export as SVG, name it f.svg, save it in the /connector/ folder // 5. Run convertSVG.py in the /connector/ folder // 6. Move the flowchart into the appropriate folder within the /connector/classes/ folder /* As created in draw.io, each item looks like this
5: Component
sockets
5: Component...
*/ // After running through the convertSVG.py script, the first line looks like this: // // *** Add functionality to a flowchart function addFunctionalityToFlowchart( ){ // Find the dictionaries and the path to the image, depending on the stage var subNodeDict = activeNodeObj.ls; var subItemNodeList = Object.keys(subNodeDict); var svgObjectDoc = flowchartObj.contentDocument; var aTagElements = svgObjectDoc.getElementsByTagName('a'); // Get all elements that have a link: the boxes // Do each box for (var tagNo = 0; tagNo < aTagElements.length; tagNo++) { // Get a box var aTagElement = aTagElements[tagNo]; // Get the code for the class that we will show when hovering over this box and to which we jump when this box is clicked // Assume var boxNodeCode = ''; // The node code is in the link in the box in the flowchart var boxSelectNo = 0; switch (aTagElement.id) { case 'back': // "Start" box at the top right boxNodeCode = getParentNodeCode(activeNodeCode); // When clicked, we go to the parent of this node break; case 'forward': // Forward box in a single-choice flowchart boxNodeCode = subItemNodeList[0]; // The class name is the one and only item in the list of subclasses // Change the text inside the forward box var nextItem = eval_redef(boxNodeCode); var nextItemName = nextItem.nm; var boxTextTag = aTagElement.getElementsByTagName('g')[0].getElementsByTagName('text')[0]; nextItemName = nextItemName.replace('&', '&'); // Workaround, because the "&" crashes the next line boxTextTag.innerHTML = nextItemName; break; default: // A regular box boxNodeCode = aTagElement.id; // The class name is in the link in the box in the flowchart boxSelectNo = subItemNodeList.indexOf(boxNodeCode) + 1; break; } // Store the class name in the control itself aTagElement.nodeCode = boxNodeCode; aTagElement.selectNo = boxSelectNo; // Set the actions when hovering and clicking aTagElement.addEventListener("mouseover", mouseOverEventListener(aTagElement)); // Instead, we pass a handle to the control, which now contains the node's code in its nodeCode property aTagElement.addEventListener("mouseout", mouseOutEventListener()); // This fails the Javascript validator: "Don't make functions within a loop." aTagElement.addEventListener("click", clickEventListener(aTagElement)); // jumpToItem(itemCode) does not work because the argument is passed by reference; therefore, all the controls get the name that is assigned last // The lines below pass the validator but do not work // aTagElement.addEventListener("mouseover", make_showNodeInfo(this.nodeCode, this.selectNo)); // Instead, we pass a handle to the control, which now contains the node's code in its nodeCode property // aTagElement.addEventListener("mouseout", make_showNodeInfo('', '')); // This fails the Javascript validator: "Don't make functions within a loop." // aTagElement.addEventListener("click", make_goToNode(this.nodeCode)); // jumpToItem(itemCode) does not work because the argument is passed by reference; therefore, all the controls get the name that is assigned last (weird) aTagElement.style.cursor = "pointer"; } // These event handler fuctions are declared outside the for loop to keep the validator from complaining: // "Functions declared within loops referencing an outer scoped variable may lead to confusing semantics." function mouseOverEventListener (tagElem) { return function() { showNodeInfo(tagElem.nodeCode, tagElem.selectNo); };} function mouseOutEventListener () { return function() { showNodeInfo('', '',0); };} function clickEventListener (tagElem) { return function() { goToNode(tagElem.nodeCode); };} } // ************ SEARCH FUNCTIONS ************ // *** Search the selected text function searchText( searchKey) { console.log('searchText' + searchKey); if (!caseSensitiveChkBx.checked) { searchKey = searchKey.toLowerCase(); } var MAX_RESULTS = 20; var noOfResults = 0; var resultStr = ''; if (searchKey != '') { for (var itemCode in nodeAncestorTable) { if(nodeAncestorTable.hasOwnProperty(itemCode)) { var itemObj = eval_redef(itemCode); // Name var itemName = itemObj.nm; var itemNameLC = itemName; if (!caseSensitiveChkBx.checked) { itemNameLC = itemNameLC.toLowerCase(); } var keyFound = (itemNameLC.indexOf(searchKey) >= 0); // Description var itemDescription = itemObj.ds; if (!caseSensitiveChkBx.checked) { itemDescription = itemDescription.toLowerCase(); } keyFound |= (itemDescription.indexOf(searchKey) >= 0); // Keywords var keywdList = itemObj.kw; if (keywdList) { for (var kwdNo = 0; kwdNo < keywdList.length; kwdNo++) { var aKeyword = keywdList[kwdNo]; if (aKeyword) { if (aKeyword != '') { // Except for empty entries keyFound |= (aKeyword.toLowerCase().indexOf(searchKey) >= 0); } } } } if (keyFound) { var linksListHTML = '' + itemName + ''; resultStr += linksListHTML + '
'; noOfResults++; } if (noOfResults > MAX_RESULTS ) { resultStr += '(too many results)'; break; } } } if (noOfResults == 0 ) { resultStr = 'No results'; } } searchResultsBox.innerHTML = resultStr; // Update the URL updateURL(); } // ************ COMPONENT SERIES FUNCTIONS ************ // *** Go to the selected component function goToSelectedComp( ){ console.log('goToSelectedComp', arguments.callee.caller.name); if (atFamily) { // At the family level backNodesStack.push(activeNodeCode); // push this family node in the stack goBackBtn.src = 'img/goback.gif'; // Enable the back navigation button addSeriesDisabBtn.style.display = 'inline'; // Hide the Add component button addSeriesBtn.style.display = 'none'; // Hide the Add component button listBtns.style.visibility = 'hidden'; // Hide the list buttons } else { // Not at the family level: jump to the node for that component's family var compRecord = allCompDataDict[activeComp]; var seriesFamilyNodeCode = compRecord.family_; var doStackNode = true; // Push this node into the stack goToNode(seriesFamilyNodeCode, doStackNode); // It does not stack or clear activeComp because we're not at the family level } // Hide the filter // Don't turn off the switch, so we can look at its state when we no longer have a selected component filterColTitleSet.style.display = 'none'; filtersDbEditBox.style.display = 'none'; homeFltrOffBox.style.display = 'none'; // Show the active component showActiveComp(); // Update the URL updateURL(); } // *** Show the active component function showActiveComp( ){ console.log('showActiveComp', arguments.callee.caller.name); // Update the navigation tree and the selection tabs updtNavigTree(activeComp); // Show only the active component in the list componentListBox.innerHTML = '
' + activeComp + '
'; // Show info for the active component showCompInfo(activeComp); listBtns.style.visibility = 'hidden'; // Hide the list buttons // Change the title in the type pane typeTitleSpan.innerHTML = 'Component identified'; } // *** Show a component's data // Called when the user hovers over a component in the component list or in the quick list // Also called when setting a new active component function showCompInfo( compName) { console.log('showCompInfo', compName, arguments.callee.caller.name); // Default pictures var descrTxt = 'Loading data'; var compImgSrc = 'img/loading.jpg'; var applImgSrc = 'img/loading.jpg'; prodImgBox.src = compImgSrc; applImgBox.src = applImgSrc; // If there is no active component, use the selected one //var shownComp = (activeComp == '')? compName : activeComp; // Navigation tree updtNavigTree(compName); var compRecord = allCompDataDict[compName]; //if (activeNodeObj.compLoaded && (compName != '')) { if (compRecord) { // Show a component's description descrTxt = ''; // Deal with null fields for (var fieldName of Object.keys(compDbFieldDict)) { if (compRecord[fieldName] == null) { compRecord[fieldName] = ''; } } // --- Component, manufacturer // Manufacturer var manufName = compRecord.manuf_name; descrTxt += 'Manuf: ' + manufName + '
'; // Series var manufLink = compRecord.manuf_link; var connSeriesLink = compName; if ((manufLink !== '') && (typeof manufLink == 'string') && (manufLink.indexOf('http') === 0)) { connSeriesLink = '
' + compName + ''; } descrTxt += 'Series: ' + connSeriesLink; // A.k.a. if (compRecord.aka_ !== '') { descrTxt += ', a.k.a. ' + compRecord.aka_; } // Specs var pdfLink = compRecord.pdf_link; if ((pdfLink !== '') && (typeof pdfLink == 'string') && (pdfLink.indexOf('http') === 0)) { descrTxt += ' [pdf]'; } // Obsolete var isObsolete = compRecord.obsolete_bool; if (isObsolete) { descrTxt += ' (obsolete)'; } descrTxt += '
'; // Other manufacturers var manufList = compRecord.manuf_list; if (manufList && (manufList != '')) { var manufList = manufList.split(CloseSquareBracket); var otherManufNamePNsStr = ''; for (var otherManufNamePNs of manufList) { if (otherManufNamePNs.length > 2) { var otherManufNamePNs = otherManufNamePNs.split(OpenSquareBracket); var otherManufName = otherManufNamePNs[0]; var otherManufPNs = otherManufNamePNs[1]; var otherManufLinks = ''; if (otherManufPNs != '') { otherManufPNs = otherManufPNs.split(/[, ]/); for (var otherManufPN of otherManufPNs) { otherManufLinks += ' ' + otherManufPN + ''; } otherManufLinks = ':' + otherManufLinks; } otherManufNamePNsStr += otherManufName + otherManufLinks + '; ' } } descrTxt += 'Other sources: ' + otherManufNamePNsStr + '
'; } // Vendor var vendorLink = compRecord.vendor_link; if ((vendorLink !== '') && (typeof vendorLink == 'string') && (vendorLink.indexOf('http') === 0)) { var dummlyLinkTag = document.createElement('a'); dummlyLinkTag.href = vendorLink; var urlDomain = dummlyLinkTag.hostname; var urlDomainParts = urlDomain.split('.'); var vendorName = urlDomainParts[urlDomainParts.length -2]; // e.g, from www.digikey.com, get the 'digikey' part descrTxt += 'Vendor: ' + vendorName + '
'; } // Standard if (compRecord.standard_ !== '') { descrTxt += 'Standard: ' + compRecord.standard_ + '
'; } // Country/region if (compRecord.country_region !== '') { descrTxt += 'Country/region: ' + compRecord.country_region + '
'; } // Notes if (compRecord.notes_ !== '') { descrTxt += 'Notes: ' + compRecord.notes_ + '
'; } // --- Main attributes descrTxt += ''; // Access, topology, structure var totCircuitsStr = compRecord.tot_circuits_lst + ' circuits'; var structStr = compRecord.structure_.slice(compRecord.structure_.indexOf(':') +1); var mainCharactStr = [compRecord.access_, compRecord.topology_, structStr, totCircuitsStr].filter(Boolean).join('; '); if (mainCharactStr !== '') { descrTxt += 'Main characteristics: ' + mainCharactStr + '
'; } // Circuits if (compRecord.tot_circuits_max > 0) { descrTxt += 'Total circuits: '; if (compRecord.tot_circuits_min < compRecord.tot_circuits_max) { descrTxt += compRecord.tot_circuits_min + '~'; } descrTxt += compRecord.tot_circuits_max + '
'; } // Class specific switch (activeClassCode) { case 'shroudless_conn_': // Breakaway if (compRecord.class_bool1 == 1) { descrTxt = 'Breakaway
'; } break; case 'rectangular_conn': // Board-to-board placement if (compRecord.class_txt3 !== '') { descrTxt += 'B2B placement: ' + compRecord.class_txt3 + '
'; } break; } descrTxt += '
'; // --- Termination & mount - wire, cable descrTxt += ''; // Wire termination var wireSizeStr = '', wireEntryOrientStr = '', wirePitchStr = '', electricalRowsStr = '', physicalRowsStr = '', wirePointsPerCktStr = ''; if ((compRecord.wire_size_mm2_min != '') && (compRecord.wire_size_mm2_max != '') && (compRecord.wire_size_mm2_min > 0) && (compRecord.wire_size_mm2_max > 0)) { if (wireSizeUnitsSel.value == 'mm2') { // Metric if (compRecord.wire_size_mm2_min < compRecord.wire_size_mm2_max) { wireSizeStr = compRecord.wire_size_mm2_min.toFixed(2) + '~'; } wireSizeStr += compRecord.wire_size_mm2_max.toFixed(2) + ' mm²'; // Unicode for a superscript '2' } else { // American if (compRecord.wire_size_mm2_min < compRecord.wire_size_mm2_max) { wireSizeStr = conv_mm2ToAWG(compRecord.wire_size_mm2_min) + '~'; } wireSizeStr += conv_mm2ToAWG(compRecord.wire_size_mm2_max); } } if (compRecord.wir_cbl_entry_dir != '') { wireEntryOrientStr = compRecord.wir_cbl_entry_dir + ' entry'; } if (compRecord.wiring_pitch_mm != '') { wirePitchStr = compRecord.wiring_pitch_mm + ' mm pitch'; } if (compRecord.electrical_rows_lst > 1) { electricalRowsStr = compRecord.electrical_rows_lst + ' electrical rows'; } if (compRecord.phys_rows_per_elec_lst > 1) { physicalRowsStr = compRecord.phys_rows_per_elec_lst + ' physical rows/electrical row'; } if (compRecord.wir_points_p_ckt_max > 1) { if (compRecord.wir_points_p_ckt_min < compRecord.wir_points_p_ckt_max) { wirePointsPerCktStr += compRecord.wir_points_p_ckt_min + '~'; } wirePointsPerCktStr += compRecord.wir_points_p_ckt_max + ' wire points per circuit'; } var wireTermStr = [compRecord.wire_cbl_termin, compRecord.contact_removability, compRecord.contact_release, compRecord.wire_cbl_release, compRecord.wiring_type, wireSizeStr, wireEntryOrientStr, wirePitchStr, electricalRowsStr, physicalRowsStr, wirePointsPerCktStr].filter(Boolean).join('; '); if (wireTermStr !== '') { descrTxt += 'Wire termination: ' + wireTermStr + '
'; } // Class specific switch (activeClassCode) { case 'wire_splices_': // class_bool1: tap_ // class_bool2: multi_wire_in_entry // class_int1: wires_max var tapStr = ''; multiWirePerEntryStr = ''; noOfWiresStr = ''; if (compRecord.class_bool1 == 1) { tapStr = 'tap'; } if (compRecord.class_bool2 == 1) { multiWirePerEntryStr = 'multiple wires per entry'; } if (compRecord.class_int1 > 0) { noOfWiresStr = compRecord.class_int1 + ' max wires'; } var extraStr = [tapStr, multiWirePerEntryStr, noOfWiresStr].filter(Boolean).join(', '); if (extraStr != '') { descrTxt += extraStr + '
'; } break; case 'junction_barrier_blocks_': // class_txt1: screw_size if (compRecord.class_txt1 != '') { descrTxt += 'Screw size: ' + compRecord.class_txt1 + '
'; } break; case 'terminal_blocks': // class_txt1: Two wiring rows if (compRecord.class_txt1 != '') { descrTxt += 'Two rows of terminals: ' + compRecord.class_txt1 + '
'; } break; case 'board_in_comp': // class_bool2: stripped_wire var strippedStr = (compRecord.class_bool2 == 1)? 'stripped' : 'unstripped'; descrTxt += 'Wires: ' + strippedStr + '
'; break; } // --- Termination & mount - PCB var pcbPitchStr = '', pcbRowsStr = ''; if (compRecord.pcb_pitch_mm > 0) { pcbPitchStr = Number(compRecord.pcb_pitch_mm).toFixed(2) + ' mm pitch'; } if (compRecord.pcb_rows_max > 1) { if (compRecord.pcb_rows_min < compRecord.pcb_rows_max) { pcbRowsStr += compRecord.pcb_rows_min + '~'; } pcbRowsStr += compRecord.pcb_rows_max + ' rows'; if (compRecord.pcb_row_spcng_mm > 0) { pcbRowsStr += ' spaced ' + Number(compRecord.pcb_row_spcng_mm).toFixed(2) + ' mm'; } } var pcbTermStr = [compRecord.pcb_mount, compRecord.pcb_contact_arrang, pcbPitchStr, pcbRowsStr].filter(Boolean).join('; '); if (pcbTermStr !== '') { descrTxt += 'PCB termination: ' + pcbTermStr + '
'; } if (compRecord.pcb_mount_orientation !== '') { descrTxt += 'PCB mount/orientation: ' + compRecord.pcb_mount_orientation + '
'; } // Class specific if (activeClassCode == 'board_in_comp') { // class_bool1: wire_mount_first // class_txt1: pcb_retention var firstMountStr = (compRecord.class_bool1 == 1)? 'terminated to wire' : 'mounted to PCB'; descrTxt += 'First: ' + firstMountStr + '
'; if (compRecord.class_txt1 != '') { descrTxt += 'PCB retention: ' + compRecord.class_txt1 + '
'; } } // --- Termination & mount - other // Panel mount var panelMountStr = compRecord.panel_mount; if (panelMountStr !== '') { panelMountStr = [panelMountStr,compRecord.pnl_mnt_2nd_term].filter(Boolean).join('; '); var pnlIsolStr = ''; if (compRecord.isolated_pnl) { pnlIsolStr = ', ' + compRecord.isolated_pnl; } descrTxt += 'Panel mount: ' + panelMountStr + compRecord + '
'; } // Other mount if (compRecord.other_mount !== '') { descrTxt += 'Other mount: ' + compRecord.other_mount + '
'; } descrTxt += '
'; // --- Mating - gender/shroud descrTxt += ''; // Mating, electrical // Gender, self-mate var familyPolarityStr = '', selfMateStr = ''; if (compRecord.family_polarity != '') { familyPolarityStr = formatListNicely(compRecord.family_polarity); } var genderStr = [familyPolarityStr, selfMateStr].filter(Boolean).join('; '); if (genderStr != '') { descrTxt += 'Gender: ' + genderStr + '
'; } if (compRecord.plug_genders != '') { descrTxt += 'Plugs: ' + formatListNicely(compRecord.plug_genders) + '
'; } if (compRecord.pnl_rcpt_genders != '') { descrTxt += 'Panel receptacles: ' + formatListNicely(compRecord.pnl_rcpt_genders) + '
'; } if (compRecord.pcb_hdr_genders != '') { descrTxt += 'PCB headers: ' + formatListNicely(compRecord.pcb_hdr_genders) + '
'; } if (compRecord.interpos_sex_shrd != '') { descrTxt += 'Interposers: ' + formatListNicely(compRecord.interpos_sex_shrd) + '
'; } // --- Mating - contact arrangement var matingPitchStr = '', matingRowsStr = ''; if (compRecord.mating_pitch_mm > 0) { matingPitchStr = Number(compRecord.mating_pitch_mm).toFixed(2) + ' mm pitch'; } if (compRecord.mate_rows_lst != '') { matingRowsStr = compRecord.mate_rows_lst + ' rows'; if (compRecord.row_spacing_mm > 0) { matingRowsStr += ' spaced ' + Number(compRecord.row_spacing_mm).toFixed(2) + ' mm'; } } var arrangementStr = [compRecord.mate_contact_arrang,matingPitchStr,matingRowsStr].filter(Boolean).join('; '); if (arrangementStr !== '') { descrTxt += 'Contact arrangement: ' + arrangementStr + '
'; } // Class specific switch (activeClassCode) { case 'device_sockets': // class_txt2: contact_sides if (compRecord.class_txt2 != '') { descrTxt += 'Contact sides: ' + compRecord.class_txt2 + '
'; } break; case 'circular_conn': // class_lst: mate_circles_lst if (compRecord.class_lst != '') { descrTxt += 'Circles: ' + compRecord.class_lst + '
'; } break; } // --- Mating - contacts var contactGenderStr = '', contactSizeStr = '', maleContactShapeStr = '', femaleContactShapeStr = ''; if (compRecord.contact_gender != '') { contactGenderStr = compRecord.contact_gender; } if (compRecord.male_cont_size_mm != '') { contactSizeStr = Number(compRecord.male_cont_size_mm).toFixed(2) + ' mm'; } if (compRecord.male_cont_shape != '') { maleContactShapeStr = compRecord.male_cont_shape + ' male'; } if (compRecord.female_cont_shape != '') { femaleContactShapeStr = compRecord.female_cont_shape + ' female'; } if (compRecord.gndrls_cont_shape != '') { femaleContactShapeStr = compRecord.gndrls_cont_shape + ' genderless'; } var contactsStr = [contactGenderStr, contactSizeStr, maleContactShapeStr, femaleContactShapeStr].filter(Boolean).join('; '); if (contactsStr !== '') { descrTxt += 'Contacts: ' + contactsStr + '
'; } // Class specific if (activeClassCode == 'exterior_power_conn') { // class_bool1: shell_earth // class_bool2: t_blade // class_int1: full_round // class_int2: half_round // class_int3: flat_blade // class_lst: l_blade // class_float1: angled_blade // class_float2: curved_blade // class_txt1: wing_direction var str1 = '', str2 = '', str3 = '', str4 = '', str5 = '', str6 = '', str7 = '', str8 = '', str9 = ''; if (compRecord.class_int1 != '') { str1 = compRecord.class_int1 + ' round pin(s)'; } if (compRecord.class_int2 != '') { str2 = compRecord.class_int2 + ' half-round pin(s)'; } if (compRecord.class_int3 != '') { str3 = compRecord.class_int3 + ' flat blade(s)'; } if (compRecord.class_float1 != '') { str4 = compRecord.class_float1 + ' angled blade(s)'; } if (compRecord.class_float2 != '') { str5 = compRecord.class_float2 + ' curved blade(s)'; } if (compRecord.class_txt1 != '') { str6 = 'curved blade with a ' + compRecord.class_txt1 + ' wing (male)'; } if (compRecord.class_bool2 == 1) { str7 = 'T-blade'; } if (compRecord.class_lst == 1) { str8 = compRecord.class_lst + ' L-blade(s)'; } if (compRecord.class_bool1 == 1) { str9 = compRecord.class_bool1 + 'shell is earth contact'; } var contactShapeStr = [str1, str2, str3, str4, str5, str6, str7, str8, str9].filter(Boolean).join(', '); if (contactShapeStr != '') { descrTxt += contactShapeStr + '
'; } } // --- Mating - hybrid if (compRecord.hybrid_ != '') { var noAuxCktsStr = '', auxRowsSpngStr = '', auxPitchStr = '', auxContactSizeStr = '', auxMaleContactShapeStr = '', auxFemaleContactShapeStr = '', auxGenderlessContactShapeStr = ''; var hybridStr = compRecord.hybrid_; if (compRecord.aux_positions_max > 0) { if (compRecord.aux_positions_min < compRecord.aux_positions_max) { noAuxCktsStr = compRecord.aux_positions_min + '~'; } noAuxCktsStr += compRecord.aux_positions_max; } if (compRecord.aux_mate_rows_max > 0) { if (compRecord.aux_mate_rows_min < compRecord.aux_mate_rows_max) { auxRowsSpngStr = compRecord.aux_mate_rows_min + '~'; } auxRowsSpngStr += compRecord.aux_mate_rows_max + ' row(s)'; } if (compRecord.aux_mate_pitch_mm > 0) { auxPitchStr += Number(compRecord.aux_mate_pitch_mm).toFixed(2) + ' mm pitch'; } if (compRecord.aux_male_cont_mm != '') { auxContactSizeStr = Number(compRecord.aux_male_cont_mm).toFixed(2) + ' mm'; } if (compRecord.aux_mal_cnt_shape != '') { auxMaleContactShapeStr = compRecord.aux_mal_cnt_shape + ' male'; } if (compRecord.aux_fem_cnt_shape != '') { auxFemaleContactShapeStr = compRecord.aux_fem_cnt_shape + ' female'; } if (compRecord.gndrls_cont_shape != '') { auxGenderlessContactShapeStr = compRecord.gndrls_cont_shape + ' genderless'; } var hybridTxtStr = [hybridStr, noAuxCktsStr, auxRowsSpngStr, auxPitchStr, auxContactSizeStr, auxMaleContactShapeStr, auxFemaleContactShapeStr, auxGenderlessContactShapeStr].filter(Boolean).join('; '); if (hybridTxtStr !== '') { descrTxt += 'Hybrid: ' + hybridTxtStr + '
'; } } // --- Mating - mechanical // Mating face var faceSurfStr = '', faceShapeStr = '', shroudWallsStr = '', matedWidthStr = '', alignmentFeaturesStr = ''; if (compRecord.mating_face_surface != '') { faceSurfStr = compRecord.mating_face_surface + ' surface'; } if (compRecord.mating_face_shape != '') { faceShapeStr = compRecord.mating_face_shape + ' shape'; } if (compRecord.shroud_walls != '') { shroudWallsStr = compRecord.shroud_walls; } if (compRecord.mate_width_dia_mm != '') { matedWidthStr = Number(compRecord.mate_width_dia_mm).toFixed(2) + ' mm mating width/diameter'; } if (compRecord.alignment_features != '') { alignmentFeaturesStr = compRecord.alignment_features; } var shroudStr = [faceSurfStr, faceShapeStr, shroudWallsStr, matedWidthStr, alignmentFeaturesStr].filter(Boolean).join('; '); if (shroudStr !== '') { descrTxt += 'Mating face: ' + shroudStr + '
'; } // Class specific switch (activeClassCode) { case 'device_sockets': // class_txt1: what_for // class_txt3: fpc_end_shape if (compRecord.class_txt1 != '') { descrTxt += 'For: ' + compRecord.class_txt1 + '
'; } if (compRecord.class_txt3 != '') { descrTxt += 'FPC end: ' + compRecord.class_txt3 + '
'; } break; case 'coax_conn_': // class_float1: shield_id // class_float2: jack_height // class_txt1: Shroud_shield if (compRecord.class_float1 != 0) { descrTxt += 'Shield diameter: ' + compRecord.class_float1 + ' mm' + '
'; } if (compRecord.class_float2 != 0) { descrTxt += 'Jack height above PCB: ' + compRecord.class_float2 + ' mm' + '
'; } if (compRecord.class_txt1 != '') { descrTxt += 'Shroud and shield: ' + compRecord.class_txt1 + '
'; } break; case 'circular_conn': if (compRecord.class_txt1 != '') { descrTxt += 'Insert size: ' + compRecord.class_txt1 + '
'; } break; } // --- Mating - keying var keyingStr = ''; var keyingType = compRecord.keying_type; if (keyingType == 'keys') { if (compRecord.no_of_keys_lst != '') { keyingStr += compRecord.no_of_keys_lst + ' keys'; } if (compRecord.key_placement != '') { keyingStr += ' on ' + compRecord.key_placement; } } else if (keyingType != '') { keyingStr = 'keyed by ' + keyingType; } if (keyingStr !== '') { descrTxt += 'Keying: ' + keyingStr + '
'; } // Class specific switch (activeClassCode) { case 'rectangular_conn': // Key location, key shape keyingStr = [compRecord.class_txt1, compRecord.class_txt2].filter(Boolean).join('; '); if (keyingStr !== '') { descrTxt += 'Keys: ' + keyingStr + '
'; } break; case 'exterior_power_conn': // Position of pin and sleeve key if (compRecord.class_txt3 != '') { descrTxt += 'Clocking: ' + compRecord.class_txt3 + '
'; } break; } // --- Mating - fastening var latchedMateStr = '', latchStructStr = '', latchSide = '', latchMate = ''; if ((compRecord.latched_mate != '') && (compRecord.latch_side != '')) { var latchSideParts = compRecord.latch_side.split(':'); if (latchSideParts.length == 2) { latchSide = latchSideParts[1]; } latchedMateStr = 'latch on ' + latchSide.toLowerCase() + ' of ' + compRecord.latched_mate.toLowerCase(); } if (compRecord.latch_structure != '') { var latchStructParts = compRecord.latch_structure.split(':'); if (latchStructParts.length == 2) { latchStructStr = latchStructParts[1]; } } var fastenStr = [compRecord.fasten_method, latchStructStr, latchedMateStr, compRecord.latch_details].filter(Boolean).join('; '); if (fastenStr !== '') { descrTxt += 'Fastening: ' + fastenStr + '
'; } // --- Mating - multiple var noOfPlugsStr = ''; if (compRecord.mating_faces_lst > 1) { noOfPlugsStr = 'for up to ' + compRecord.mating_faces_lst + ' plugs or subconnectors';} var multiMateStr = [compRecord.multi_mating,noOfPlugsStr].filter(Boolean).join('; '); if (multiMateStr !== '') { descrTxt += 'Multiple: ' + multiMateStr + '
'; } descrTxt += '
'; // --- Housing descrTxt += ''; var housingColorStr = '', housingMaterialStr = '', multiPartHousingStr = '', pcbHeightStr = '', switchStr = ''; if (compRecord.housing_color != '') { housingColorStr = compRecord.housing_color; } if (compRecord.housing_material != '') { housingMaterialStr = formatListNicely(compRecord.housing_material); } if (compRecord.multi_part_housing != '') { multiPartHousingStr = compRecord.multi_part_housing; } if (compRecord.pcb_height_mm_lst != '') { pcbHeightStr = compRecord.pcb_height_mm_lst + ' mm mated height'; } // Specific class if (['concentric_conn','coax_conn_'].includes(activeClassCode)) { if (compRecord.class_bool1 == 1) { switchStr = ' includes switch'; } } var housingStr = [housingColorStr, housingMaterialStr, multiPartHousingStr, pcbHeightStr, switchStr].filter(Boolean).join('; '); if (housingStr !== '') { descrTxt += 'Housing: ' + housingStr + '
'; } descrTxt += '
'; // --- Other - ratings var voltStr = '', currStr = '', freqStr = '', impedanceStr = ''; if ((compRecord.max_voltage_v != '') && (compRecord.max_voltage_v != 0)) { voltStr = compRecord.max_voltage_v + ' V'; } if ((compRecord.max_current_a != '') && (compRecord.max_current_a != 0)) { currStr = compRecord.max_current_a + ' A'; } if ((compRecord.impedance_ohm != '') && (compRecord.impedance_ohm != 0)) { impedanceStr = compRecord.impedance_ohm + ' Ω'; } if ((compRecord.max_frequency_ghz != '') && (compRecord.max_frequency_ghz != 0)) { freqStr = compRecord.max_frequency_ghz + ' GHz'; } var ratingsStr = [voltStr, currStr, impedanceStr,freqStr].filter(Boolean).join('; '); if (ratingsStr !== '') { descrTxt += 'Ratings: ' + ratingsStr + '
'; } // Class specific switch (activeClassCode) { case 'coax_conn_': // class_int2: power_at_low_freq_w // class_int3: power_at_cutoff_freq_w var lowFreqPwrStr = '', cutOffFreqPwrStr = ''; if (compRecord.class_int2 != 0) { var lowPwr_kW = compRecord.class_int2 / 1000; lowFreqPwrStr = lowPwr_kW + ' kW at low freq.'; } if (compRecord.class_int3 != '') { var lowPwrAtCutoff_kW = compRecord.class_int3 / 1000; cutOffFreqPwrStr = lowPwrAtCutoff_kW + ' kW at cut-off freq.'; } var coaxPowerStr = [lowFreqPwrStr, cutOffFreqPwrStr].filter(Boolean).join(', '); if (coaxPowerStr != '') { descrTxt += 'Power: ' + coaxPowerStr + '
'; } break; case 'exterior_power_conn': // class_txt2: function if (compRecord.class_txt2 != '') { descrTxt += 'Function: ' + compRecord.class_txt2 + '
'; } break; } // Applications var applicationStr = [compRecord.appl_industry, compRecord.appl_product, compRecord.appl_for_device,compRecord.appl_harsh_envir, compRecord.appl_wiring, compRecord.appl_other].filter(Boolean).join('; '); if (applicationStr !== '') { descrTxt += 'Application: ' + applicationStr + '
'; } // Pictures // Extract just the first word in the component name, as the name of the pictures var pictName = compName.split(' ' + OpenParen); pictName = pictName[0].trim(); pictName = pictName.replace('/',''); // Remove the slash used in some part numbers pictName = pictName.replace(String.fromCharCode(176),''); // Remove the degrees symbol used in some part numbers // Component picture compImgSrc = 'img/missing.jpg'; var legendTxt = 'Add picture'; if (compRecord.pictures_.includes('comp')) { compImgSrc = 'compimg/' + pictName + '.jpg'; compImgUploadDiv.style.display = 'none'; legendTxt = 'Replace picture'; } // If editing, show the picture upload form if ((activeComp != '') && enabEditChkBox.checked) { compImgUploadDiv.style.display = 'block'; compImgUploadForm.action = 'cgi-bin/upldpict.py?appl=0&comp=' + pictName; compImgUploadLegend.innerHTML = legendTxt; } // Application picture applImgSrc = 'img/missing.jpg'; legendTxt = 'Add picture'; if (compRecord.pictures_.includes('appl')) { applImgSrc = 'applimg/' + pictName + '.jpg'; applImgUploadDiv.style.display = 'none'; legendTxt = 'Replace picture'; } // If editing, show the picture upload form if ((activeComp != '') && enabEditChkBox.checked) { applImgUploadDiv.style.display = 'block'; applImgUploadForm.action = 'cgi-bin/upldpict.py?appl=1&comp=' + pictName; applImgUploadLegend.innerHTML = legendTxt; } } // If there is a component // Show the description and the pictures showCompDescr(descrTxt); prodImgBox.src = compImgSrc; applImgBox.src = applImgSrc; // Show the DB modify buttons if (activeComp != '') { dbModifyBtns.style.display = 'inline'; dbModifyDisabBtns.style.display = 'none'; } } // showCompInfo // ************ USER ACTIONS IN MAIN WINDOW ************ // *** The user hovered over or clicked a brand logo function showBrand( brandName) { console.log('showBrand',brandName); if (selectedBrand == '') { // var brandRecord = brandDict[brandName]; // Description var brandDescr = 'Brand:' + brandName + '
'; if (brandRecord.manufURL == '') { brandDescr += 'Manufacturer: ' + brandName ; } else { brandDescr += 'Manufacturer: ' + brandName + ''; } if (brandRecord.country != '') { brandDescr += '
Country: ' + brandRecord.country ; } if (brandRecord.notes != '') { brandDescr += '
Notes: ' + brandRecord.notes; } if (brandRecord.manufName != '') { // Link to select products from this manufacturer brandDescr += '

'; } showGeneralDescr(brandDescr); // Image var imgSrc = 'img/dot.png'; if (brandRecord.logoList.length > 0 ) { imgSrc = 'logos/' + brandRecord.logoList[0] + '.jpg'; } prodImgBox.src = imgSrc; imgSrc = 'img/dot.png'; if (brandRecord.logoList.length > 1 ) { imgSrc = 'logos/' + brandRecord.logoList[1] + '.jpg'; } applImgBox.src = imgSrc; } } // *** The user clicked a brand logo function brandClick( brandName) { console.log('brandClick',brandName); // Select a brand if (selectedBrand != '') { selectedBrand = ''; showBrand(brandName); } selectedBrand = brandName; } // *** The user clicked a Quick Filter topology button function quickTopoBtnClick( thisQuickTopoBtn) { console.log('quickTopoBtnClick'); // Allow up to two butttons to be active // Possibilities: // 0 total: This button was turned off, none are on -> OK; no toptology is selected // 1 total: This button was turned off, another one is on -> OK; the topology is something-to-itself // 1 total: This button was turned on, it's the only one -> OK; the topology is something-to-itself // 2 total: This button was turned on, another one is also on -> OK; the topology is something-to-something_else // 3 total: This button was turned on, two other ones are also on -> First, turn off the first one to be selected; the topology is something-to-something_else // Find how many buttons are on, and remember one of them var noOfSelBtns = 0; var aSelQuickTopoBtn; var quickTopoBtns = quickFltrBox.getElementsByClassName('quickTopoBtns'); for (var quickTopoBtn of quickTopoBtns) { if (quickTopoBtn.checked) { noOfSelBtns++; aSelQuickTopoBtn = quickTopoBtn; } } // Save globally the newest button to be pressed and the previously pressed one // That way, if the user presses a 3rd button, we can drop the first one switch (noOfSelBtns) { case 0: // This button was turned off, none are on oldestSelTopoBtn = null; newestSelTopoBtn = null; break; case 1: // This button was turned on, it's the only one // This button was turned off, another one is on oldestSelTopoBtn = aSelQuickTopoBtn; newestSelTopoBtn = null; break; case 2: // This button was turned on, another one is also on newestSelTopoBtn = thisQuickTopoBtn; break; case 3: // This button was turned on, two other ones are also on // Turn off the oldest one, remeber the other two oldestSelTopoBtn.checked = false; oldestSelTopoBtn = newestSelTopoBtn; newestSelTopoBtn = thisQuickTopoBtn; noOfSelBtns = 2; break; } // Now, at most two buttons are turned on (noOfSelBtns has the number) // See which ones, in the order that they are presented in the pane, to avoid having both wire-to-board and board-to-wire, since they are the same var primaryQuickTopoBtn, secondaryQuickTopoBtn; for (quickTopoBtn of quickTopoBtns) { if (quickTopoBtn.checked) { if (primaryQuickTopoBtn) { secondaryQuickTopoBtn = quickTopoBtn; } else { primaryQuickTopoBtn = quickTopoBtn; } } } // Now, primaryQuickTopoBtn has the primary conductor and secondaryQuickTopoBtn the secondary conductor // Determine the topology accordingly var topoValueStr = ''; if (noOfSelBtns > 0) { var primaryQuickTopoVal = primaryQuickTopoBtn.value; topoValueStr = primaryQuickTopoVal[0].toUpperCase() + primaryQuickTopoVal.slice(1) + "-to-"; if (noOfSelBtns == 1) { topoValueStr += primaryQuickTopoBtn.value; } else { topoValueStr += secondaryQuickTopoBtn.value; } } // Reflect it in the attributes pane quickFtrAttrSel(topology_Sel,topoValueStr, quickTopoTxtFld); // Correct the instructions in the title if (noOfSelBtns == 0) { quickTopoTxtFld.innerHTML = "Select 1 or 2 items"; } } // *** The user clicked the "list all components from this manufacturer" button function listManufCompClick( manufName) { mainTab_N.click(); if (!filterSwitch.checked) { filterSwitch.click(); } filterAttrTab.click(); filterChkBox1.click(); manufFiltrSel.value = manufName; filterSelChanged( manufFiltrSel); } // *** The user clicked a Quick Filter the nummber of circuits radio button function quickFltrNoOfCktsClick( inputElem) { console.log('quickFltrNoOfCktsClick'); if (inputElem == quickFltrNoOfCktsTxtFld) { quickFltrNoOfCktsBtn0.checked = false; quickFltrNoOfCktsBtn1.checked = false; quickFtrAttrSel(tot_circuits_lstSel,''); } else { quickFltrNoOfCktsTxtFld.value = ''; quickFtrAttrSel(tot_circuits_lstSel,inputElem.value); } } // *** The user changed the Quick Filter number of circuits text field function quickFltrNoOfCktsChanged( ) { console.log('quickFltrNoOfCktsChanged'); var quickFltrNoOfCkts = quickFltrNoOfCktsTxtFld.value; if ((quickFltrNoOfCkts != '') && isNumber(quickFltrNoOfCkts)) { // We have a number of circuits quickFtrAttrSel(tot_circuits_lstSel,quickFltrNoOfCkts); } } // *** The user clicked a Quick Filter pieces button function quickPiecesBtnClick( radioBtn) { console.log('quickPiecesBtnClick'); // Get the selection and display the related filter section var structValueStr = getRadioBtnVal('quickPiecesBtns'); if (structValueStr.slice(0,4) == structure_Sel.value.slice(0,4)) { // Look at only the first few characters because the "one piece" selection is different from the value in the attributes select // The user clicked a radio button that was already selected. Toggle the button off radioBtn.checked = false; structure_Sel.selectedIndex = 0; quick1PieceDiv.style.display = 'none'; quickShapeDiv.style.display = 'none'; // Reflect it in the attributes pane quickFtrAttrSel(structure_Sel,'', quickPiecesTxt); } else { // The user clicked a radio button that was not already selected switch (structValueStr) { case 'One piece': // Single piece quick1PieceDiv.style.display = 'block'; quickShapeDiv.style.display = 'none'; structValueStr = 'One piece, any type'; var onePieceValueStr = getRadioBtnVal('quick1PieceBtns'); if (onePieceValueStr != '') { structValueStr = onePieceValueStr; } break; case 'Two pieces:2-piece 1-mating': // Two pieces quick1PieceDiv.style.display = 'none'; quickShapeDiv.style.display = 'block'; break; case 'Three pieces:3-piece 2-mating': // Three pieces quick1PieceDiv.style.display = 'none'; quickShapeDiv.style.display = 'none'; break; } // Reflect it in the attributes pane quickFtrAttrSel(structure_Sel,structValueStr, quickPiecesTxt); // Show or hide the shape selectors quickShapeDiv.style.display = (structValueStr == 'Two pieces:2-piece 1-mating')? 'block' : 'none'; } } // *** The user changed the Quick Filter shape function quickFltrShapeChanged( radioBtn) { console.log('quickFltrShapeChanged'); // Reflect it in the mate face attribute select var radioValue = quickRadioClick(radioBtn, 'quickShapeBtns', mating_face_shapeSel, quickShapeTxt); // Show or hide related sections var isRound = (radioValue == "Round, any type"); var isRect = !isRound && (radioValue != ''); quickDiameterDiv.style.display = isRound? 'block' : 'none'; quickPitchRowsDiv.style.display = isRect? 'block' : 'none'; } // *** The user changed the Quick Filter diameter, number of rows function quickFltrMeasureChanged( txtFld, attrSel) { console.log('quickFltrMeasureChanged'); var txtFldValue = txtFld.value; if ((txtFldValue != '') && isNumber(txtFldValue)) { quickFtrAttrSel(attrSel,txtFldValue); } } // *** The user clicked a Quick Filter radio button function quickRadioClick( radioBtn, btnClassName, attrSel, quickLabel) { console.log('quickRadioClick'); var radioValue = getRadioBtnVal(btnClassName); if (radioValue == attrSel.value) { // The user clicked a radio button that was already selected. Toggle the button off radioBtn.checked = false; radioValue = ''; } quickFtrAttrSel(attrSel, radioValue, quickLabel); return radioValue; } // *** Set an attributes filter select function quickFtrAttrSel( attrSel, selValue, labelTxtFld) { console.log('quickFtrAttrSel',selValue); // Reflect it in the attributes pane var labelStr = selValue; if (selValue == '') { // Clear the select attrSel.selectedIndex = 0; labelStr = 'Select an item'; } else { // Set the select attrSel.value = selValue; if (attrSel.selectedIndex == -1) { attrSel.selectedIndex = 0; } } // If requested, show the selection in the title if (labelTxtFld) { labelTxtFld.innerHTML = labelStr; } // Act as if the user had changed the select // This shows or hides the Clear Filter buttons, shows the list of components, and updates the URL var filterSection = filterSelChanged(attrSel); if (selValue == '') { // Clear value // If all controls are clear, hide the quick filter clear buttons var quickPictInputs = quickFltrBox.getElementsByTagName('input'); var allClear = true; for (var quickPictInput of quickPictInputs) { if (quickPictInput.type == 'text') { if (quickPictInput.value != '') { allClear = false; break; } } else { if (quickPictInput.checked) { allClear = false; break; } } } if (allClear) { clearQuickFilterBtn.style.visibility = 'hidden'; } } else { // Set value // Open that filter section in the attributes pane var rowTriangleBtn = filterSection.getElementsByClassName('trianglebtn')[0]; rowTriangleBtn.checked = true; // Show the quick filter clear buttons clearQuickFilterBtn.style.visibility = 'visible'; } } // *** The user clicked the Show full component list button function showFullCompListClick( ) { console.log('showFullCompListClick'); compListMoreBtn.style.visibility = 'hidden'; componentListBox.innerHTML = compListHTML; } // *** The user clicked the Data Editing check box in the settings panel function dataEditEnabClick( ) { console.log('dataEditEnabClick'); dataEditCtrlsPnl.style.display = (enabEditChkBox.checked)? 'block' : 'none'; } // *** The user clicked the Upload button in an image upoad form function uploadImgClick( isApplImg) { console.log('uploadImgClick', isApplImg); // Save the data to the database var compRecord = allCompDataDict[activeComp]; var compRecordPictures = compRecord.pictures_.split(','); var pictureType = isApplImg? 'appl' : 'comp'; if (!(compRecordPictures.includes(pictureType))) { compRecordPictures.push(pictureType); } compRecord = {}; compRecord.comp_name = activeComp; // Add the component name into the dictionary compRecord.pictures_ = compRecordPictures.filter(Boolean).join(','); var jsonTableData = JSON.stringify(compRecord); // Convert the dictionary to a JSON string var dbURL = 'cgi-bin/saveTableData.py?compName=' + activeComp + '&tableData=' + jsonTableData; var xmlHttpRequest = new XMLHttpRequest(); xmlHttpRequest.open("GET",dbURL,true); xmlHttpRequest.activeComp = activeComp; // Save the name of the component in the request object because it's available in the onreadystatechange function xmlHttpRequest.send(); // Later, after the database has been updated, execute this function xmlHttpRequest.onreadystatechange = function() { if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200) { console.log(('flagged that ' + xmlHttpRequest.activeComp + ' has a picture'), xmlHttpRequest.responseText); } }; } // *** The user clicked the left arrow in the navigation tree // Use the stack to go back to the lower node in this same branch of the tree function downLevelArrowClick( ) { console.log('downLevelArrowClick'); var newNode = activeNodeCode; // Assume there is an active component: stay in this same family node if (activeComp == '') { // Theer is no active component newNode = getParentNodeCode(activeNodeCode); // If the new node has a single child, do not go to it, because goToNode will immediately jump back to the present node // Instead, go one more level down the tree var downLvlNodeDef = eval_redef(newNode); var subNodeDict = downLvlNodeDef.ls; if (subNodeDict) { var subNodeList = Object.keys(subNodeDict); if (subNodeList.length == 1) { // The new node has only one child newNode = getParentNodeCode(newNode); // Go to its parent node instead } } } var doStackNode = true; // Do push this node into the stack goToNode(newNode, doStackNode); } // *** The user clicked the "Back" button in the "Selection" tab // Use the stack to go back to the previous node or component function backBtnClick( ) { console.log('backBtnClick'); if (backNodesStack.length > 0) { // There are items to go back to // Save this node or component in the forward stack in case the user wants to go back to it var pushItem = (activeComp == '')? activeNodeCode : [activeNodeCode, activeComp]; // If a component, push a list with the node code and the component name fwdNodesStack.push(pushItem); goFwdBtn.src = 'img/gofwd.gif'; // Enable the navigation button // Go to the previously visited node or component var backItem = backNodesStack.pop(); if (backNodesStack.length == 0) { goBackBtn.src = 'img/goback_off.gif'; // The stack is empty: disable the back button } var doStackNode = false; // Don't push this node into the stack if (typeof backItem == 'string') { // It's a node. Go back to it goToNode(backItem, doStackNode); // If there is a selected component, this clears activeComp } else { // It's a component. Go back to it // Extract the node code and the component's name from the list of 2 items var nodeCode = backItem[0]; goToNode(nodeCode, doStackNode); // This clears activeComp activeComp = backItem[1]; // We'll go to that component after the database is loaded showActiveComp(); } } } // *** The user clicked the "Forward" button in the "Selection" tab function fwdBtnClick( ) { console.log('fwdBtnClick'); if (fwdNodesStack.length > 0) { // Go back to the previous forward node or component var fwdItem = fwdNodesStack.pop(); if (typeof fwdItem == 'string') { // It's a node, Go back to it activeComp = ''; // Deselect any active component var doStackNode = true; // Push this node into the stack goToNode(fwdItem, doStackNode); } else { // It's a component. Go back to it var nodeCode = fwdItem[0]; activeComp = fwdItem[1]; goToSelectedComp(); } // If no more, disable the button if (fwdNodesStack.length == 0) { goFwdBtn.src = 'img/gofwd_off.gif'; } } } // *** The user hovered into the Navigate pane function navigPaneEnter() { // Show the help image for this node's subnodes showNodeInfo('', 0); // 0 = show the selection help image for this node's subnodes } // *** The user hovered out of the Navigate pane function navigPaneLeave() { // Show the help image for this node's subnodes showNodeInfo('', -1); // 0 = show this node's book page or application image } // *** The user clicked the application image function applImageClick() { console.log('applImageClick'); // If a book image, show it var applImgSrc = applImgBox.src; if (applImgSrc.slice(-5) == 'b.jpg') { bookPagesImg.src = applImgSrc; bookPagesDiv.style.display = 'block'; //window.location = applImgSrc; } } // *** The user clicked the book pages div function bookPagesClick() { bookPagesDiv.style.display = 'none'; } // *** The user toggled the filter on/off switch function filterSwitchClick( event) { console.log('filterSwitchClick', activeComp); // See if a control-click if (event && (testShowAllFilters != event.ctrlKey)) { testShowAllFilters = event.ctrlKey; setUpFilter(); // Update the filter items } // Show or hide the filter showHideFilter(); // No component is selected // Update the list of components (if this node uses a database) and enable the selectors, based on the present filter parameters showCompList(); // Update the URL updateURL(); } // *** The user opened the filter settings box function openFilterSettingsClick( ) { if (settingsDialog.style.display != 'block') { // Unless already shown // Show the settings dialog settingsDialog.style.display = 'block'; // Prevent the click that created the dialog from propagating to the window, which would close it event.stopPropagation(); } // If the dialog is shown, the click propagates to the window, which will close it. // Update the URL //updateURL(); } // *** The user wants to clear all the selectors in a box function clearSelectors( selectorBox) { console.log('clearSelectors'); var filterInputs = selectorBox.getElementsByTagName('input'); for (var inputNo = 0; inputNo < filterInputs.length; inputNo++) { if (filterInputs[inputNo].type === 'radio') { if (filterInputs[inputNo].value == '-') { filterInputs[inputNo].checked = true; } } } // Show the component list showCompList(); // Update the URL updateURL(); } // *** The user want to close all the selectors in a box function closeSelectors( ) { console.log('closeSelectors'); var triangleCheckBoxes = filtersDbEditBox.querySelectorAll('input[type="checkbox"]'); for (var triangleCheckBox of triangleCheckBoxes) { triangleCheckBox.checked = false; } // Update the URL updateURL(); } // *** The user wants to clear the Quick Filter function clearQuickFilterClick( ) { console.log('clearQuickFilterClick'); // Hide the clear button clearQuickFilterBtn.style.visibility = 'hidden'; // Clear all the selectors in the Quick filter var quickPictInputs = quickFltrBox.getElementsByTagName('input'); for (var quickPictInput of quickPictInputs) { if (quickPictInput.type == 'text') { quickPictInput.value = ''; } else { quickPictInput.checked = false; } } // Hide the sections that depend on the selection quick1PieceDiv.style.display = 'none'; quickShapeDiv.style.display = 'none'; quickDiameterDiv.style.display = 'none'; quickPitchRowsDiv.style.display = 'none'; // Clear the affected attributes selects if (clearAttrFilterBtn.style.visibility != 'hidden') { clearFilterClick(clearAttrFilterBtn); } } // *** The user wants to clear a filter function clearFilterClick( clearFilterBtn) { // If only a section, this is the clear button. If undefined, we want to clear all the filters console.log('clearFilterClick'); // Component name filter if ((clearFilterBtn == compManufFiltrClrBtn) || (clearFilterBtn == clearAttrFilterBtn)) { filterCompNameTxtFld.value = ''; } // Find the DIV to clear var filterDiv; if (clearFilterBtn == clearAttrFilterBtn) { filterDiv = attrFltrBox; } // The attributes filter clear button if (clearFilterBtn == clearApplFilterBtn) { filterDiv = applFltrBox; } // The application filter clear button if (filterDiv) { // The user clicked a clear filters icon in a tab // Hide all the clear filter icons in that tab var clearSectFilterBtns = filterDiv.getElementsByClassName('clearSectFilterIcon'); for (var clearSectFilterBtn of clearSectFilterBtns) { clearSectFilterBtn.style.visibility = 'hidden'; } // Clear the quick filter if (clearQuickFilterBtn.style.visibility != 'hidden') { clearQuickFilterClick(); } } else { // The user clicked a clear button for a specific filter section filterDiv = clearFilterBtn.parentNode; } // Hide the clear filter icon clearFilterBtn.style.visibility = 'hidden'; // Clear all the selects in the specified section of the entire filter var filterSelects = filterDiv.getElementsByTagName('select'); for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { // Reset this select var theSelect = filterSelects[selectNo]; theSelect.selectedIndex = 0; theSelect.oldIndex = 0; // Re-enable all the items for (var anOption of theSelect.options) { anOption.disabled = false; } // Hide the "clear filter" icon if (filterSelects[selectNo].className.includes('applFilterSel')) { clearApplFilterBtn.style.visibility = 'hidden'; } else { clearAttrFilterBtn.style.visibility = 'hidden'; } } // Show the list of components (if this node uses a database) and enable the selectors, based on the present filter parameters showCompList(); // Update the URL updateURL(); } // *** The user selected an item in the list of components function compSeriesClick( compSeriesName){ console.log('compSeriesClick', compSeriesName); // Turn off the filter if (filterSwitch.checked) { filterSwitch.checked = false; showHideFilter(); } // Go to that component activeComp = compSeriesName; goToSelectedComp(); } // *** The user clicked the Navigation, Browse, or Search tab function selectMainTabClick( clickedTab) { console.log('selectMainTabClick'); // Update the navigation tree and the selection tabs updtMainTabs(); // Except for the "Navigate by type" tab, if at some intermediate level, go home if (!atHome) { if (mainTab_Q.checked) { goToNode(HomeNodeCode); } else { if (mainTab_N.checked) { updtNavigTree(activeComp) } else { clearNavigTree(); } } } // Update the URL updateURL(); } // *** The user clicked a "Navigate" tab: Names, Traits, Pictures, Flowchart function navigateModeTabClick( ){ console.log('navigateModeTabClick'); // Update the navigation tabs updtNavigTabs(); // Update the URL updateURL(); } // *** Clear the image and description function clearParamDescr( ) { // Description showGeneralDescr(''); // Image prodImgBox.src = 'img/dot.png'; } // *** See if a node or its children nodes pass filters, recursively function filterChildNodesRecurs( nodeCode, fltrSelDict, anyNodesThatPassA) { var nodePasses = true; var anyNodesThatPass = []; // First check this node var resultList = filterNode(nodeCode, fltrSelDict, anyNodesThatPassA, []); var resultFlg = resultList[0]; var nodeObj = resultList[1]; var remainingFiltrObj = resultList[2]; if (resultFlg == 1) { // This node passes all the filters. // It and all its children pass the filter nodePasses = true; anyNodesThatPass = [nodeCode]; anyNodesThatPassA = [nodeCode]; } if (resultFlg == 0) { // This node fails a filter. // This node fails a filter. Therefore, no nodes pass the filter nodePasses = false; } if (resultFlg == -1) { // This node passes some of the filters // Check its children var childrenDict = nodeObj.ls; nodePasses = false; // Assume that we reached a family node and the tree file is missing the definition for a filter if (childrenDict) { // This is not a family node for (var childNodeCode in childrenDict) { resultList = filterChildNodesRecurs(childNodeCode, remainingFiltrObj, anyNodesThatPassA); var childPasses = resultList[0]; var childNodesThatPass = resultList[1]; if (childPasses) { nodePasses = true; // All it takes is for one child to pass, and this node passes anyNodesThatPass = anyNodesThatPass.concat(childNodesThatPass); // Add this child's list of nodes that pass to the total list of nodes that pass } } } } return [nodePasses, anyNodesThatPass]; } // *** See if a node passes filters function filterNode( nodeCode, fltrSelDict, foundAttrs) { // Check for duplicate attribute definitions var nodeObj = nodesDict[nodeCode]; for (var attrName of foundAttrs) { if (attrName in nodeObj) { console.log('//**** EXTRA ATTRIBUTE **** ' + attrName + ' ' + nodeCode); } } // Make a deep clone of the filter object, so that we don't affect the original when removing its properties var localFltrSelDict = JSON.parse(JSON.stringify(fltrSelDict)); // Make a deep clone of the list of attributes that have already been defined, so that we don't affect the original when adding elements var localFoundAttrs = JSON.parse(JSON.stringify(foundAttrs)); var failsAFltr = false; for (var filterKey of Object.keys(localFltrSelDict)) { var filterVal = localFltrSelDict[filterKey]; if (filterKey in nodeObj) { var passesThisFilter = (nodeObj[filterKey].includes(filterVal)); if (passesThisFilter) { // Flag that we're done with this filter by removing its key // That way, this node's children won't look for it delete localFltrSelDict[filterKey]; // To check that an attribute is not duplicated, add it to the found object localFoundAttrs.push(filterKey); } else { failsAFltr = true; break; } } } // If this node passed all the filters, add it to the list var passesAllFltrs = (Object.keys(localFltrSelDict).length == 0); // Return: resultFlg localFltrSelDict localFoundAttrs // This node passes all the filters that it was given: 1 empty all the filters that it was given // This node passes some of the filters that it was given: -1 remaining filters the filters that it was given that were found // This node fails one the filters that it was given: 0 has some varies var resultFlg = passesAllFltrs? 1 : (failsAFltr)? 0 : -1; return [resultFlg, nodeObj, localFltrSelDict, localFoundAttrs]; } // *** The user selected a different topology function topologyChanged( ) { // var topoSelVal = document.querySelector('input[name=topoAttrBtns]:checked').value; // <<< topologySpan.innerHTML = TopologyNames[topoSelVal]; // Display the appropriate following selectors wiringSelBox.style.visibility = ('-dpwW'.includes(topoSelVal))? 'visible' : 'hidden'; noWiringSelSpan.style.visibility = (wiringSelBox.style.visibility == 'hidden')? 'visible' : 'hidden'; // panelDiv.style.visibility = ('-BdDwW'.includes(topoSelVal))? 'visible' : 'hidden'; } // *** The user selected a different structure function structureChanged( ) { var structSelVal = document.querySelector('input[name=structAttrBtns]:checked').value; //latchedDiv.style.visibility = ('-CI'.includes(structSelVal))? 'visible' : 'hidden'; // blindMateDiv.style.visibility = ('-CI'.includes(structSelVal))? 'visible' : 'hidden'; } // *** The user changed the settings function settingsChanged( ) { if (filterSwitch.checked) { setUpFilter(); // To change the units } if (dbEditBtnsDiv.style.display == 'block') { showDatabaseEdit(editCompMode); } } // ************ FILTERING ************ // *** Flag components for other nodes, so we won't look at them when listing components // Called before updating the filter or editing the database function flagCompForOtherNodes( ) { console.log('flagCompForOtherNodes'); var aCompInNode; for (var compName of Object.keys(allCompDataDict)) { // Assume that this component belongs to this node var forThisNode = true; aCompInNode = compName; // Prepare a list starting from the active node itself and continuing with its ancestors var nodeAncestorList = JSON.parse(JSON.stringify(activeNodeObj.ancestorList)); // Make a deep copy of the active node's ancestor list nodeAncestorList.push(activeNodeCode); // Add of the active node // If any of the tree branches for this component doesn't match a tree branch for the active node, this component doesn't belong to this node for (var taxLvlNo = 1; taxLvlNo < nodeAncestorList.length; taxLvlNo++) { if (nodeAncestorList[taxLvlNo] != allCompDataDict[compName][TaxonomyFieldNames[taxLvlNo]]) { forThisNode = false; break; } } // Flag whether this component belongs to this node, so we do or do not look at it allCompDataDict[compName].forThisNode = forThisNode; } // Return the name of the last component that we found i this node return aCompInNode; } // flagCompForOtherNodes // *** Show only the filter selections with active selectors function showSectWithActvSelectors( ) { console.log('showSectWithActvSelectors'); var filterSections = document.getElementsByClassName('filterSection'); // Do each filter section for (var filterSection of filterSections) { var filterContents = filterSection.children[0]; if (filterContents) { // The section specific to a class may be empty var tableRows = filterContents.rows; if (tableRows) { var inUse = false; for (var tableRow of tableRows) { if (tableRow.style.display == 'table-row') { inUse = true; break; } } // The table is the grandchild of the div for the whole section filterSection.parentNode.parentNode.style.display = inUse? 'block': 'none'; } } } } // *** The user changed the text in the component name filter function filterCompNameChanged( ) { console.log('filterCompNameChanged'); // Show the clear section button compManufFiltrClrBtn.style.visibility = 'visible'; // Show the component list showCompList(); // Update the URL updateURL(); } // *** The user opened a filter select function filterSelClicked( theSelect) { console.log('filterSelClicked'); /* There are 4 possibilities 1. The user didn't change the selection - Do nothing 2. Nothing is selected - Disable the items that would return 0 results 3. Previously selected, no other select was changed since then - Do nothing (keep the same unselected items) 4. Previously selected, another select was changed since then - Recalculate the items that would return 0 results */ var disabFltrItems = false; if (!theSelect.oldIndex || (theSelect.selectedIndex != theSelect.oldIndex)) { // The selection changed if (theSelect.selectedIndex == 0) { // No selection made yet // Flag that we want to disable the items that would return 0 results disabFltrItems = true; } else if (lastFltrSelect != theSelect) { // This select has a selection and another select was changed in the meanwhile // Remember the present selection var oldIndex = theSelect.selectedIndex; // Re-enable all the items for (var anOption of theSelect.options) { anOption.disabled = false; } // Select nothing theSelect.selectedIndex = 0; // Show the components list just to get a list of all possibilities showCompList(); // Restore the old selection theSelect.selectedIndex = oldIndex; // Flag that we want to disable the items that would return 0 results disabFltrItems = true; } } // Remember this selection so the next time we can see if it changed theSelect.oldIndex = theSelect.selectedIndex; // Store it in the select itself if (disabFltrItems) { // Disable the items that would return 0 results // Get this field's name from the ID of the row that contains this select var filterRow = theSelect.closest('tr'); fieldName = getFieldNameFromRow(filterRow); var dimsTolPct = Number(pitchTolSel.value); var fltrType = ''; //if ((fieldName == 'manuf_name') && (inputVal.includes(ANY_BRAND_STR))) {} // We'll compare just the first word in the company name if (fieldName == 'wire_size_mm2_min') { // We'll compare this value to the range for each component in the database fltrType = 'fltrWireSize'; } else if (fieldName.includes('_lst')) { // We'll compare this value to the value for each component in the database plus or minus the tolerance selected in the settings fltrType = 'fltrList'; } else if (compDbFieldDict[fieldName].type == 'float') { // We'll compare this value to the value for each component in the database plus or minus the tolerance selected in the settings fltrType = 'fltrToler'; } else { // We'll compare this value to the value for each component in the database and see if they are the same fltrType = 'fltrEqual'; } var filterCell = theSelect.closest('td'); var invertChkBox = filterCell.getElementsByClassName('invSelChkBox')[0]; var invertSel = invertChkBox.checked; // Check each option var noOfOptionsWithComp = 0; for (var anOption of theSelect.options) { switch (anOption.text) { case 'Select...': break; case AnySpecifiedStr: anOption.disabled = (noOfOptionsWithComp < 2); break; default: // Check each component that passes the filter until we find one that would pass the filter if the user selected this option var optionVal = anOption.value; var passesFilter = false; for (compName of fltrCompList) { compRecord = allCompDataDict[compName]; // Assume that this component passes the filter var compFieldVal = compRecord[fieldName]; var hasValue = false; if (compFieldVal == '') { continue; } else { switch (fltrType) { case 'fltrWireSize': // Wire size var compMinWireSize = compFieldVal / WireSizeTol; var compMaxWireSize = compRecord['wire_size_mm2_max'] * WireSizeTol; if (invertSel != ((compMinWireSize < optionVal) && (optionVal < compMaxWireSize))) { passesFilter = true; } break; case 'fltrList': // List var compRecStr = compFieldVal.toString(); var compLstItems = compRecStr.split(','); for (var compLstItem of compLstItems) { if (isNaN(compLstItem)) { // Range var rangeValues = compLstItem.split('-'); minValue = Number(rangeValues[0]); maxValue = Number(rangeValues[1]); if ((minValue <= optionVal) && (optionVal <= maxValue)) { hasValue = true; break; } } else { // Number if (Number(compLstItem) == optionVal) { hasValue = true; } } } if (invertSel != hasValue) { passesFilter = true; } break; case 'fltrToler': // Tolerance var compFieldVal = Number(compFieldVal); if (!isNaN(compFieldVal) && (compFieldVal != 0)) { // Valid value optionVal = Number(optionVal); var valueTol = optionVal * dimsTolPct / 100; minValue = optionVal - valueTol; maxValue = optionVal + valueTol; if (invertSel != ((minValue <= compFieldVal) && (compFieldVal <= maxValue))) { passesFilter = true; } } else { // 0 or not specified passesFilter = true; } break; case 'fltrEqual': // Equal if (isNumber(compFieldVal)) { // This field has a single numeric value hasValue = (Number(compFieldVal) == optionVal); } else if (compFieldVal.includes(',')) { // This field has a list of values for (var aValue of compFieldVal.split(',')) { if (aValue == optionVal) { hasValue = true; break; } } } else { // This field has a single string value var anyValLoc = optionVal.indexOf(AnyTypeStr); if (anyValLoc > 0) { // Options group, any item in the group var compGroupName = compFieldVal.split(':')[0]; var optionGroupName = optionVal.slice(0,anyValLoc); hasValue = (compGroupName == optionGroupName); } else { // Not an options group, or an options group but not any item in the group hasValue = (compFieldVal == optionVal); } } if (invertSel != hasValue) { passesFilter = true; } break; } // switch (fltrType) } // compFieldVal != '' if (passesFilter) { break; } } // For each component if (passesFilter) { noOfOptionsWithComp++; } anOption.disabled = !passesFilter; break; } // Switch option type } // For each option } // Disable items } // *** The user changed a filter select function filterSelChanged( theSelect) { console.log('filterSelChanged'); // Remember that this was the most recently used filter select lastFltrSelect = theSelect; // Show or hide the clear section button var filterSection = theSelect.closest('div').parentNode; var filterSelects = filtersDbEditBox.getElementsByClassName('filterSel'); var selectsAreClear = true; for (var filterSelect of filterSelects) { if (filterSelect.selectedIndex > 0) { selectsAreClear = false; break; } } var clearSectFilterBtn = filterSection.getElementsByClassName('clearSectFilterIcon')[0]; clearSectFilterBtn.style.visibility = selectsAreClear? 'hidden' : 'visible'; // Show the component list showCompList(); // Update the URL updateURL(); return filterSection; } // *** The user changed a filter invert checkbox function filterInvertChanged( ) { console.log('filterInvertChanged'); // Show the component list showCompList(); // Update the URL updateURL(); } // *** The user clicked the mode button in the component list function compListModeClick( ) { // showManufChkBx // Rotate to the next mode var compListModeBtnImg = compListModeBtn.src.split('/').pop(); var newSrc = ''; switch (compListModeBtnImg) { case 'plainlist.png': newSrc = 'bymanuf.png'; break; case 'bymanuf.png': newSrc = 'byclass.png'; break; case 'byclass.png': newSrc = 'plainlist.png'; break; } compListModeBtn.src = 'img/' + newSrc; // Update the component list showCompList(); } // *** The user clicked a manufacturer in the list of components function selectManuf( manufName) { manufFiltrSel.value = manufName; if (!filterSwitch.checked) { filterSwitch.checked = true; // Turn on the filter filterChkBox1.checked = true; // Open the Components/manufacturer box } filterSwitchClick(); } // *** Show the list of components that pass the filter and enable the selectors for the subclasses that have components that pass the filter // Called by node scripts whether or not the node also uses a database // Also called by the main window when the user changes a manufacturer or application choice function showCompList( ) { console.log('showCompList', arguments.callee.caller.name); var fieldName, invertSel, inputVal, inputGroupName, compName, subNodeCode, subNodeDict; var subNodesPassDict = {}; // Dictionary of subnodes that pass the filter var filterDicts = {}; // Set of 4 dictionaries, one for each type of filter // Clear the filter dictionaries filterDicts.fltrAnySpec = {}; filterDicts.fltrManuf = {}; filterDicts.fltrWireSize = {}; filterDicts.fltrList = {}; filterDicts.fltrToler = {}; filterDicts.fltrEqual = {}; // Prepare a dictionary for each subnode to store whether that subnode passes the filter if (!atFamily) { subNodeDict = activeNodeObj.ls; for (subNodeCode of Object.keys(subNodeDict)) { subNodesPassDict[subNodeCode] = false; } } if (filterSwitch.checked) { // Don't do it if we're using this control in the database edit mode // PART A: Get the selectors' values and prepare a filter object // Do each row in the filter panel var filterRows = document.getElementsByClassName('filterRow'); for (var filterRow of filterRows) { if (filterRow.style.display != 'none') { // Get this field's name from the ID of the row in the filter table fieldName = getFieldNameFromRow(filterRow); // Skip the component name because it's not in the connector record (it's the key for the record) if (fieldName == 'comp_name') { continue; } // This is a text field or a select var inputElem; var selectorCell = filterRow.cells[1]; // The second cell has the selector var tdElements = selectorCell.childNodes; // The second cell has the selector invertSel = false; for (inputElem of tdElements) { // Skip the text nodes that occur if the HTML has anything between the "TD" tag and the "SELECT" tag // Invert checkbox if (inputElem.nodeName == 'LABEL') { var invSelChkBox = inputElem.childNodes[0]; invertSel = invSelChkBox.checked; } // Select if (inputElem.nodeName == 'SELECT') { // Get the value in the text field or select and place it in one of three dictionaries, depending on what we'll do with that value var theDict = ''; inputVal = inputElem.value; if (inputVal == 'AnySpec') { filterDicts.fltrAnySpec[fieldName] = invertSel; } else if (inputVal != '') { if (fieldName == 'manuf_name') { var anyBrand = false; if (inputVal.includes(ANY_BRAND_STR)) { anyBrand = true; inputVal = inputVal.split(' ')[0]; // We'll compare just the first word in the company name } filterDicts.fltrManuf = {'inputVal':inputVal, 'invertSel':invertSel, 'anyBrand': anyBrand}; } else { if (fieldName == 'wire_size_mm2_min') { // We'll compare this value to the range for each component in the database theDict = 'fltrWireSize'; } else if (fieldName.includes('_lst')) { // We'll compare this value to the value for each component in the database plus or minus the tolerance selected in the settings theDict = 'fltrList'; } else if (compDbFieldDict[fieldName].type == 'float') { // We'll compare this value to the value for each component in the database plus or minus the tolerance selected in the settings theDict = 'fltrToler'; } else { // We'll compare this value to the value for each component in the database and see if they are the same theDict = 'fltrEqual'; } filterDicts[theDict][fieldName] = {'inputVal':inputVal, 'invertSel':invertSel}; } } } } } } // PART B: Apply the filters (if any) to each component var subNodesThatPassFilter = []; var subNodeStageName = TaxonomyFieldNames[activeNodeObj.ancestorList.length +1]; var minValue, maxValue, hasValue; for (compName of Object.keys(allCompDataDict)) { // Assume that this component passes the filter compRecord = allCompDataDict[compName]; var passesFilter = true; // Skip records for other nodes if (!compRecord.forThisNode) { continue; } // Component name filter if (passesFilter) { var searchStr = filterCompNameTxtFld.value; if (searchStr != '') { var passesNameFilter = false; var aka = compRecord.aka_; if ((typeof searchStr) == 'string') { searchStr = searchStr.toLowerCase(); if ((typeof compName) == 'string') { if (compName.toLowerCase().includes(searchStr)) {passesNameFilter = true;} } if ((typeof aka) == 'string') { if (aka.toLowerCase().includes(searchStr)) {passesNameFilter = true;} } } if ((typeof searchStr) == 'number') { if ((compName.includes(searchStr)) || (aka.includes(searchStr))) {passesNameFilter = true;} } // If none found, try the manufacturers list instead if (!passesNameFilter) { var otherManufListStr = compRecord.manuf_list; // More manufacturers for industry-standard components if (otherManufListStr != '') { var otherManufList = otherManufListStr.split(CloseSquareBracket); for (var otherManufNameData of otherManufList) { if (otherManufNameData != '') { var otherManufPartListParts = otherManufNameData.split(OpenSquareBracket); if (otherManufPartListParts.length == 2) { var otherManufPartListStr = otherManufPartListParts[1]; if (otherManufPartListStr != '') { otherManufPartListStr = otherManufPartListStr.replace(',',' '); var otherManufPartList = otherManufPartListStr.split(' '); for (var otherManufPart of otherManufPartList) { if ((typeof otherManufPart) == 'string') { otherManufPart = otherManufPart.toLowerCase(); } if (otherManufPart.includes(searchStr)) { passesNameFilter = true; break; } } } } else { console.log('*** NO PART NUMBER LIST IN MANUFACTURER LIST for ', compName) } if (passesNameFilter) { break; } } } } } if (!passesNameFilter) {passesFilter = false;} } } // No value specified if (passesFilter) { for (fieldName of Object.keys(filterDicts.fltrAnySpec)) { invertSel = filterDicts.fltrAnySpec[fieldName]; // Filter-out all the items without a value if (invertSel !=(compRecord[fieldName] == '')) { passesFilter = false; break; } } } // Manufacturer name if (passesFilter) { if (Object.keys(filterDicts.fltrManuf).length > 0) { var dbManufName = compRecord.manuf_name; if (filterDicts.fltrManuf.anyBrand) { // Any brand: match just the first word of the manufacturer's name dbManufName = dbManufName.split(' ')[0]; } // Manufacturer name in the manuf_name field var manufFound = (filterDicts.fltrManuf.inputVal == dbManufName); // Manufacturer name in the manuf_list field if (!manufFound) { var otherManufListStr = compRecord.manuf_list; // More manufacturers for industry-standard components if (otherManufListStr != '') { var otherManufList = otherManufListStr.split(CloseSquareBracket); for (var otherManufNameData of otherManufList) { var otherManufName = otherManufNameData.split(OpenSquareBracket)[0]; if (filterDicts.fltrManuf.anyBrand) { // Any brand: match just the first word of the manufacturer's name otherManufName = otherManufName.split(' ')[0]; } if (otherManufName == filterDicts.fltrManuf.inputVal) { manufFound = true; break; } } } } passesFilter = (filterDicts.fltrManuf.invertSel != manufFound); } } // Wire size if (passesFilter) { if (Object.keys(filterDicts.fltrWireSize).length > 0) { inputVal = filterDicts.fltrWireSize['wire_size_mm2_min'].inputVal; invertSel = filterDicts.fltrWireSize['wire_size_mm2_min'].invertSel; var compMinWireSize = compRecord['wire_size_mm2_min'] / WireSizeTol; var compMaxWireSize = compRecord['wire_size_mm2_max'] * WireSizeTol; passesFilter = (invertSel != ((compMinWireSize < inputVal) || (inputVal > compMaxWireSize))); } } // List if (passesFilter) { for (fieldName of Object.keys(filterDicts.fltrList)) { inputVal = filterDicts.fltrList[fieldName].inputVal; invertSel = filterDicts.fltrList[fieldName].invertSel; var compRecStr = compRecord[fieldName].toString(); var compLstItems = compRecStr.split(','); hasValue = false; for (var compLstItem of compLstItems) { if (isNaN(compLstItem)) { // Range var rangeValues = compLstItem.split('-'); minValue = Number(rangeValues[0]); maxValue = Number(rangeValues[1]); if ((minValue <= inputVal) && (inputVal <= maxValue)) { hasValue = true; break; } } else { // Number if (Number(compLstItem) == inputVal) { hasValue = true; break; } } } if (invertSel == hasValue) { passesFilter = false; break; } } } // Tolerance if (passesFilter) { var dimsTolPct = Number(pitchTolSel.value); for (fieldName of Object.keys(filterDicts.fltrToler)) { var compValue = Number(compRecord[fieldName]); if (!isNaN(compValue) && (compValue != 0)) { // Valid value inputVal = Number(filterDicts.fltrToler[fieldName].inputVal); invertSel = filterDicts.fltrToler[fieldName].invertSel; var valueTol = inputVal * dimsTolPct / 100; minValue = inputVal - valueTol; maxValue = inputVal + valueTol; if (invertSel != ((compValue < minValue) || (compValue > maxValue))) { passesFilter = false; break; } } else { // 0 or not specified passesFilter = false; break; } } } // Equal if (passesFilter) { for (fieldName of Object.keys(filterDicts.fltrEqual)) { inputVal = filterDicts.fltrEqual[fieldName].inputVal; inputGroupName = ''; var anyValLoc = inputVal.indexOf(AnyTypeStr); if (anyValLoc > 0) { inputGroupName = inputVal.slice(0,anyValLoc); } invertSel = filterDicts.fltrEqual[fieldName].invertSel; var compFieldVal = compRecord[fieldName]; hasValue = false; if (isNumber(compFieldVal)) { // This field has a single numeric value hasValue = (Number(compFieldVal) == inputVal); } else if (compFieldVal.includes(',')) { // This field has a list of values var compFieldVals = compFieldVal.split(',') for (var compFieldVal of compFieldVals) { if (inputGroupName == '') { // Normal hasValue = (compFieldVal == inputVal); } else { // Any value within this group var compGroupName = compFieldVal.split(':')[0]; hasValue = (compGroupName == inputGroupName); } if (hasValue) { break; } } } else { // This field has a single string value if (inputGroupName != '') { // Options group, any item in the group var compGroupName = compFieldVal.split(':')[0]; hasValue = (compGroupName == inputGroupName); } else { // Not an options group, or an options group but not any item in the group hasValue = (compFieldVal == inputVal); } } if (invertSel == hasValue) { passesFilter = false; break; } } } // Flag if this component passes the filter compRecord[CompPassesFilterX] = passesFilter; // Flag that its subnode passes the filter if (!atFamily && passesFilter) { var compSubNodeCode = compRecord[subNodeStageName]; subNodesPassDict[compSubNodeCode] = true; } } } // PART C: Show the the list of components or note that there are no results // Assume no results noOfCompSpan.innerHTML = ''; componentCopyList = ''; // Prepare lists of components that pass the filter for the right pane and that can be copied to the clipboard var compListModeBtnImg = compListModeBtn.src.split('/').pop(); fltrCompList = []; // List of components that pass the filter var fltrManufDict = {}; // Dictionary of manufacturers that have components that pass the filter var fltrFamilyDict = {}; // Dictionary of families that have components that pass the filter var compRecord = {}; var familyName; componentCopyList = '* MANUF. - SERIES\n'; // First line of the list of components that can be copied to the clipboard var compList = Object.keys(allCompDataDict); for (compName of compList) { // For each component compRecord = allCompDataDict[compName]; if ((compRecord.forThisNode) && (!filterSwitch.checked || compRecord[CompPassesFilterX])) { // Component list fltrCompList.push(compName); // Add an item to the list that can be copied to the clipboard var manufName = compRecord.manuf_name; var manufLink = manufName; var manufURL = compRecord.manuf_link; if ((manufURL != '') && (typeof manufURL == 'string') && (manufURL.indexOf('http') === 0)) { manufLink = OpenSquareBracket + manufName + '](' + manufURL + CloseParen; // Social media link, not HTML link } var seriesLink = compName; var pdfURL = compRecord.pdf_link; if ((pdfURL != '') && (typeof manufURL == 'string') && (pdfURL.indexOf('http') == 0)) { seriesLink = OpenSquareBracket + seriesLink + '](' + pdfURL + ') (pdf)'; // Social media link, not HTML link } var vendorLink = ''; var vendorURL = compRecord.vendor_link; if ((vendorURL != '') && (typeof manufURL == 'string') && (vendorURL.indexOf('http') == 0)) { var dummlyLinkTag = document.createElement('a'); dummlyLinkTag.href = vendorURL; var urlDomain = dummlyLinkTag.hostname; var urlDomainParts = urlDomain.split('.'); var vendorName = urlDomainParts[urlDomainParts.length -2]; // e.g, from www.digikey.com, get the 'digikey' part vendorLink = OpenSquareBracket + vendorName + '](' + vendorURL + CloseParen; // Social media link, not HTML link } componentCopyList += '* ' + [manufLink, seriesLink, vendorLink].filter(Boolean).join(' - ') + '\n'; // .filter(Boolean) removes blank strings // Manufacturer list if ((compListModeBtnImg == 'bymanuf.png') && (manufName !== '')) { // If there's a manufacturer if (!(manufName in fltrManufDict)) { // If we haven't done this manufacturer yet fltrManufDict[manufName] = []; // Create a list of components for that manufacturer } fltrManufDict[manufName].push(compName); } // Family list if (compListModeBtnImg == 'byclass.png') { familyName = compRecord.family_; if (!(familyName in fltrFamilyDict)) { // If we haven't done this family yet fltrFamilyDict[familyName] = []; // Create a list of components for that family } fltrFamilyDict[familyName].push(compName); } } } // Add an autocomplete list to the component name filter addAutocomplete(filterCompNameTxtFld, fltrCompList); // Show the the number of items that passes the filter noOfCompSpan.innerHTML = fltrCompList.length; showMoreBtnQtySpan.innerHTML = fltrCompList.length; if (fltrCompList.length == 0) { // No results componentListBox.innerHTML = componentCopyList = 'Nothing matches the filter'; activeComp = ''; } else { // There are results // Note: the right way to create a select is to create individual DOM elements. // However, select.appendChild takes a long time. div.innerHTML takes far less time. // Yet, it is followed by a "Parse HTML" action that takes some time, though still less than manipulating each element in the DOM compListHTML = ''; var compListObj = {}; compListObj.noOfListItems = 0; compListObj.shortCompListHTML = ''; var fltrCompName; var listItemHTML = ''; switch (compListModeBtnImg) { case 'plainlist.png': // Plain list fltrCompList = sortListNoCase(fltrCompList); for (fltrCompName of fltrCompList) { // For each component listItemHTML = '
' + fltrCompName + '
'; addCompListItem(compListObj, listItemHTML); } break; case 'bymanuf.png': // List by manufacturer // Make a sorted list of manufacturers whose components pass the filter var fltrManufList = sortListNoCase(Object.keys(fltrManufDict)); // Do each manufacturer for (var fltrManufName of fltrManufList) { // Manufacturer name listItemHTML = '
' + fltrManufName + '
'; addCompListItem(compListObj, listItemHTML); // List of components from this manufacturer var fltrCompListForManuf = fltrManufDict[fltrManufName]; for (fltrCompName of fltrCompListForManuf) { listItemHTML = '
' + fltrCompName + '
'; addCompListItem(compListObj, listItemHTML); } } break; case 'byclass.png': // List by component family // Make a sorted list of families whose components pass the filter var fltrFamilyList = sortListNoCase(Object.keys(fltrFamilyDict)); // Do each family for (familyName of fltrFamilyList) { // Family name var familyNameStr = familyName.replaceAll('_', ' '); familyNameStr = familyNameStr[0].toUpperCase() + familyNameStr.slice(1); // Capitalize the first letter listItemHTML = '
' + familyNameStr + '
'; addCompListItem(compListObj, listItemHTML); // List of components from this family var compListForFamily = fltrFamilyDict[familyName]; for (fltrCompName of compListForFamily) { listItemHTML = '
' + fltrCompName + '
'; addCompListItem(compListObj, listItemHTML); } } break; } // Show the short list of components componentListBox.innerHTML = compListObj.shortCompListHTML; compListMoreBtn.style.visibility = (fltrCompList.length > MaxCompListItems)? 'visible' : 'hidden'; } // PART D: Enable or disable the selectors for subnodes that contain components that pass the filter if (!atFamily) { // At the family stage, the active node has no subnodes var subNodeList = Object.keys(subNodesPassDict); for (var selectorNo in subNodeList) { subNodeCode = subNodeList[selectorNo]; if(subNodeDict.hasOwnProperty(subNodeCode)) { var selectorEnabled = subNodesPassDict[subNodeCode] || !filterSwitch.checked; // Enable or disable this subnode selector if (navigTab_F.checked) { if (typeof flowchartObj != 'undefined') { // Flowchart selectorID = flowchartObj.contentDocument.getElementById(subNodeCode); // <<< flowchartObj is not defined if (selectorID) { selectorID.style.pointerEvents = selectorEnabled? 'auto' : 'none'; // Disable it or enable it // Show it either normal or grayed and thinner var innerRect = selectorID.children[0];// The rounded rectangle in a flowchart innerRect.style.fill = selectorEnabled? 'white' : 'lightgray'; innerRect.style.strokeWidth = selectorEnabled? '4' : '1'; } } else { console.log('!!!! flowchartObj is not defined !!!!!!',subNodeCode); } } else { // Menu item or picture var preFix = (navigTab_P.checked)? 'selPict' : ((navigTab_N.checked)? 'selNamesBtn' : 'selTraitsBtn'); selectorID = document.getElementById(preFix + selectorNo); // The name of the selector if (selectorID) { selectorID.style.pointerEvents = selectorEnabled? 'auto' : 'none'; // Disable it or enable it // Show it normal or dimmed if (selectorEnabled) { selectorID.classList.remove('selectorDisabled'); } else { selectorID.classList.add('selectorDisabled'); } } } } } } } // showCompList // *** Add a component to the component list and count it function addCompListItem( compListObj, listItemHTML) { compListHTML += listItemHTML; if (compListObj.noOfListItems < MaxCompListItems) { compListObj.shortCompListHTML += listItemHTML; } compListObj.noOfListItems++; } // *** Get a list of values for a given field function getDbValueList( fieldName) { var hasBlank = false; var allNumbers = true, allStrings = true; var valueList = []; for (var compName of Object.keys(allCompDataDict)) { // For each component // Skip records for other nodes if (!allCompDataDict[compName].forThisNode) { continue; } // Flag if the value is blank, otherwise add it to a list of values var thisValue = allCompDataDict[compName][fieldName]; if (thisValue === '') { hasBlank = true; } else { if (typeof thisValue == 'number') { allStrings = false; // Number thisValue = Number(thisValue); if (!valueList.includes(thisValue)) { valueList.push(thisValue); } } else { allNumbers = false; if (typeof thisValue == 'string') { // Text if (thisValue.includes(',')) { var thisValueList = thisValue.split(','); for (var listItem of thisValueList) { listItem = listItem.trim(); if (!valueList.includes(listItem)) { valueList.push(listItem); } } } else { thisValue = thisValue.trim(); if (!valueList.includes(thisValue)) { valueList.push(thisValue); } } } } } } // If a mixture of strings and numbers, convert to all strings if (!(allStrings || allNumbers)) { valueList = valueList.map(String); } // Sort the values if (valueList.length > 1) { if (allNumbers) { // Numbers valueList = valueList.sort((a, b) => (a - b)); // Numeric sort (The built-in sort function is for strings, not numbers) // Remove the first 0, unless a boolean field if ((compDbFieldDict[fieldName].type != 'tinyint') || (compDbFieldDict[fieldName].length != 1)) { if (valueList[0] == 0) { valueList = valueList.slice(1); hasBlank = true; } } } else { // Strings var fieldDefValues = compDbFieldDict[fieldName].values; if (testShowAllFilters) { // For testing valueList = fieldDefValues; } else if (fieldDefValues.length > 1) { // Reorder the values as they appear in the database field definition var orderedValueList = []; for (var fieldDefValue of fieldDefValues) { if (valueList.includes(fieldDefValue)) {orderedValueList.push(fieldDefValue);} } valueList = orderedValueList; } else { // Case insensitive sort valueList = sortListNoCase(valueList); } } } // See if there are at least two values in this field // hasBlank length // no values: no false 0 // blank only; no true 0 // one non-blank value: no false 1 // two non-blank values: yes false 2 // blank + one non-blank value: yes true 1 var hasMultipleValues = (valueList.length >= (hasBlank? 1 : 2)); return { 'valueList': valueList, 'hasMultipleValues': hasMultipleValues, 'hasEmptyFields': hasBlank }; } // ************ DATABASE MODIFICATION ACTIONS ************ /* For wire splices appl_wiring appl_industry appl_other comp_name aka_ manuf_name manuf_link pdf_link vendor_link obsolete_bool notes_ pictures_ structure_ tot_circuits_min / tot_circuits_max wire_cbl_termin wire_cbl_release wire_size_mm2_min / wire_size_mm2_max entries_p_ckt_min wir_cbl_entry_dir housing_color max_current max_voltage multi_wire_per_entry tap_ wires_min / wires_max */ // *** Compile the filter ranges from the components table function compileDB( ) { console.log('compileDB'); var goAhead = confirm('Long process.\nCompiling the database\'s components table into the filter ranges and manufacurer tables takes a long time.\nProceed?'); if (goAhead) { // Filter ranges and manufacturers var dbURL = 'cgi-bin/compileFilterValues.py'; var xhr = new XMLHttpRequest(); xhr.open("GET",dbURL,true); xhr.send(); // Later, after the data has been compiled, execute this function xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status === 200) { alert(xhr.responseText); } }; } } // *** Show the controls to create or edit a database item function showDatabaseEdit( editMode) { // 'add', 'edit', or 'dup' console.log('showDatabaseEdit', editMode); // Save the mode globally in case the user changes the settings editCompMode = editMode; // Show the controls for database editing and hide those for fitering // Hide the home filter-off box homeFltrOffBox.style.display = 'none'; // Hide the filter selectors and controls filterColTitleSet.style.display = 'none'; dbEditColTitleSet.style.display = 'block'; // Show the database edit controls filtersDbEditBox.style.display = 'block'; closeFiltersBtn.style.display = 'block'; dbEditLabel.innerHTML = DbEditTtles[editMode]; dbEditBtnsDiv.style.display = 'block'; for (var seeAllImg of document.getElementsByClassName('seeAll')) { seeAllImg.style.display = 'inline'; } // Hide the cells for the filter and show the cells for the database entry // Do it in this order, so that cells that are for both will be visible for (var filterCell of document.getElementsByClassName('filterCell')) { filterCell.style.display = 'none'; } var dbEntryCell; for (dbEntryCell of document.getElementsByClassName('dbEntryCell')) { dbEntryCell.style.display = 'table-cell'; } if (editMode == 'add') { filterChkBox1.checked = true;} // Flag records for other nodes, so we won't look at them when compiling values for each field var sampleComp = flagCompForOtherNodes(); // Returns an example of a component in this node if ((activeComp != '') && (editMode != 'add')) { sampleComp = activeComp; } // Do each row of the database edit form for (dbEntryCell of document.getElementsByClassName('dbEntryCell')) { // Get the field name from the row id var filterRow = dbEntryCell.closest('tr'); // Get the row this cell is in // Get this field's name from the ID of the row in the filter table var fieldName = getFieldNameFromRow(filterRow); // See if we want to show all the controls in this section var rowsTable = filterRow.closest('table'); var filterSection = rowsTable.parentNode; var showAllSectControls = filterSection.showAll; // Get a list of the values for this field from the filter ranges database var valueObj = getDbValueList(fieldName); var valueList = valueObj.valueList; var hasMultipleValues = valueObj.hasMultipleValues; // Set if 2 or more non-blank values, or a blank value plus one or more non-blank values // Show or hide this parameter var showParam = (hasMultipleValues || showAllSectControls || (fieldName == 'comp_name')); filterRow.style.display = showParam? 'table-row' : 'none'; if (showParam) { // Get the default value from the sample component: if editing, the active component; otherwise any component in the node var defaultVal = ''; if ((activeComp != '') && (editMode != 'add')) { defaultVal = allCompDataDict[activeComp][fieldName]; if (defaultVal == 0) {defaultVal = ''; } } var manufName = '', modelList = ''; // Handle special cases switch(fieldName) { case 'comp_name': switch (editMode) { case 'add': defaultVal = ''; break; case 'dup': defaultVal = activeComp + ' copy'; break; case 'edit': defaultVal = activeComp; break; } break; case 'manuf_name': // Industry standard checkbox seriesEditIndStdChkBx.checked = (defaultVal == '_Industry standard_'); seriesEditManufChanged(); break; case 'manuf_list': // Manufacturer list // Show a table with two columns, one for the manufacturer name and one for their part numbers // Convert the default list to a dictionary with one item per manufacturer if (defaultVal && (defaultVal != '')) { var manufListDict = {}; // Decode from the proprietary encoding // The value is of the form manuf[model,model]manuf[model,model]manuf[model,model] for (var anItem of defaultVal.split(String.fromCharCode(0x5D))) { // Closing square bracket if (anItem != '') { var itemParts = anItem.split(String.fromCharCode(0x5B)); // Opening square bracket manufName = itemParts[0]; modelList = itemParts[1]; manufListDict[manufName] = modelList; } } // Sort by manufacturer name var manufNamesList = Object.keys(manufListDict); manufNamesList.sort(); // Fill the list for(var manufNo=0; manufNo < ManufListLength; manufNo++){ if (manufNo < manufNamesList.length) { manufName = manufNamesList[manufNo]; modelList = manufListDict[manufName]; } document.getElementById('manufListName' + manufNo).value = manufName; document.getElementById('manufListSeries' + manufNo).value = modelList; } } break; case 'wire_size_mm2_min': // Wire size // Show the units wireSizeUnitsSpan.innerHTML = wireSizeUnitsSel.value; // Fill the max value field var maxDefaultVal = ''; if ((activeComp != '') && (editMode != 'add')) { maxDefaultVal = allCompDataDict[activeComp].wire_size_mm2_max; if (maxDefaultVal == 0) {maxDefaultVal = '';} } break; } // Multi selection lists added to text fields whose database type is "set" // Prefill the text field (if any) with the default value and give it a list of possible values that is shown if the user enters the control // Look for a multiSelList text field // Find the text field var dbEntryTxtFld = dbEntryCell.querySelector('input'); if (dbEntryTxtFld) { // Set its default value dbEntryTxtFld.value = defaultVal; var txtFieldFunction = dbEntryTxtFld.parentElement.className; switch (txtFieldFunction) { case 'multiSelList': // Database items of "set" type get a multiSelList function // Add its autocomplete list // Normally they are the values already present in the component database // But, if showing every control, then they are all the possible values in the set defined by this field if (showAllSectControls) { valueList = compDbFieldDict[fieldName].values;} dbEntryTxtFld.valueList = valueList; break; case 'autocomplete': // Database items of "text" type get an autocomplete function addAutocomplete(dbEntryTxtFld,valueList); break; } } } else if ((valueList.length == 1) && !hasMultipleValues) { // This parameter is not shown but the components in this node have a single value // Save that value in the row element, so we can retrieve it when preparing the record to save filterRow.defaultVal = valueList[0]; } } // For each component // Show either the manufacturer name or the list of manufacturers seriesEditIndStdChkBx.checked = (manuf_nameTxtFld.value == '_Industry standard_'); seriesEditManufChanged(); // Show only the selections with active selectors showSectWithActvSelectors(); } // showDatabaseEdit // *** The user clicked the button to show all the controls in the database edit block function showAllDbEditCtrls( ) { console.log('showAllDbEditCtrls'); showAllDbEditControls = !showAllDbEditControls; var filterSections = document.getElementsByClassName('filterSection'); for (var filterSection of filterSections) { filterSection.showAll = showAllDbEditControls; } showDatabaseEdit(editCompMode); } // *** The user clicked the button to show all the controls in a section of the database edit block function showAllDbEditSectCtrls( filterSectionId) { console.log('showAllDbEditSectCtrls', filterSectionId); var filterSection = document.getElementById(filterSectionId); if (filterSection.showAll) { filterSectionId = false; } // Stop showing all the controls filterSection.showAll = !filterSection.showAll; // Toggle showing the controls in this section showDatabaseEdit(editCompMode); } // *** The user changed the manufacturer type selection function seriesEditManufChanged( ) { console.log('seriesEditManufChanged', seriesEditIndStdChkBx.checked); if (seriesEditIndStdChkBx.checked) { manuf_nameTxtFld.value = '_Industry standard_'; manuf_listRow.style.display = 'table-row'; manuf_nameDiv.style.display = 'none'; manuf_nameTxtFld.setAttribute('readonly', true); } else { manuf_listRow.style.display = 'none'; manuf_nameDiv.style.display = 'block'; manuf_nameTxtFld.removeAttribute('readonly'); } } // *** The user completed or canceled editing a component function editCompClose( saveClick) { console.log('editCompClose', saveClick); var errorMsg = ''; var fieldName, manufName, txtBoxVal, minValue, maxValue, filterRow, dbEntryCell; if(saveClick) { // The user clicked the "Save" button // Gather the entries var compRecord = {}; var fieldNames = Object.keys(compDbFieldDict); // Save the classification fields for (var taxLvlNo = ClassX; taxLvlNo < FamilyX; taxLvlNo++) { fieldName = TaxonomyFieldNames[taxLvlNo]; compRecord[fieldName] = activeNodeObj.ancestorList[taxLvlNo -0]; } compRecord.family_ = activeNodeCode; // Component name var compName = comp_nameTxtFld.value; if (editCompMode != 'edit') { if (Object.keys(allCompDataDict).includes(compName)) { errorMsg = compName + ' already exists'; } } if (compName == '') { errorMsg = 'The component name cannot be blank'; } // Do each active row for (dbEntryCell of document.getElementsByClassName('dbEntryCell')) { filterRow = dbEntryCell.closest('tr'); // Get the row this cell is in // Get this field's name from the ID of the row in the filter table fieldName = getFieldNameFromRow(filterRow); if (filterRow.style.display == 'none') { // This parameter is not shown but the components in this node have a single value // Get that value from the row element and place it in the connector record to save var defaultVal = filterRow.defaultVal; if (defaultVal) { compRecord[fieldName] = defaultVal; } } else { // This parameter is shown // Get the value from its controls // Find the value in the text box (if any) var dbEditTxtBox = dbEntryCell.querySelector('input[type="text"]'); txtBoxVal = ''; if (dbEditTxtBox) { txtBoxVal = dbEditTxtBox.value; } if (fieldName == 'comp_name') { // Compoment name: do not store it in a field because it's the index } else if (fieldName == 'manuf_list') { // Manufacturers list var manufList = {}; if (seriesEditIndStdChkBx.checked) { for(var manufNo=0; manufNo < ManufListLength; manufNo++){ manufName = document.getElementById('manufListName' + manufNo).value; if (manufName == '') { continue; } var modelList = document.getElementById('manufListSeries' + manufNo).value; if (manufName in manufList) { manufList[manufName] = manufList[manufName] + ',' + modelList; } else { manufList[manufName] = modelList; } } } // Encode in a proprietary way (otherwise, when stringifyng to JSON, it introduces double quotes that break the URL) // The value is of the form manuf[model,model]manuf[model,model]manuf[model,model] var manufListStr = ''; for (manufName of Object.keys(manufList)) { manufListStr += manufName + OpenSquareBracket + manufList[manufName] + CloseSquareBracket; } compRecord[fieldName] = manufListStr; } else if (fieldName == 'wire_size_mm2_min') { // Wire size minValue = txtBoxVal; maxValue = wire_size_mm2_maxTxtFld.value; if ((minValue != '') && (maxValue != '') && (minValue > maxValue)) { errorMsg = 'Wire size range error'; } else { compRecord.wire_size_mm2_min = minValue; compRecord.wire_size_mm2_max = maxValue; } } else if (fieldName.includes('_link')) { // URL compRecord[fieldName] = txtBoxVal; if (txtBoxVal != '') { try { new URL(txtBoxVal); // If invalid URL, this fails } catch (e) { errorMsg = 'The value in ' + fieldName + ' is not a valid URL'; } } } else if (fieldName.includes('_bool')) { // Checkbox var dbEditChkBox = dbEntryCell.querySelector('input[type="checkbox"]'); compRecord[fieldName] = dbEditChkBox.checked? 1 : 0; } else { // This parameter is visible switch(compDbFieldDict[fieldName].type) { case 'int': case 'tinyint': case 'float': // Number if ((txtBoxVal != '') && (!isNumber(txtBoxVal))) { errorMsg = 'The value in ' + fieldName + ' is not a number'; } else { compRecord[fieldName] = Number(txtBoxVal); } break; case 'text': case 'set': case 'varchar': compRecord[fieldName] = txtBoxVal; break; } } } // Visible row } // Each row if (errorMsg == '') { // Update the local data if (editCompMode == 'edit') { // Editing the active component // Uptate the field for (fieldName of Object.keys(compRecord)) { // Only does the fields that are editable. All other field remain the same allCompDataDict[activeComp][fieldName] = compRecord[fieldName]; } } else { // Adding a new component activeComp = compName; // Add a new record to the component dictionary allCompDataDict[compName] = {}; for (fieldName of Object.keys(compDbFieldDict)) { // Do all the fields if (compRecord[fieldName]) { // The component name is not (yet) in this record, so we don't do it allCompDataDict[activeComp][fieldName] = compRecord[fieldName]; } } } // Save the data to the database compRecord.comp_name = compName; // Add the component name into the dictionary var jsonTableData = JSON.stringify(compRecord); // Convert the dictionary to a JSON string jsonTableData = jsonTableData.replace('&','%26'); //jsonTableData = encodeURIComponent(jsonTableData); // Encode special characters var updateCompName = (editCompMode == 'edit')? compName : '-'; // '-' for a new row; component name to update a row var dbURL = 'cgi-bin/saveTableData.py?compName=' + updateCompName + '&tableData=' + jsonTableData; var xmlHttpRequest = new XMLHttpRequest(); xmlHttpRequest.open("GET",dbURL,true); xmlHttpRequest.send(); // Later, after the database has been updated, execute this function xmlHttpRequest.onreadystatechange = function() { if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status === 200) { console.log('done saving DB', xmlHttpRequest.responseText); } }; // Close the dialog and show the updated component goToSelectedComp(); } else { // There is an error alert(errorMsg); } } // If save if (errorMsg == '') { // Close the box filtersDbEditBox.style.display = 'none'; // Hide the elements for editing and show the ones for the filter // Do it in this order, so that cells that are for both will be visible // Hide the database edit selectors and controls dbEditColTitleSet.style.display = 'none'; dbEditBtnsDiv.style.display = 'none'; // Hide the seeAll icons for (var seeAllImg of document.getElementsByClassName('seeAll')) { seeAllImg.style.display = 'none'; } // Hide the rows that are only used for editing for (var dbEditOnlyRow of document.getElementsByClassName('dbEditOnlyRow')) { dbEditOnlyRow.style.display = 'none'; } // Hide the table cells for editing for (dbEntryCell of document.getElementsByClassName('dbEntryCell')) { dbEntryCell.style.display = 'none'; } // Restore the filter controls and selectors, show the cells for the filter if (activeComp == '') { filterColTitleSet.style.display = 'inline'; // Show the table cells for filtering for (var filterCell of document.getElementsByClassName('filterCell')) { filterCell.style.display = 'table-cell'; } } // Show or hide the filter showHideFilter(); } } // editCompClose // *** Show or hide the filter function showHideFilter( ) { filtersDbEditBox.style.display = filterSwitch.checked? 'block' : 'none'; closeFiltersBtn.style.display = filterSwitch.checked? 'block' : 'none'; homeFltrOffBox.style.display = (filterSwitch.checked || !atHome)? 'none' : 'block'; } // *** The user wants to delete the selected component function deleteCompClick( ) { console.log('deleteCompClick'); var delSeriesConfirm = confirm('Are you sure you want to delete the ' + activeComp + ' component?'); if (delSeriesConfirm) { // Delete the component var dbURL = 'cgi-bin/delComponent.py?compName=' + activeComp; var xmlHttpRequest = new XMLHttpRequest(); xmlHttpRequest.open("GET",dbURL,true); xmlHttpRequest.activeComp = activeComp; // Save the name of the component in the request object because it's available in the onreadystatechange function xmlHttpRequest.send(); // Later, after the database has been updated, execute this function xmlHttpRequest.onreadystatechange = function() { if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200) { console.log((xmlHttpRequest.activeComp + ' deleted'), xmlHttpRequest.responseText); } }; // Remove it from the local storage delete allCompDataDict[activeComp]; activeComp = ''; // Go to the family goToNode(activeNodeCode); } } // *** Add a dialog to a text field that list autocomplete suggestions function addAutocomplete( txtFld, autoComplList) { // The text field; an array of autocompleted values autoComplList = autoComplList.sort(); var autoComplActiveItem; // Add a listener to the text field for when the user starts typing into it txtFld.addEventListener('input', function(evnt) { var val = this.value; if (!val) { return false;} autoComplActiveItem = -1; // Create a DIV element that will contain the items (values) var txtFldExtension = document.createElement("DIV"); txtFldExtension.txtFld = txtFld; // Save a link to the text field in the div object, which we will retrieve from the event, so we have a clear source for it txtFldExtension.setAttribute("id", this.id + "autocomplete-list"); txtFldExtension.setAttribute("class", "autocomplete-items"); // Append the DIV element as a child of the autocomplete container this.parentNode.appendChild(txtFldExtension); // Close any previously opened modal dialogs and remember this modal dialog so we can close it later closePreviousModalDialog(txtFldExtension); // For each item in the array var noOfItems = 0; for (var itemNo = 0; itemNo < autoComplList.length; itemNo++) { // Check if the item starts with the same letters as the text field value if (autoComplList[itemNo].substr(0, val.length).toUpperCase() == val.toUpperCase()) { // Create a DIV element for each matching element var listItem = document.createElement("DIV"); // Make the matching letters bold listItem.innerHTML = "" + autoComplList[itemNo].substr(0, val.length) + ""; listItem.innerHTML += autoComplList[itemNo].substr(val.length); // Insert a input field that will hold the current array item's value listItem.innerHTML += ""; // Execute a function when someone clicks on the item value (DIV element) //listItem.txtFld = txtFld; // Save a link to the text field in the list item object, which we will retrieve from the event, so we have a clear source for it listItem.addEventListener("click", autoCompleteEventHandler(evnt, txtFld)); txtFldExtension.appendChild(listItem); // Limit it to a maximum number of items if (noOfItems++ == MaxAutoCompleteItems) { break; } } } // The event handler fuction is declared outside the for loop to keep the validator from complaining: // "Functions declared within loops referencing an outer scoped variable may lead to confusing semantics." function autoCompleteEventHandler(evnt, myTxtField) { return function() { // return the original event handler // Insert the value from the autocomplete panel into the associated text field myTxtField.value = this.getElementsByTagName("input")[0].value; // Close this modal dialog and clear the handle to an open modal dialog since none are open closePreviousModalDialog(null); // Prevent the click that created the dialog from propagating to the window, which would close the dialog evnt.stopPropagation(); // Update the component list showCompList(); // Update the URL updateURL(); }; } }); /// Add a listener to the text field for when the user presses a key on the keyboard txtFld.addEventListener("keydown", function(evnt) { var txtFldExtension = document.getElementById(this.id + "autocomplete-list"); if (txtFldExtension) { var autoComplItems = txtFldExtension.getElementsByTagName("div"); if (autoComplItems) { var newAutoComplActiveItem = -1; switch (evnt.keyCode) { case 27: // Escape // Close the modal dialog and clear the handle to an open modal dialog since none are open closePreviousModalDialog(null); break; case 35: // End // Go to the last options newAutoComplActiveItem = autoComplItems.length -1; break; case 36: // Home // Go to the first options newAutoComplActiveItem = 0; break; case 38: // Up arrow // Go to the previous option newAutoComplActiveItem = autoComplActiveItem -1; if (newAutoComplActiveItem < 0) newAutoComplActiveItem = (autoComplItems.length - 1); // If past the end, roll over back to the other end break; case 40: // Down arrow // Go to the next option newAutoComplActiveItem = (autoComplActiveItem + 1) % autoComplItems.length; // If past the end, roll over back to the other end break; case 13: // Enter // Click the "active" item if (autoComplActiveItem > -1) { autoComplItems[autoComplActiveItem].click(); } break; } // Go to a new option if (newAutoComplActiveItem > -1) { // De-highlight the previous option if (autoComplActiveItem > -1) { autoComplItems[autoComplActiveItem].classList.remove("autocomplete-active"); } // Change the option and highlight it autoComplActiveItem = newAutoComplActiveItem; autoComplItems[autoComplActiveItem].classList.add("autocomplete-active"); } } } }); } // *** The user clicked a checkbox in a dialog function updtTxtBox( fieldName) { console.log(fieldName); var txtFldExtension = document.getElementById(fieldName + 'ListDiv'); // Make a list of all the selected checkboxes var valueList = []; for (var divElem of txtFldExtension.childNodes) { if (divElem.tagName == 'LABEL') { var chkBox = divElem.childNodes[0]; if (chkBox.checked) { valueList.push(chkBox.value); } } } // Convert the list to a comma-delimited string var valueListStr = ''; if (valueList.length > 0) { valueListStr = valueList.filter(Boolean).join(','); } // Place it in the text box document.getElementById(fieldName + 'TxtFld').value = valueListStr; } // *** The user clicked a database entry text field for a field the uses a set // Add a dialog to a text field with a checkbox for each possible value in that set function openMultiList( dbEntryTxtFld) { console.log('openMultiList'); if (!openModalDialog && dbEntryTxtFld.valueList) { // Unless a dialog is already open // Get the field name from the row id var filterRow = dbEntryTxtFld.closest('tr'); // Get this field's name from the ID of the row in the filter table var fieldName = getFieldNameFromRow(filterRow); // Create a DIV to contain the checkboxes and append it to the text field's container var txtFldExtension = document.createElement("DIV"); txtFldExtension.setAttribute("id", fieldName + "ListDiv"); txtFldExtension.setAttribute('class', 'multiSelListDiv'); txtFldExtension.addEventListener("click", function(evnt) { evnt.stopPropagation(); // Prevent click inside the dialog from propagating to the window, which would close the dialog }); dbEntryTxtFld.parentNode.appendChild(txtFldExtension); // Add a check box for each item in the default for this field var multiListHTML = ''; var valueList = dbEntryTxtFld.valueList; var groupedList = (valueList[0].includes(':')); var groupName = ''; var optionText = ''; for (var optionValue of valueList) { optionText = optionValue; if (multiListHTML != '') multiListHTML += '
'; if (groupedList) { var optionParts = optionValue.split(':'); var thisGroupName = optionParts[0]; optionText = optionValue.slice(thisGroupName.length +1); if (thisGroupName != groupName) { // Start a new group groupName = thisGroupName; multiListHTML += '' + groupName + '
'; } } // Add this checkbox var checkedStr = (dbEntryTxtFld.value.includes(optionValue))? 'checked' : ''; //optionText = optionText[0].toUpperCase() + optionText.slice(1); // Capitalize the first letter multiListHTML += ''; } txtFldExtension.innerHTML = multiListHTML; // Close any previously open dialogs if (openModalDialog) { openModalDialog.parentNode.removeChild(openModalDialog); } // Save a handle to this dialog, so the window can close it when the user clicks outside the window openModalDialog = txtFldExtension; // Prevent the click that created the dialog from propagating to the window, which would close the dialog event.stopPropagation(); } } // ************ UPDATE THE DISPLAY FUNCTIONS ************ // *** Clear the navigation tree function clearNavigTree( ) { console.log('clearNavigTree', arguments.callee.caller.name); downLevelArrow.style.visibility = 'hidden'; // Hide the items for (navTreeLblNo = ClassX; navTreeLblNo <= SeriesX; navTreeLblNo++) { navTreeLbl = eval_redef('navTreeLbl' + navTreeLblNo); navTreeLbl.style.display = 'none'; } // Hide the arrows for (arrowNo = 1; arrowNo <= 5; arrowNo++) { navTreeArrow = eval_redef('navTreeArrow' + arrowNo); navTreeArrow.style.display = 'none'; } } // *** Update the navigation tree and the selection tabs // Called if the node changes, or the user selects a component function updtNavigTree( compName) { console.log('updtNavigTree', compName, arguments.callee.caller.name); // Update the navigation tree at the top // Left arrow downLevelArrow.style.visibility = (activeNodeObj.ancestorList.length == 0)? 'hidden' : 'visible'; var nodeCode, navTreeArrow, navTreeLbl, itemHTML, clickHTML; // Links to the ancestor nodes var nodeAncestorList = [...activeNodeObj.ancestorList]; // Shallow copy, so we don't affect the table var navTreeLblNo, arrowNo; var nodeDepth = nodeAncestorList.length; for (navTreeLblNo = 0; navTreeLblNo < nodeDepth; navTreeLblNo++) { nodeCode = nodeAncestorList[navTreeLblNo]; var nodeObj = nodesDict[nodeCode]; var nodeName = nodeObj.nm; clickHTML = 'onClick="goToNode(\'' + nodeCode + '\');"'; itemHTML = '' + nodeName + ''; navTreeLbl = eval_redef('navTreeLbl' + navTreeLblNo); navTreeLbl.innerHTML = itemHTML; navTreeLbl.style.display = 'inline-block'; navTreeLbl.style.cursor = 'pointer'; navTreeLbl.style.textDecoration = 'underline'; } // Name of the present node, either plain, or as a link if a component is selected var thisNodeName = activeNodeObj.nm; navTreeLbl = eval_redef('navTreeLbl' + nodeDepth); navTreeLbl.style.display = 'inline-block'; if (activeComp == '') { // No component selected: show the present node without a link clickHTML = ''; navTreeLbl.style.cursor = 'auto'; navTreeLbl.style.textDecoration = 'none'; // Clear the name of the selected components navTreeLbl5.innerHTML = ''; navTreeLbl5.style.display = 'none'; } else { // A component is selected (this must be a family level node): show the present node with a link clickHTML = 'onClick="goToNode(\'' + activeNodeCode + '\');"'; // Deselect the component navTreeLbl.style.cursor = 'pointer'; navTreeLbl.style.textDecoration = 'underline'; nodeDepth = 5; // Name of the selected components navTreeLbl5.innerHTML = activeComp; navTreeLbl5.style.display = 'inline-block'; } navTreeLbl.innerHTML = '' + thisNodeName + ''; if (typeof compName == 'undefined') { // Not browsing the component list // Hide the items that haven't yet been selected for (navTreeLblNo = nodeDepth +1; navTreeLblNo <= SeriesX; navTreeLblNo++) { navTreeLbl = eval_redef('navTreeLbl' + navTreeLblNo); navTreeLbl.style.display = 'none'; } // Arrows for (arrowNo = 1; arrowNo <= 5; arrowNo++) { navTreeArrow = eval_redef('navTreeArrow' + arrowNo); navTreeArrow.style.display = (arrowNo <= nodeDepth)? 'inline-block' : 'none'; } } else { // Browsing the component list // Show the items that haven't yet been selected var compRecord = allCompDataDict[compName]; if (compRecord) { for (navTreeLblNo = nodeDepth +1; navTreeLblNo <= SeriesX-1; navTreeLblNo++) { var taxonomyFieldName = TaxonomyFieldNames[navTreeLblNo]; nodeCode = compRecord[taxonomyFieldName]; var lvlName = ''; try { lvlName = nodesDict[nodeCode].nm; } catch (e) { lvlName = '' + nodeCode + ' missing'; } navTreeLbl = eval_redef('navTreeLbl' + navTreeLblNo); navTreeLbl.innerHTML = lvlName; navTreeLbl.style.display = 'inline-block'; navTreeLbl.style.cursor = 'auto'; navTreeLbl.style.textDecoration = 'none'; } navTreeLbl5.innerHTML = compName; navTreeLbl5.style.display = 'inline-block'; // Show all the arrows for (arrowNo = 1; arrowNo <= 5; arrowNo++) { navTreeArrow = eval_redef('navTreeArrow' + arrowNo); navTreeArrow.style.display = 'inline-block'; } } } // If required reduce the widths of some elements in the tree so they all fit in one row // Get the available width of the row and the total width of the elements within it var safetyMargin = 6; var rowWidth = navigTextRow.offsetWidth - safetyMargin; var treeElements = navigTextRow.children; var totalWidth = 0; for (navTreeLblNo = 0; navTreeLblNo < treeElements.length; navTreeLblNo++) { treeElements[navTreeLblNo].style.width = 'auto'; totalWidth += treeElements[navTreeLblNo].offsetWidth; } // Shrink the tree elements other than the right-most one var extraWidth = totalWidth - rowWidth; if (extraWidth > 0) { // If the elements do not fit in one row var noOfElementsToShrink = activeNodeObj.ancestorList.length - ClassX; var chopWidth = extraWidth / noOfElementsToShrink; for (navTreeLblNo = 1; navTreeLblNo < noOfElementsToShrink +1; navTreeLblNo++) { navTreeLbl = eval_redef('navTreeLbl' + navTreeLblNo); var treeElementWidth = navTreeLbl.offsetWidth; treeElementWidth = (treeElementWidth - chopWidth) + 'px'; navTreeLbl.style.width = treeElementWidth; } } } // updtNavigTree // *** Update the navigation tree and the selection tabs // Called if the node changes or the user changed a main tab function updtMainTabs( ) { console.log('updtMainTabs', arguments.callee.caller.name); // Show the selection level var stageNo = activeNodeObj.ancestorList.length; stageNoTxt.innerHTML = stageNo; if (stageNo <= FamilyX) { typeTitleSpan.innerHTML = 'Select the ' + TaxonomyEnglishNames[stageNo +1]; } // Update the selection tabs for the selected mode // Clear the contents of the cards for navigation search, to force reloading the contents // We do this even if the mode is browse, because we may then switch to the navigate mode navigationNamesSelectorBox.innerHTML = ''; navigationTraitsSelectorBox.innerHTML = ''; navigationPicturesSelectorBox.innerHTML = ''; navigationFlowchartSelectorBox.innerHTML = ''; // Clear the browse panes so that they can be refreshed, but only if the user opens them // We do this even if the mode is navigate, because we may then switch to the browse mode // Update the tabs for the selected selection mode if (mainTab_Q.checked) { // "Navigate by type" tab updtQuickPickTab(); } if (mainTab_N.checked) { // "Navigate by type" tab updtNavigTabs(); } if (mainTab_B.checked) { // Browse by pictures tab updtBrowseByPictsTab(); } if (mainTab_L.checked) { // "Logos" tab updtLogosTab(); } if (mainTab_F.checked) { // "Find by term" tab updtFindTabs(); } } // *** Update the Quick Pick tab // Called at init if the selection mode is "Quick pick" or if the user changes to the "Quick pick" tab function updtQuickPickTab( ) { console.log('updtQuickPickTab', arguments.callee.caller.name); if (quickPickGallery.innerHTML == '') { // First time this tab was opened // Fill the pictures var quickPickHTML = ''; for (var mostSearchedComp of QuickPickComps) { var isComp = mostSearchedComp[0]; var useApplImg = mostSearchedComp[1]; var nodeCompName = mostSearchedComp[2]; if (isComp) { // Component var imgName = nodeCompName; var parentesisPos = nodeCompName.indexOf(OpenParen); // ) Some component names have extra info in paranthesis. Remove it if (parentesisPos > 0) { imgName = nodeCompName.slice(0,parentesisPos).trim(); } var imgFldr = useApplImg? 'applimg' : 'compimg'; quickPickHTML += '' + nodeCompName + ''; } else { // Node var nodePath = getPathForNode(nodeCompName); if (nodesDict[nodeCompName]) { var nodeName = nodesDict[nodeCompName].nm; var imgName = useApplImg? 'a' : 'i'; quickPickHTML += '' + nodeName + ''; } else { console.log('*** MISSING quick-pick data', nodeCompName) } } } quickPickGallery.innerHTML = quickPickHTML; // Request the full components database goToNode(HomeNodeCode); } } // *** Update the navigate tab // Called at init if the selection mode is "navigate" or if the user changes to the "Navigate by type" tab function updtNavigTabs( ) { console.log('updtNavigTabs', arguments.callee.caller.name); var boxHTML = 'Family identified'; var nodeEnglishName, subNodeCode, selNo, subNodeDict, itemDict; if (navigTab_N.checked || navigTab_T.checked) { var isTraits = navigTab_T.checked; var selectorBox = isTraits? navigationTraitsSelectorBox: navigationNamesSelectorBox; // Update the select by names or by traits tab // If this is the first time this tab was opened, fill its contents if (selectorBox.innerHTML == '') { if (!atFamily) { // Instructions boxHTML = isTraits? activeNodeObj.tq : ''; // Create the selector list and show it subNodeDict = activeNodeObj.ls; if (subNodeDict) { selNo = 0; for (subNodeCode in subNodeDict) { if(subNodeDict.hasOwnProperty(subNodeCode)) { var itemText = ''; if (isTraits) { // Traits selectors itemText = subNodeDict[subNodeCode]; } else { // Name selectors itemDict = eval_redef(subNodeCode); if (itemDict) { itemText = itemDict.nm; } } var displayNo = selNo + 1; if (atHome) {displayNo += 3;} // To match the chapter number in the book var btnIDbase = isTraits? 'selTraitsBtn' : 'selNamesBtn'; boxHTML += '
' + displayNo + ': ' + itemText + '
'; selNo++; } } } } // Book notice if (atHome) {boxHTML += 'Numbers: chapter numbers in the book';} selectorBox.innerHTML = boxHTML; } } if (navigTab_P.checked) { // Update the select by pictures tab // If this is the first time this tab was opened, fill its contents if (navigationPicturesSelectorBox.innerHTML == '') { console.log('update pictures tab'); if (!atFamily) { // Below family level // Instructions boxHgTML = ''; // Find the dictionaries and the path to the image, depending on the stage subNodeDict = activeNodeObj.ls; var imgPath = getPathForNode(activeNodeCode); // Create a list of pictures and show it selNo = 1; for (subNodeCode in subNodeDict) { if(subNodeDict.hasOwnProperty(subNodeCode)) { if (subNodeCode.substr(0,4) !== 'sel_') { // Except for the helper categories in the Class stage var itemName = ''; itemDict = eval_redef(subNodeCode); if (itemDict) { itemName = itemDict.nm; } var imgSrc = imgPath + subNodeCode + '/i.jpg'; boxHTML += '' + itemName + ''; selNo++; } } } boxHTML = 'Click on a picture.
' + boxHTML + '
'; // font-size: 0 removes the white space between rows of images for the descender } navigationPicturesSelectorBox.innerHTML = boxHTML; } } if (navigTab_F.checked) { // Update the select by flowchart tab // If this is the first time this tab was opened, fill its contents if (navigationFlowchartSelectorBox.innerHTML == '') { console.log('update flowchart tab'); if (!atFamily) { // Below family level // Instructions boxHTML = 'Click on a box at right.'; // Find the dictionaries and the path to the image, depending on the stage var flowChartPath = getPathForNode(activeNodeCode); flowChartPath += 'f.svg'; boxHTML += '
Missing flowchart
'; } // Show it navigationFlowchartSelectorBox.innerHTML = boxHTML; } } /* // Disable any selections that do not pass the filter if (filterSwitch.checked) { showCompList(); } */ } // updtNavigTabs // *** Update the browse by pictures tab function updtBrowseByPictsTab( ){ console.log('updtBrowseByPictsTab', arguments.callee.caller.name); // If this is the first time this tab was opened, fill its contents (it takes a long time, so no need to do it unless the user wants it) if (compGalleryDiv.innerHTML == '') { // Fill the categorized image list var rootDef = eval_redef(HomeNodeCode); var classListDict = rootDef.ls; var boxHTML = ''; for (var classCode in classListDict) { if(classListDict.hasOwnProperty(classCode)) { // For every class var classDef = eval_redef(classCode); var subClassListDict = classDef.ls; for (var subClassCode in subClassListDict) { if(subClassListDict.hasOwnProperty(subClassCode)) { // For every subclass in this class var subClassDef = eval_redef(subClassCode); var orderListDict = subClassDef.ls; for (var orderCode in orderListDict) { if(orderListDict.hasOwnProperty(orderCode)) { // For every order in this subclass var orderDef = eval_redef(orderCode); // List of families var familyListDict = orderDef.ls; if (Object.keys(familyListDict).length > 1) { // Not a node with a single child var imgCnt = 0; var MaxNoOfImgs = 8; for (var familyCode in familyListDict) { if(familyListDict.hasOwnProperty(familyCode)) { // Do each family in this order var familyDef = eval_redef(familyCode); // Limit classes to 8 items (as of this writing, only GP card edge sockets and MIL circular connectors exceed this) if (imgCnt == MaxNoOfImgs) { break; } // Family image var familyName = familyDef.nm; imgPath = getPathForNode(familyCode); boxHTML += '' + familyName + ' component'; imgCnt++; } } } boxHTML += '
'; } } } } } } // Show the gallery compGalleryDiv.innerHTML = boxHTML; } } // *** Update the logos tab function updtLogosTab( ){ console.log('updtLogosTab', arguments.callee.caller.name); // If this is the first time this tab was opened, fill its contents (it takes a long time, so no need to do it unless the user wants it) if (logoGalleryDiv.innerHTML == '') { // Request the manufacturer data var dbURL = 'cgi-bin/getBrandsTable.py'; console.log('request manufacturer data'); var xmlHttpRequest = new XMLHttpRequest(); xmlHttpRequest.open("GET",dbURL,true); xmlHttpRequest.send(); // Later, after the database loads, execute this function xmlHttpRequest.onreadystatechange = function() { if (xmlHttpRequest.readyState == 4 && xmlHttpRequest.status === 200) { brandDataReceived(xmlHttpRequest); } }; // Place something in the gallery so that we don't do it again logoGalleryDiv.innerHTML = 'Waiting for data'; } } // *** We received the brand table, asynchronously function brandDataReceived( xmlHttpRequest){ console.log('brandDataReceived'); // Convert to a dictionary var boxHTML = ''; var brandTbl = JSON.parse(xmlHttpRequest.responseText); // |Description | Data | var brandData = brandTbl[1]; // Get just the data for (var brandRecord of brandData) { // | brand_name | logo_lst | manuf_name| manuf_link | country_ | notes_ | // Extract the data var brandName = brandRecord[0]; var logoListStr = brandRecord[1]; var logoList = logoListStr.split(','); var manufName = brandRecord[2]; var manufURL = brandRecord[3]; var brandCountry = brandRecord[4]; var brandNotes = brandRecord[5]; var brandTitle = brandName; if (brandName != manufName) { brandTitle += ' (' + manufName + ')'; } // Fill the logo gallery for (var logoName of logoList) { if (logoName != '') { boxHTML += ''; } } // Store this brand in the brand dictionary brandDict[brandName] = { logoList: logoList, manufName: manufName, manufURL: manufURL, country: brandCountry, notes: brandNotes } } // Show the gallery logoGalleryDiv.innerHTML = boxHTML; } // *** Update the Find tab function updtFindTabs( ){ console.log('updtFindTabs', arguments.callee.caller.name); // If this is the first time this tab was opened, fill its contents if (keywordListBox.innerHTML == '') { var keywordHTML = ''; var keywordNodeList; var keywordNodeCode; var aKeywrd; var keywordsList = sortListNoCase(Object.keys(keywordsDict)); // Sort the keyword list for (var kwrdNo = 0; kwrdNo < keywordsList.length; kwrdNo++) { aKeywrd = keywordsList[kwrdNo]; keywordNodeList = keywordsDict[aKeywrd]; keywordHTML += aKeywrd + ': '; for (var linkNo = 1; linkNo <= keywordNodeList.length; linkNo++) { keywordNodeCode = keywordNodeList[linkNo -1]; // The numbers shown start from 1, but the indexes in a list start from 0 keywordHTML += '' + linkNo + ', '; } keywordHTML += '
'; } keywordListBox.innerHTML = keywordHTML; } // If the URL specifies a keyword, enter it if (initKeyword != '') { searchTextBox.value = initKeyword; searchText(initKeyword); } // If the Family level, hide the gallery //searchBoxContents.style.display = (atFamily)? 'none' : 'block'; } // ************ URL FUNCTIONS ************ // *** Update the URL function updateURL( ) { console.log('updateURL', arguments.callee.caller.name); // Prepare a new query var queryStr = ''; switch (getSelectedTabCode('MainSelTabs')) { case 'N': // Navigate // Node and component var nodeCode = '', compCode = ''; if ((activeNodeCode != '') && !atHome) {nodeCode = 'n=' + activeNodeCode;} if (activeComp != '') { compCode = 'c='; compCode += encodeURIComponent(activeComp); } // Filter var filterCode = ''; if (filterSwitch.checked) { // The filter state is encoded into one to four parts // Part 1: First 76 selects // Part 2: Selects past # 76 // Part 3: String in the "Comp name:" filter text box // Part 4: Manufacturer name. Done separately because the list of manufacturers is likely to change as more manufacturers are added to the database // Note that the manufacturer name is encoded twice: once in part 1 (the invert selection is used but the selected index will be overriden) and once in part 4 // If we only did it in part 4, we would lose information about the invert button var fltrCodes = ['','','','']; // First set of selects, Second set of selects, Component name, Manufacturer name // Selects // The Attributes section has 75 selects; each select also has an invert checkbox // Most selects have less than 35 options, so their index and the state of the invert checkbox takes at most 70 values (less than EncNoOfChars) // However, some have more, especially in the Home node: // tot_circuits_lst 707 // mate_width_dia_mm 394 // latch_details 195 // wiring_pitch_mm 157 // mating_pitch_mm 143 // pcb_row_spcng_mm 130 // max_current_a 117 // pcb_pitch_mm 113 // male_cont_size_mm 113 // row_spacing_mm 101 // appl_other 96 // max_voltage_v 93 // wir_points_p_ckt_lst 81 // standard_ 76 // max_frequency_ghz 43 // electrical_rows_lst 42 // phys_rows_per_elec_lst 42 // mate_rows_lst 41 // The state of each used select is encoded in two or three characters: // First character (with 77 possible values) is the number of the select // Second character: LSB set if using three characters; next significant bit set if inverted; more significant bits: 4 times (the selected index minus one) // Third character (if used): most significant part of selected index var filterSelects = filtersDbEditBox.getElementsByClassName('filterSel'); var filterInvertBoxes = filtersDbEditBox.getElementsByClassName('invSelChkBox'); var isAttrFilter = false; var isApplFilter = false; for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { var filterSelect = filterSelects[selectNo]; var selectedIndex = filterSelect.selectedIndex; var invertChecked = filterInvertBoxes[selectNo].checked; if (selectedIndex > 0) { var filterCodeSect = 0; var selectNoOfst = selectNo; if (selectNo >= EncNoOfChars) { filterCodeSect = 1; selectNoOfst -= EncNoOfChars; } // Encode the number of the select fltrCodes[filterCodeSect] += EncChars[selectNoOfst]; // Encode the number of the selected option and invert checkbox var selectedIndexCode = 4 * (selectedIndex - 1) + (invertChecked? 2: 0); if (selectedIndexCode < EncNoOfChars) { fltrCodes[filterCodeSect] += EncChars[selectedIndexCode]; } else { selectedIndexCode += 1; // Flag that we're using three characters fltrCodes[filterCodeSect] += EncChars[selectedIndexCode % EncNoOfChars]; // Least significant character fltrCodes[filterCodeSect] += EncChars[Math.floor(selectedIndexCode / EncNoOfChars)]; // Most significant character } if (filterSelect.className.includes('applFilterSel')) { isApplFilter = true; } else { isAttrFilter = true; } } } // Component name filter text box fltrCodes[2] = filterCompNameTxtFld.value; // Manufacturer name filter select fltrCodes[3] = manufFiltrSel.value; // Join the four parts, separated by semicolons // For example: // '': The filter is not used // 'ABCD': only selects #75 and lower are used // ';EFGH': only selects #76 and higher are used // 'ABCD;EFGH': multiple selects are used, some below and some above #76 // ';;IJK': no selects are used, but there is text in the "Comp name:" filter text box // ';;;LMN': no selects are used and there is no text in the "Comp name:" filter text box, but a manufacturer is selected // ';;IJK;LMN': no selects are used, but there is text in the "Comp name:" filter text box amd a manufacturer is selected // 'ABCD;EFGH;IJK;LMN': all are used for (var partNo = 0; partNo < 4; partNo++) { filterCode += fltrCodes[partNo] + ';'; } // Remove trailing semicolons while (filterCode[filterCode.length -1] == ';') { filterCode = filterCode.slice(0,filterCode.length -1); } // Show or hide filter clear buttons if (filterCode != '') { filterCode = 'f=' + filterCode; if (isAttrFilter) { clearAttrFilterBtn.style.visibility = 'visible'; } if (isApplFilter) { clearApplFilterBtn.style.visibility = 'visible'; } } else { clearAttrFilterBtn.style.visibility = 'hidden'; clearApplFilterBtn.style.visibility = 'hidden'; var clearSectFilterBtns = document.getElementsByClassName('clearSectFilterBtn'); for (var clearSectFilterBtn of clearSectFilterBtns) { clearSectFilterBtn.style.visibility = 'hidden'; } } } // Prepare the new query queryStr = ['N=',nodeCode, compCode, filterCode].filter(Boolean).join('&'); // .filter(Boolean) removes blank strings break; case 'Q': // Quick pick queryStr = 'Q='; break; case 'B': // Browse queryStr = 'B='; break; case 'L': // Logos queryStr = 'L='; break; case 'F': // Find mode: search box text // By Find queryStr = 'k='; if (searchTextBox.value != '') { queryStr += searchTextBox.value; } break; } // Remove the old query var pageURL = window.location.href; if (pageURL.indexOf('?') > 0) { pageURL = pageURL.substring(0, pageURL.indexOf('?')); } if (queryStr != '') { pageURL += '?' + queryStr;} // Replace the URL in the URL bar, without refreshing the page window.history.replaceState({}, document.title, pageURL); } // *** Copy the component list to the clipboard function copyComponentList( ) { console.log('copyComponentList',componentCopyList); var tempTextArea = document.createElement('textarea'); componentListBox.appendChild(tempTextArea); tempTextArea.innerHTML = componentCopyList; tempTextArea.focus(); tempTextArea.select(); document.execCommand('copy'); tempTextArea.remove(); } // ************ INITIALIZE ************ // *** Compile all the data for each node for ease of access // Called only once by init function compileAllData( ) { console.log('compileAllData', arguments.callee.caller.name); var keywordsDict = {}; // Dictionary of keywords, with a list of nodes that include that keyword; used to create a list of keywords and links, but only if the user goes to browse it; this speeds up init considerably var seeAlsoCheckList = []; // List of all the "See also" node codes in all the nodes, to check if any are undefined var browseByCompPictHTML = '', browseByApplPictHTML = ''; // In the tree file, there are 3 possibilities on which node specifies attributes: // 1. The node itself // 2. An ancestor to that node // 3. Each of that node's children on even their children (the letter codes from each are all compiled into a single string) // In this program, each node must have its attributes specified // Therefore, if the tree file doesn't specify them, we add them in // Now, as we go down the tree and compile the data on each node, if it has no attributes, we give it the parent's attributes // That takes care of case #2. // Afterward, we'll take care of case #3 // Do each class var rootNodeObj = compileNodeData([], HomeNodeCode, {}, seeAlsoCheckList); var classListDict = rootNodeObj.ls; for (var classCode in classListDict) { // Compile data for this class var classNodeObj = compileNodeData([HomeNodeCode], classCode, rootNodeObj, seeAlsoCheckList); // Do each subclass in this order var subClassListDict = classNodeObj.ls; for (var subClassCode in subClassListDict) { // Compile data for this subclass var subClassNodeObj = compileNodeData([HomeNodeCode, classCode], subClassCode, classNodeObj, seeAlsoCheckList); // Do each order in this subclass var orderListDict = subClassNodeObj.ls; for (var orderCode in orderListDict) { // Compile data for this order var orderNodeObj = compileNodeData([HomeNodeCode, classCode, subClassCode], orderCode, subClassNodeObj, seeAlsoCheckList); // Do each family in this order var familyListDict = orderNodeObj.ls; var orderBranch = {}; for (var familyCode in familyListDict) { // Compile data for this family var familyNodeObj = compileNodeData([HomeNodeCode, classCode, subClassCode,orderCode], familyCode, orderNodeObj, seeAlsoCheckList); } } } } // For each "See Also" node code, check that a node with that code has been defined for (var seeAlsoNo = 0; seeAlsoNo < seeAlsoCheckList.length; seeAlsoNo++) { var seeAlsoItem = seeAlsoCheckList[seeAlsoNo]; if (!(seeAlsoItem in nodesDict)) { debugText.innerHTML = 'Missing See Also definition: ' + seeAlsoItem; } } } // *** Compile all the data for a node // Called by init, once for each node // Returns the object for the node function compileNodeData( nodeAncestorList, nodeCode, parentNodeObj, seeAlsoCheckList) { // Create an object for this node var nodeObj = eval_redef(nodeCode); if (typeof nodeObj == 'undefined') { // The tree file did not define this node debugText.innerHTML = 'Missing node: ' + classCode; } else { // The tree file defines this node // Add to this node a list of its ancestors nodeObj.ancestorList = nodeAncestorList; // Add this node to the path table nodeAncestorTable[nodeCode] = nodeAncestorList; // Data base not loaded nodeObj.compLoaded = false; // Compile all the keywords for this node // Compiled in the global variable keywordsDict var keywdList = nodeObj.kw; if (typeof keywdList != 'undefined') { for (var kwdNo = 0; kwdNo < keywdList.length; kwdNo++) { var aKeyword = keywdList[kwdNo]; if (aKeyword != '') { // Except for empty entries // If no dictionary entry for this keyword, create one with an empty list if (!(aKeyword in keywordsDict)) { keywordsDict[aKeyword] = []; } // Add the tree for this family to the list for this keyword keywordsDict[aKeyword].push(nodeCode); } } } // Compile all the "See also" for this node, to check if any are undefined var itemsSeeAlsoList = nodeObj.sa; if (itemsSeeAlsoList) { for (var itemNo = 0; itemNo < itemsSeeAlsoList.length; itemNo++) { seeAlsoCheckList.push(itemsSeeAlsoList[itemNo]); } } // Store the object for this node nodesDict[nodeCode] = nodeObj; } return nodeObj; } // *** Request component data from the database // At init, this is done after the field data are received // Otherwise, it's done when we change a node and we don't have the node data yet function requestCompData( ) { console.log('requestCompData'); // We don't yet have the components for this node // We do have the field names var dbURL = 'cgi-bin/getComponentsData.py'; // Plain request for the home node // Specify which data we want var classFieldName = TaxonomyFieldNames[activeNodeObj.ancestorList.length]; dbURL += '?fldName=' + classFieldName + '&fldValue=' + activeNodeCode; console.log('request component data for',activeNodeCode); var xmlHttpRequestData = new XMLHttpRequest(); xmlHttpRequestData.open("GET",dbURL,true); xmlHttpRequestData.send(); // Later, after the database loads, execute this function xmlHttpRequestData.onreadystatechange = function() { if (xmlHttpRequestData.readyState == 4 && xmlHttpRequestData.status === 200) { compDataReceived(xmlHttpRequestData); } }; // Show the list of components componentListBox.innerHTML = 'Loading'; } // *** Request filter range data from the database // At init, this is done after the field data are received // Otherwise, it's done when we change a node and we don't yet have the data for that node's filter ranges function requestFltrRngData( ) { console.log('requestFltrRngData for', activeNodeCode); // For this node var subNodeListQuery = '\'' + activeNodeCode + '\''; // For this node's subnodes // We need them for filtering the left-side selectors without having to go through the components data if (!atFamily) { // A family doesn't have subnodes var subNodeDict = activeNodeObj.ls; var subNodeList = Object.keys(subNodeDict); for (var selectorNo in subNodeList) { var subNodeCode = subNodeList[selectorNo]; var subNodeObj = nodesDict[subNodeCode]; if (!subNodeObj.fltrRngs) { if (subNodeListQuery != '') {subNodeListQuery += ',';} subNodeListQuery += '\'' + subNodeCode + '\''; } } } // We now have a list of the nodes whose filter ranges we need // Request them from the database // After we receive them, process them asynchronously var dbURL = 'cgi-bin/getFilterData.py?node=' + subNodeListQuery; var xhr = new XMLHttpRequest(); xhr.open("GET",dbURL,true); xhr.send(); // Later, after the database loads, execute this function xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status === 200) { // Extract this node's filter ranges and save them in the node object // Load the filter controls with the filter ranges for the active node fltrRngDataReceived(xhr.responseText); } }; } // *** We received the field names from the components table, asynchronously function compFieldDefsReceived( xmlHttpRequest) { console.log('compFieldDefsReceived'); // Store the fields definitions // Need to detect if the responseText is an error and put it to the console <<< var fieldDefList = JSON.parse(xmlHttpRequest.responseText); // Convert to a dictionary for (var fieldDef of fieldDefList) { // | Field | Type | Null| Key | Default | Extra | // Parse the data var fieldName = fieldDef[0]; var fieldTypeStr = fieldDef[1]; // Assume there is no length/value list var fieldType = fieldTypeStr; // Data type, var fieldLength = ''; // Length (for numeric types) var fieldValueList = []; // Value list (for sets and enums) var parPos = fieldTypeStr.indexOf(OpenParen); //) Find the start of the length/values, whioch are in parenthesis if (parPos != -1) { // There is a length/value fieldType = fieldTypeStr.slice(0,parPos); // Data type is before the opening parenthesis var fieldLengthValues = fieldTypeStr.slice(parPos+1,-1); // length/value is after the opening parenthesis. Remove the two parenthesis if (['set', 'enum'].includes(fieldType)) { fieldValueList = fieldLengthValues.slice(1,-1).split('\',\''); // Remove the single quotes } else { fieldLength = fieldLengthValues; } } // Save it in a dictionary var fieldDefDict = {'type': fieldType,'length': fieldLength,'values': fieldValueList}; // Save it in the components field definition dictionary compDbFieldDict[fieldName] = fieldDefDict; } // Normally, we receive field data before we receive component data // Occasionally, at inint, the field data are delayed. If so, we did not request the component data yet. // If so, ask for them now if (!activeNodeObj.compLoaded) { requestCompData(); } } // compFieldDefsReceived // *** We received the field names from the filter range table, asynchronously function fltrRngFieldDefsReceived( xmlHttpRequest) { console.log('fltrRngFieldDefsReceived'); // Store the fields definitions // Need to detect if the responseText is an error and put it to the console <<< var fieldDefList = JSON.parse(xmlHttpRequest.responseText); // Convert to a dictionary for (var fieldDef of fieldDefList) { // | Field | Type | Null| Key | Default | Extra | // Parse the data fltrRngFieldList.push(fieldDef[0]); // Field name } // Normally, we receive field data before we receive filter ranges data // Occasionally, at inint, the field data are delayed. If so, we did not request the filter ranges data yet. // If so, ask for them now if (!nodesDict[activeNodeCode].fltrRngs) { // Request the filter ranges for this node and its subnodes from the database requestFltrRngData(); } } // *** Save the settings into a cookie function saveSettingsCookie( ) { var dfltFltr = getRadioBtnId('FilterSelTabs'); var dfltList = compListModeBtn.src.split('/').pop(); var dfltMainTab = getRadioBtnId('MainSelTabs').slice(-1); var settingsCode = [wireSizeUnitsSel.value,widthUnits.value,pitchTolSel.value,enabEditChkBox.checked,dfltFltr,dfltList,dfltMainTab]; setCookie(CookieSettings, JSON.stringify(settingsCode), 1000); // Hide the Save Cookies icon and button, as they are no longer necessary saveCookiesIcon.style.display = 'none'; settingsSaveSettingsRow.style.display = 'none'; } // *** Initialize // Called only once at init function init( ) { console.log('init'); // Get the field definitions for the components table console.log('request components db fields'); var dbURL = 'cgi-bin/getTableFields.py?table=components'; var xmlHttpReqCompFields = new XMLHttpRequest(); xmlHttpReqCompFields.open("GET",dbURL,true); xmlHttpReqCompFields.send(); // Later, after the database loads, execute this function xmlHttpReqCompFields.onreadystatechange = function() { if (xmlHttpReqCompFields.readyState == 4 && xmlHttpReqCompFields.status === 200) { compFieldDefsReceived(xmlHttpReqCompFields); } }; // Get the field definitions for the filter ranges table console.log('request filter ranges db fields'); dbURL = 'cgi-bin/getTableFields.py?table=filter_ranges'; var xmlHttpReqFltrFields = new XMLHttpRequest(); xmlHttpReqFltrFields.open("GET",dbURL,true); xmlHttpReqFltrFields.send(); // Later, after the database loads, execute this function xmlHttpReqFltrFields.onreadystatechange = function() { if (xmlHttpReqFltrFields.readyState == 4 && xmlHttpReqFltrFields.status === 200) { fltrRngFieldDefsReceived(xmlHttpReqFltrFields); } }; // From the tree file, compile the paths, pictures, and keywords for each node compileAllData(); // If so directed, install a cookie in the user's computer to allow editing if (SetEditPermission) { setCookie(CookieEditPermission, 'true', 400); // The longer that Chrome allows is 400 days } // Load the setting from the cookies, if any var cookieValue = getCookie(CookieSettings); var dfltSelectionMode; var dfltFilterTab; if (cookieValue != '') { var settingsCode = JSON.parse(cookieValue); wireSizeUnitsSel.value = settingsCode[CookieWireSizeUnits]; widthUnits.value = settingsCode[CookieWidthUnits]; pitchTolSel.value = settingsCode[CookiePitchTol]; enabEditChkBox.checked = (settingsCode[CookieEnabEditChkBox] != false); dataEditCtrlsPnl.style.display = (enabEditChkBox.checked)? 'block' : 'none'; var dfltFilterName = settingsCode[CookieDefaultFilter]; dfltSelectionMode = settingsCode[CookieDefaultMainTab]; dfltFilterTab = document.getElementById(dfltFilterName); var dfltListType = settingsCode[CookieDefaultList]; if (dfltListType && (dfltListType != '')) { compListModeBtn.src = 'img/' + dfltListType; } // Hide the Save Cookies icon and button, as they are no longer necessary saveCookiesIcon.style.display = 'none'; settingsSaveSettingsRow.style.display = 'none'; } // Load the data edit permission from the cookies, if any cookieValue = getCookie(CookieEditPermission); if (cookieValue == '') { dataEditSettingsRow.style.display = 'none'; // Hide the Data edit checkbox, as this user is not permitted to edit } // Read the query from the URL var urlParams = new URLSearchParams(window.location.search); // By default, select the navigation mode, home // f: filter code; navigation mode // n: node code; navigation mode // c: component name (n must be a family); navigation mode // b: browse mode // k: keyword; find mode var initNodeCode = HomeNodeCode; var selectionMode = ''; if (urlParams.has('m')) { // Old version of Identiconn var pageURL = location.href; pageURL = pageURL.replace('identification.html','identification91.html'); location.href = pageURL; } else if (urlParams.has('B')) { // Browse selectionMode = 'B'; } else if (urlParams.has('L')) { // Logos selectionMode = 'L'; } else if (urlParams.has('Q')) { // Quick pick selectionMode = 'Q'; } else if (urlParams.has('k')) { // Find selectionMode = 'F'; initKeyword = urlParams.get('k'); // This is completed after the database is loaded } else if (urlParams.has('N')) { // Navigation selectionMode = 'N'; // Node if (urlParams.has('n')) { // Some node var preselectNodeCode = urlParams.get('n'); if (preselectNodeCode in nodeAncestorTable) { initNodeCode = preselectNodeCode; } } // Component and filter. These are handled after the database is loaded if (urlParams.has('c')) { activeComp = urlParams.get('c'); } if (urlParams.has('f')) { urlFilterParams = urlParams.get('f'); filterSwitch.checked = true; } if (urlParams.has('n') || urlParams.has('f')) { // Filter, or start from a node filterAttrTab.checked = true; } else { // Home, no filter if (dfltFilterTab) { dfltFilterTab.checked = true; } else { filterQuickTab.checked = true; } } } else { // No URI parameters if (dfltSelectionMode) { selectionMode = dfltSelectionMode; } else { // Default: Quick pick selectionMode = 'Q'; } zz('***',dfltFilterTab,'***') if (dfltFilterTab) { dfltFilterTab.checked = true; } else { filterQuickTab.checked = true; } } // Select the tabs for the specified selection mode document.getElementById('mainTab_' + selectionMode).checked = true; document.getElementById('navigTab_N').checked = true; // Go to the initial node var doStackNode = false; // Don't push this node into the stack var doUpdtURL = false; // Don't update the URL goToNode(initNodeCode, doStackNode, doUpdtURL); /* // Set the Title tag for small icons var smallIcons = document.getElementsByClassName('smalIcon'); for (var iconNo = 0; iconNo < smallIcons.length; iconNo++) { smallIcons[iconNo].title = smallIcons[iconNo].alt; } */ // Let an Enter key in the search text field start a search searchTextBox.addEventListener('keyup', function (e) { console.log(e); if (e.key === 'Enter') { searchText(this.value); } }); // Set the width of the selects in the filter panel and the text boxes in the database edit panel var filterRows = document.querySelectorAll('.dbEditOnlyRow,.filterRow'); for (var filterRow of filterRows) { // The table's deprecated summary attribute holds the width of the controls within this section var sectionTable = filterRow.closest('table'); var ctrlWidth = sectionTable.summary; if (ctrlWidth != '') { // Reduce the width of controls that are followed by units (e.g., "mm") if (filterRow.className.includes('hasUnits')) { ctrlWidth -= UnitsLabelWidth; } var selWidth = ctrlWidth - InvertChkBxWidth; // The selects are shorter to make space for the invert check box // Find the table cell in this row with the filter and edit components var filterCell, editCell; for (var rowCell of filterRow.cells) { if (rowCell.className.includes('filterCell')) { filterCell = rowCell; } if (rowCell.className.includes('dbEntryCell')) { editCell = rowCell; } } // Filter selector if (filterCell) { var filterSel = null; var filterElements = filterCell.childNodes; for (var filterElement of filterElements) { // Skip the text nodes that occur if the HTML has anything between the "TD" tag and the "SELECT" tag if (filterElement.nodeName == 'SELECT') { filterSel = filterElement; break; } } if (filterSel) { filterSel.style = 'width:' + selWidth + 'px;'; } } // Database edit text field if (editCell) { var editTxtBox = null; var editElements = editCell.childNodes; for (var editElement of editElements) { // Skip the text nodes that occur if the HTML has anything between the "TD" tag and the "SELECT" tag if (editElement.nodeName == 'INPUT') { editTxtBox = editElement; break; } if (editElement.nodeName == 'DIV') { var firstElement = editElement.childNodes[0]; if (firstElement.id) { editTxtBox = firstElement; break; } } } var hasSize = (editElement.size) && (editElement.size != 20); if (editTxtBox && !hasSize) { editTxtBox.style = 'width:' + ctrlWidth + 'px;'; } } } } // If the user clicks outside a modal dialog, close it document.addEventListener("click", function (evnt) { // Close any previously opened modal dialog and clear the handle to an open modal dialog since none are open closePreviousModalDialog( null); // Close any previously opened settings dialog settingsDialog.style.display = 'none'; }); // Give functionality to the upload buttons over the product and application images prodImgBox.onload = () => { URL.revokeObjectURL(prodImgBox.src); }; editCompPictHiddenFileBtn.addEventListener('change', function() { prodImgBox.src = URL.createObjectURL(editCompPictHiddenFileBtn.files[0]); editCompPictHiddenUploadBtn.style.display = 'inline'; }); applImgBox.onload = () => { URL.revokeObjectURL(applImgBox.src); }; editApplPictHiddenFileBtn.addEventListener('change', function() { var fileURL = editApplPictHiddenFileBtn.files[0]; if (fileURL) { applImgBox.src = URL.createObjectURL(fileURL); editApplPictHiddenUploadBtn.style.display = 'inline'; } }); // Android uses "font boosting" that messes up the spacing // None of the proscribed workarounds work (max-height:1000000px; text-size-adjust: none;) // Workaround: reduce the space between characters var isAndroid = (navigator.userAgent.indexOf('Android') > 0); if (isAndroid) { var filterCells = document.getElementsByClassName('filterNameCell'); for (var filterCell of filterCells) { filterCell.classList.add('androidFont'); } } // If the user accepted cookies, save the settings in a cookie when the user closes the window window.addEventListener("pagehide", function(event) { var cookieValue = getCookie(CookieSettings); if (cookieValue != '') { saveSettingsCookie(); } }); } // Initialize init();