/* Javascript for the identification page */ // More scripts are in many class folders /* GLOBAL CONSTANTS */ // Field indexes var StartX = 0; var ClassX = 1; var SubClassX = 2; // Not used var OrderX = 3; // Not used var FamilyX = 4; var SeriesX = 5; var TaxonomyFieldNames = ['','class_','sub_class','order_','family_']; var TaxonomyEnglishNames = ['','class','sub-class','order','family','series']; // More indexes var SeriesPassesFilterListX = 1; var SeriesNameListX = BrandNameListX = 0; var ManufHasPartsX = 2; // Strings var ANY_BRAND_STR = ' (any brand)'; // Misc var ManufApplMaxChars = 19; // Each item in the list of manufacturers and applications is limited to this number of characters var AttrNames = ['acc', 'struct', 'topo', 'curr', 'vtg', 'spd']; var FullAttrPostFix = 'Full'; // Each node has copies of its attributes in properties whose name ends with this postfix var ApplCodes = ['ind', 'loc', 'env', 'prod', 'dev', 'wir']; // Codes for all applications in the Select by function page var AttrCodes = ['acc', 'topo', 'wire', 'term', 'ckt', 'struct', 'curr', 'hybr', 'vtg', 'spd', 'panl', 'mate']; // Codes for all attributes in the Select by function page /* GLOBAL FIXED DATA */ // 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 /* 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 * * * * - subItemDict: activeNodeObj.ls * * * * - uses: activeNodeObj.uses * x x x - // none, script, db, or both done: activeNodeObj.done * x x x - // 1 if Davide finished this node in the book keywords: activeNodeObj.kw x x x x * */ // Present node var activeNodeCode = ''; // Code for the presently selected node var activeNodeUses = ''; // What this node uses for script and database: '' = don't know yet; 'none' = neither (not even from a parent node); 'script' = just a node script (no database); 'db' = just a database (no script); 'both' = script and database var activeNodeObj = null; var activeNodePath = ''; // Path to the folder for the presently selected node var activeNodeIsFamily = false; // Set if the presently selected node is at the family level var activeCompSeries = ''; // Name of the selected component series. clear if no component series is selected // Script and/or database node. It could be this node, or it could be a parent node var dbScriptNodeCode = ''; // Code the node that has the database and/or script for this node var dbScriptNodePath = ''; // Folder with the filter and/or database for this node (may be an ancestor node) // Book pages var bookPagesNodePath = ''; // Folder with the image of the book pages for this node (may be an ancestor node) // Component database var dataBaseLoaded = false; // Set if the database used by this node is loaded var manufDict = {}; // Dictionary with one item for each manufacturer: a sorted list of that manufacturer's series' name and a flag set if it passes the filter var sortedManufList = []; // Sorted list of manufacturer names var sortedCompList = []; // Sorted list of components var connSeriesData = []; // Dictionary with one item per connector series var componentCopyList = ''; // List of component series, to be copied to the panel // Script var scriptLoaded = false; // Set if the script used by this node is loaded. Note that we can't use scriptDOM_Node because its value is set when we first request the script, before it's actually loaded var scriptDOM_Node = ''; // DOM node in the document that has the script for the present node (do not confuse DOM nodes (in the HTML document) and nodes in the connectors tree) // Navigation var initFilterURI = ''; // Initial URI for the filter controls; saved because we can't use at init; we can only use it after the script is loaded var initApplURI = ''; // Initial URI for the application selectors var initAttrURI = ''; // Initial URI for the attribute selectors var initFindURI = ''; // Initial URI 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 // List of applications var applCompileDict = {}; // Dictionary with one item for each application listed in a database var dbNodesList = []; // List of all nodes with a database, used only when compiling a dictionary of applications var createApplDict = false; // Assume that do not need to create a list of applications. To create one, simply delete the applDict.json from the "classes' folder // ************ UTILITIES ************ // *** Get the id of the selected radio button function getRadioBtnId( radioBtnsName) { var checkedRadioBtnId = ''; var btnList = document.getElementsByName(radioBtnsName); for (var btnNo=0; btnNo 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' + 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]; if (activeNodeObj.uses) { // The tree file specifies what this node uses. If it uses a script or database, it's in this node activeNodeUses = activeNodeObj.uses; dbScriptNodeCode = activeNodeCode; } else { // The tree file doesn't specify what this node uses, which means that an ancestor node does. Go look for it var thisNodeAncestors = nodeAncestorTable[activeNodeCode]; for (var ancestorNo = thisNodeAncestors.length -1; ancestorNo >= 0; ancestorNo--) { // Determine what this ancestor uses dbScriptNodeCode = thisNodeAncestors[ancestorNo]; var ancestorNodeDef = eval(dbScriptNodeCode); if (ancestorNodeDef.uses) { // This ancestor specifies what it uses activeNodeUses = ancestorNodeDef.uses; break; // Stop looking } } } dbScriptNodePath = getPathForNode(dbScriptNodeCode); // 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 var thisNodeAncestors = nodeAncestorTable[activeNodeCode]; for (var ancestorNo = thisNodeAncestors.length -1; ancestorNo >= 0; ancestorNo--) { // Determine if this ancestor has the image of book pages about it var ancestorNodeCode = thisNodeAncestors[ancestorNo]; var ancestorNodeDef = eval(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 } // 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 updtFiltering) updtNavigTree(); updtMainTabs(); if(dbScriptNodeCode == oldDbScriptNodeCode) { // The node with the script and/or database stayed the same // We already have all the info to show the component list // Update the list of components for this node if (activeNodeUses != 'script') { // This node uses just a database or both a script and a database. // In either case, we already have the database (it hasn't changed from the last node), so we display its stuff now // If a series is selected, show it if (activeCompSeries != '') { // A component series is selected // Show the selected series showSelectedSeries(); } else { // No component series is selected // Update the filtering updtFiltering(); } } // Update the selects in the filter by manufacturer or application if (dataBaseLoaded) { updtManufApplSelects(); } } else { // The node with the script and/or database changed // We don't yet have the info to show the component list // Request the script and/or database from the server, asynchronously // After we receive them, process them // This node's stuff will be displayed after it loads // Note that the code to process the script is a function defined within this function, but the code to process the database is in the ajax function // Remove any previously populated applications, in case this node doesn't use a database applicationSel.options.length = 1; // 1 = leave the "Any" option in place // First deal with the script // Remove any old script from the document if (scriptLoaded) { // If the class script is loaded document.body.removeChild[scriptDOM_Node]; scriptLoaded = false; // Flag that there is no script } // If this node uses a script, request it if (valInList(activeNodeUses, ['script','both'])) { console.log('reqest script'); // Create a script and add it to the document var classScript = document.createElement('script'); var jsURL = dbScriptNodePath + 'js.txt'; classScript.setAttribute('src', jsURL); scriptDOM_Node = document.body.appendChild(classScript); // Save the DOM node with the script so we can remove it later // Later, after the script loads, execute this function classScript.onload = function () {scriptDoneLoading();} } // Then deal with the database // Flag that we don't have a database dataBaseLoaded = false; connSeriesData = []; // Clear the dictionary of components // If this node uses a database, request it if (valInList(activeNodeUses, ['db','both'])) { console.log('reqest db'); // Request the database var dbURL = dbScriptNodePath + 'db.csv'; var jsonFile = new XMLHttpRequest(); jsonFile.open("GET",dbURL,true); jsonFile.send(); // Later, after the database loads, execute this function jsonFile.onreadystatechange = function() {dbDoneLoading(jsonFile);} // Show or hide dbFilterBox.style.display = 'block'; // Show or hide the filter for manufacturer and application, depening on the switch } else { // This node has no database dbFilterBox.style.display = 'none'; // Hide the filter for manufacturer and application componentSeriesBox.innerHTML = 'No components database'; } } // The node with the script and/or database changed /* // Turn on or off the filter switch // Browse or search: disabled // Navigate: // We still have not applied the filter presets from the URI at init: on // Same script as previous node: leave as it was // The script changed: off if (mainTab_N.checked) { if (dbScriptNodeCode != oldDbScriptNodeCode) { setFilterSwState('off'); } if (initFilterURI != '') { setFilterSwState('on'); } } */ // Show the filter box appropriate for what this node uses if (activeNodeUses == 'none') { scriptAndDbFilterBox.style.display = 'none'; noFilterDiv.style.display = 'block'; } else { scriptAndDbFilterBox.style.display = 'block'; noFilterDiv.style.display = 'none'; dbFilterBox.style.display = (activeNodeUses != 'script')? 'block' : 'none'; scriptFilterBox.style.display = (activeNodeUses != 'db')? 'block' : 'none'; } // Except for init, update the URI if (typeof doUpdtURI == "undefined") { doUpdtURI = true; } if (doUpdtURI) { updateURI(); } } // goToNode // *** The script finished loading, asynchronously function scriptDoneLoading( ) { console.log('***** CLASS SCRIPT ' + dbScriptNodeCode + ' LOADED *****'); // The script for this node finished loading, asynchronously // Flag it scriptLoaded = true; // Load the HTML for the filter scriptFilterBox.innerHTML = window['filterBoxHTML']; // Set the titles shown when hovering over selects in the filter box var filterSelects = filterBox.getElementsByTagName('select'); for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { var aSelect = filterSelects[selectNo]; var selIndex = aSelect.selectedIndex; if (selIndex > 0) { var selectTitle = aSelect.options[selIndex].title; if (selectTitle !== '') { aSelect.title = selectTitle; } } } // If we just started, and the URI specified filter settings, preset the filter controls if (initFilterURI !== '') { // If have presets // Extract the presets var filterStateFields = initFilterURI.split('-'); initFilterURI = ''; // Flag that we have done so, so we don't do it again // Do the selects var selectIndexList = filterStateFields[0]; var CodeA = 'A'.charCodeAt(0); var filterSelects = filterBox.getElementsByTagName('select'); for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { var selectCode = selectIndexList.charAt(selectNo); var selectedIndex = 10 + selectCode.charCodeAt(0) - CodeA; if (isNumber(selectCode)) { selectedIndex = Number(selectCode); } filterSelects[selectNo].selectedIndex = selectedIndex; } // Do the radio buttons, text boxes, and checkboxes var checkedValCode = filterStateFields[1]; var checkedValList = parseInt(checkedValCode, 16); var textStateList = filterStateFields[2].split('|'); var filterInputs = filterBox.getElementsByTagName('input'); var textFieldNo = textStateList.length; for (var inputNo = filterInputs.length -1; inputNo >=0 ; inputNo--) { switch (filterInputs[inputNo].type) { case 'checkbox': case 'radio': filterInputs[inputNo].checked = ((checkedValList %2) === 1); checkedValList = parseInt(checkedValList/2); break; case 'text': filterInputs[inputNo].value = textStateList[--textFieldNo]; break; } } } // Initialize the visibility of the filter controls initFilterControls(); // Defined in the individual node scripts // Complete the processing that is done when everything this node needs has been loaded nodeDoneLoading(); } // *** The database finished loading, asynchronously function dbDoneLoading( jsonFile) { if (jsonFile.readyState == 4 && jsonFile.status === 200) { console.log('dbDoneLoading and ready'); // Place the data into a dictionary var dbLines = jsonFile.responseText.split(/\r\n|\r|\n/); // Get the field names var fieldNames = dbLines[0].split('\t'); var noOfFields = fieldNames.length; // Convert the text file to a dictionary for (var lineNo = 1; lineNo < dbLines.length; lineNo++) { var recordFields = dbLines[lineNo].split('\t'); if (recordFields.length === noOfFields) { var connSeries = recordFields[0]; // The first field is the unique key for the record if (connSeries !== '') { // Skip blank records connSeriesData[connSeries] = []; // Store each field for this component series for (var fieldNo = 1; fieldNo < noOfFields; fieldNo++) { var fieldData = recordFields[fieldNo].trim(); if (isNumber(fieldData)) { fieldData = Number(fieldData); } connSeriesData[connSeries][fieldNames[fieldNo]] = fieldData; } } } } // Flag that the database is loaded dataBaseLoaded = true; var noOfRecords = Object.keys(connSeriesData).length; console.log('***** DATABASE ' + dbScriptNodeCode + ' LOADED ***** ' + noOfRecords + ' items'); // Validate the values of the nodes for this component series var missingNodeCodes = ''; for (var connSeries of Object.keys(connSeriesData)) { var connRecord = connSeriesData[connSeries]; for (var taxLvlNo = 3; taxLvlNo < TaxonomyFieldNames.length; taxLvlNo++) { var fieldName = TaxonomyFieldNames[taxLvlNo]; var nodeCode = connRecord[fieldName]; if (!nodeAncestorTable.hasOwnProperty(nodeCode)) { missingNodeCodes += '
' + connSeries + ':' + nodeCode; } } } if (missingNodeCodes != '') { debugText.innerHTML = 'Undefined node(s) in db: ' + missingNodeCodes; } // Prepare the filters based on info in the database // Create sorted list of manufacturers and applications var compList = []; // List of components var manufList = []; // Unsorted list of manufacturer names var standardsList = []; // Unsorted list of regulatory standards var manufName, manufNo, newOption; // Grouped here to pass the validator manufDict = {}; // Clear the dictionary of manufacturers, in case we already had one for a different connector type var connSeriesKeys = Object.keys(connSeriesData); for (var seriesNo = 0; seriesNo < connSeriesKeys.length; seriesNo++) { // for (var connSeries of Object.keys(connSeriesData)) doesn't work inIE var connSeries = connSeriesKeys[seriesNo] var connRecord = connSeriesData[connSeries]; // Component compList.push(connSeries); // Manufacturer manufName = connRecord['manuf_name']; if (manufName !== '') { // If there's a manufacturer if (manufList.indexOf(manufName) === -1) { // If we haven't done this manufacturer yet manufList.push(manufName); manufDict[manufName] = [[],[], true]; // Create two arrays, one for the name of the series, one for a flag to indicate whether that series passes the filter; also, a flag that indicates that this manufacturer has parts that meet the filter } // Series manufDict[manufName][SeriesNameListX].push(connSeries); manufDict[manufName][SeriesPassesFilterListX].push(true); // Assume it passes the filter } // Application var anApplication = connRecord['application_']; if (anApplication !== '') { // If an application is specified // Create a record for this component, then add it to the list of components for this application var componentInfo = { NodeCode: activeNodeCode, CompName: connSeries, ManufName: manufName, HasCompPict: (connRecord['comp_pict'] == 'Y'), HasApplPict: (connRecord['appl_pict'] == 'Y'), }; // If the dictionary of applications doesn't have a list for this application, create one if (!(anApplication in applCompileDict)) { applCompileDict[anApplication] = []; // Create a list for this application } // Add this component to the list for this application applCompileDict[anApplication].push(componentInfo); // If specified, add a second application var secondApplication = connRecord['application_2']; if (secondApplication !== '') { // If the dictionary of applications doesn't have a list for this application, create one if (!(secondApplication in applCompileDict)) { applCompileDict[secondApplication] = []; // Create a list for this application } applCompileDict[secondApplication].push(componentInfo); } } // Standard var regStandardName = connRecord['standard_']; if ((regStandardName !== '') && (standardsList.indexOf(regStandardName) === -1)) { // If we haven't done this standard yet standardsList.push(regStandardName); } } sortedManufList = sortList(manufList); sortedCompList = sortList(compList); var sortedStandardsList = sortList(standardsList); // Manufacturers with multiple brands // Prepare a list of manufacturers with multiple brands var prevManufNameFirstWord = ''; var multiBrandManufList = []; for (manufNo = 0; manufNo < sortedManufList.length; manufNo++ ) { manufName = sortedManufList[manufNo]; var manufNameWordList = manufName.split(' '); if (manufNameWordList.length > 1) { var manufNameFirstWord = manufNameWordList[0]; if (prevManufNameFirstWord == manufNameFirstWord) { // If this name appears twice in a row, then it's a manufacturer with multiple brands if (multiBrandManufList.indexOf(manufNameFirstWord) === -1) { // If we haven't done this manufacturer yet multiBrandManufList.push(manufNameFirstWord); } } prevManufNameFirstWord = manufNameFirstWord; } } // Sort the series list for each manufacturer for (manufNo = 0; manufNo < sortedManufList.length; manufNo++ ) { manufName = sortedManufList[manufNo]; var manufSeriesList = manufDict[manufName][SeriesNameListX]; var sortedManufSeriesList = sortList(manufSeriesList); manufDict[manufName][SeriesNameListX] = sortedManufSeriesList; } // Populate the pull-down list of standards if (document.getElementById('standardSel') !== null) { standardSel.options.length = 1; // Remove any previously populated standards. 1 = leave the "Standards?" option in place for (var applNo = 0; applNo < sortedStandardsList.length; applNo++ ) { newOption = document.createElement("option"); var croppedApplName = sortedStandardsList[applNo].substring(0,ManufApplMaxChars); newOption.text = croppedApplName; newOption.value = sortedStandardsList[applNo]; standardSel.add(newOption); } } // Link to let the user download the database downloadIcon.href = dbScriptNodePath + '/db.csv'; // Complete the processing that is done when everything this node needs has been loaded nodeDoneLoading(); // If creating a list of applications, do the next database; when done, spit out a stringified dictionary in the browser console if (createApplDict) { // We need to create a list of applications if (dbNodesList.length > 0) { // There are more nodes // Request the next node with a database to compile its list of applications var nextNodeCode = dbNodesList.pop(); var doStackNode = false; // Don't push this node into the stack var doUpdtURI = false; // Don't update the URI goToNode(nextNodeCode, doStackNode, doUpdtURI); } else { if (Object.keys(applCompileDict).length > 0) { // Turn it to a string var applCompileDictStr = JSON.stringify(applCompileDict); // Display it console.log(applCompileDictStr); // Flag that we're done createApplDict = false; } } } } // Status is OK } // dbDoneLoading // *** Everything required by this node has loaded (script and/or database) // Complete the processing function nodeDoneLoading( ) { console.log('nodeDoneLoading', arguments.callee.caller.name); var doneLoading = false; switch (activeNodeUses) { case 'script': //doneLoading = scriptLoaded; break; case 'db': doneLoading = dataBaseLoaded; break; case 'both': doneLoading = scriptLoaded && dataBaseLoaded; break; } if (doneLoading) { console.log('nodeDoneLoading excecute'); if (activeCompSeries == '') { // Update the list of components based on the present filter parameters updtFiltering(); } else { // Show to the selected series showSelectedSeries(); } // Update the selects in the filter by manufacturer or application updtManufApplSelects(); } } // *** Update the selects in the filter by manufacturer or application // Called when the database finishes loading, or when a node changes and the database is already loaded function updtManufApplSelects( ) { console.log('updtManufApplSelects', arguments.callee.caller.name); // Prepare lists of manufacturers and applications of the item in the database that pass the filters var applList = []; // Unsorted list of applications var manufList = []; // Unsorted list of applications for (var compName of Object.keys(connSeriesData)) { // For each item in the database var connRecord = connSeriesData[compName]; if (connRecord['passes_fltr']) { // If it passes the filter // Manufacturer var manufName = connRecord['manuf_name']; if (manufList.indexOf(manufName) === -1) { // If we haven't done this application yet manufList.push(manufName); } // Application var applField = connRecord['application_']; if (applField !== '') { // If applications are specified // Extract each application (in case there is more than one) var applicList = applField.split(','); for (var anApplication of applicList) { anApplication = anApplication.trim(); if (applList.indexOf(anApplication) === -1) { // If we haven't done this application yet applList.push(anApplication); } } } } } // Populate the select of manufacturers manufNameSel.options.length = 1; // Remove any previously populated manufacturers. 1 = leave the "Any" option in place manufList = sortList(manufList); // Manufacturers with multiple brands var prevManufNameFirstWord = ''; var multiBrandManufList = []; manufList = sortList(manufList); for (var manufName of manufList) { var manufNameWordList = manufName.split(' '); if (manufNameWordList.length > 1) { var manufNameFirstWord = manufNameWordList[0]; if (prevManufNameFirstWord == manufNameFirstWord) { // If this name appears twice in a row, then it's a manufacturer with multiple brands var manufNameAnyBrand = manufNameFirstWord + ANY_BRAND_STR; if (multiBrandManufList.indexOf(manufNameAnyBrand) === -1) { // If we haven't done this manufacturer yet multiBrandManufList.push(manufNameAnyBrand); } } prevManufNameFirstWord = manufNameFirstWord; } } manufList = manufList.concat(multiBrandManufList); // Populate the select of manufacturers manufList = sortList(manufList); for (var manufName of manufList) { newOption = document.createElement("option"); newOption.value = manufName; var croppedManufName = manufName.substring(0,ManufApplMaxChars); newOption.text = croppedManufName; manufNameSel.add(newOption); } // Populate the select of applications applicationSel.options.length = 1; // Remove any previously populated applications. 1 = leave the "Any" option in place applList = sortList(applList); for (var applName of applList) { newOption = document.createElement("option"); newOption.text = newOption.value = applName; applicationSel.add(newOption); } } // updtManufApplSelects // *** 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; } descriptionTxtBox.innerHTML = 'MISSING NODE DATA'; // Assume that this node is not defined var descrTxt = ''; var nodeCodeDict = nodesDict[nodeCode]; if (nodeCodeDict) { // Get a dictionary of the subnodes (none if this is a family node) var subItemDict = nodeCodeDict.ls; // Images var imgPath = getPathForNode(nodeCode); // Top image showImage(imgPath, 'i', 'prodImgBox'); // Bottom image 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(subItemDict) { // If a node other than family if (Object.keys(subItemDict).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'; } } } if (itemNo == -1) { // Not hovering into a Navigate pane // Show the book pages if (bookPagesNodePath != '') { imgPath = bookPagesNodePath; bottomImgName = 'b'; applImgCursor = 'pointer'; } } } applImgBox.style.cursor = applImgCursor; showImage(imgPath, bottomImgName, 'applImgBox'); // Description var itemNoStr = ''; if (itemNo) { if (itemNo > 0) { itemNoStr = itemNo + ': '; } } descrTxt = '' + itemNoStr + nodeCodeDict.nm + '
' + nodeCodeDict.ds; descriptionTxtBox.innerHTML = descrTxt; // Attributes icons for (var attrName of AttrNames) { showAttr(nodeCodeDict, attrName); } } } // *** Show the attributes for a node function showAttr( nodeObj, attrName) { // Get this node's string for this attribute var fullAttrName = attrName + FullAttrPostFix; if (fullAttrName in nodeObj) { var attrStr = nodeObj[fullAttrName]; // Show or hide this attribute's icons var iconList = document.getElementsByName(attrName); // All the icons for this attribute for (var attrIcon of iconList) { var attrCode = attrIcon.id.slice(-1); // Get the last lette it the icon's ID. E.g., for topoIcon_w, return w if (attrStr.includes(attrCode)) { attrIcon.style.visibility = 'visible'; attrIcon.style.opacity = 1; } else { attrIcon.style.opacity = 0.2; } } } } // ************ 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 subItemDict = activeNodeObj.ls; var subItemNodeList = Object.keys(subItemDict); 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(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", function(){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", function(){showNodeInfo('','',0);}); // This fails the Javascript validator: "Don't make functions within a loop." aTagElement.addEventListener("click", function(){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"; } } // ************ 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(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 URI updateURI(); } // ************ COMPONENT SERIES FUNCTIONS ************ // *** Go to the selected component series function goToSelectedSeries( ){ console.log('goToSelectedSeries', arguments.callee.caller.name) if (activeNodeIsFamily) { // At the family level: push this family node in the stack backNodesStack.push(activeNodeCode); goBackBtn.src = 'img/goback.gif'; // Enable the nbavigation button //console.log('________BACK', backNodesStack) } else { // Not at the family level: jump to the node for that series' family var connRecord = connSeriesData[activeCompSeries]; var seriesFamilyNodeCode = connRecord['family_']; var doStackNode = true; // Push this node into the stack goToNode(seriesFamilyNodeCode, doStackNode); // If does not stack or clear activeCompSeries because we're not at the family level } // Turn off the filter setFilterSwState('off'); // Show the selected series showSelectedSeries(); // Update the URI updateURI(); } // *** Show the selected component series function showSelectedSeries( ){ console.log('showSelectedSeries', arguments.callee.caller.name) // Update the navigation tree and the selection tabs updtNavigTree(activeCompSeries); // Show only the one component series in the list componentSeriesBox.innerHTML = '
' + activeCompSeries + '
'; // Show info for the series showSeriesInfo(activeCompSeries); } // *** Show a component series' little bit of data that is available whel browsing by application function showSeriesShortInfo( nodeCode, compName, manufName, hasCompPict, hasApplPict) { console.log('showSeriesShortInfo', nodeCode, compName, manufName, hasCompPict, hasApplPict, arguments.callee.caller.name); // Show the description text var compDestrStr = 'Manuf.: ' + manufName + '
' + 'Series: ' + compName; descriptionTxtBox.innerHTML = compDestrStr; // Show the pictures //var compNodePath = getPathForNode(nodeCode); var pictName = compName.split('('); // ) added it so that the text editor doesn't complain about an unmatched parenthesis pictName = pictName[0].trim(); pictName = pictName.replace('/',''); // Remove the slash used in some part numbers var compImgSrc = applImgSrc = 'img/missing.jpg'; if (hasCompPict) { //compImgSrc = compNodePath + 'compimg/' + pictName + '.jpg'; compImgSrc = 'compimg/' + pictName + '.jpg'; } prodImgBox.src = compImgSrc; if (hasApplPict) { //applImgSrc = compNodePath + 'applimg/' + pictName + '.jpg'; applImgSrc = 'applimg/' + pictName + '.jpg'; } applImgBox.src = applImgSrc; } // *** Show a component series' data function showSeriesInfo( connSeries) { console.log('showSeriesInfo', connSeries, arguments.callee.caller.name); // If no connector series is specified, use the selected one var shownConnSeries = (activeCompSeries == '')? connSeries : activeCompSeries; // Navigation tree updtNavigTree(connSeries); // Default pictures var descrTxt = 'Loading data'; var compImgSrc = 'img/loading.jpg'; var applImgSrc = 'img/loading.jpg'; if (dataBaseLoaded && (shownConnSeries !== '')) { // If the cursor entered over the family name // Show a component series' description // Common information: Manufacturer, series, Vendor, Family var connRecord = connSeriesData[shownConnSeries]; var descrTxt = ''; // Manufacturer var manufName = connRecord['manuf_name']; if (manufName != '') { if (manufName.startsWith('_')) { // Industry-standard connector // Make a list of all the manufactures that make this industry-standard connector descrTxt += 'Industry standard'; var manufList = connRecord['manuf_list']; if (manufList && (manufList != '')) { descrTxt += '
'; } descrTxt += '
'; } else { // Proprietary connector descrTxt += 'Manuf: ' + manufName + '
'; } } // Series var manufLink = connRecord['manuf_link']; var connSeriesLink = shownConnSeries; if ((manufLink !== '') && (manufLink.indexOf('http') === 0)) { connSeriesLink = '
' + shownConnSeries + ''; } // A.k.a. descrTxt += 'Series: ' + connSeriesLink; if ((typeof connRecord['aka_'] != "undefined") && (connRecord['aka_'] !== '')) { descrTxt += ', a.k.a. ' + connRecord['aka_']; } // Specs var pdfLink = connRecord['pdf_link']; if ((pdfLink !== '') && (pdfLink.indexOf('http') === 0)) { descrTxt += ' [pdf]'; } // Obsolete var isObsolete = connRecord['obsolete_']; if (isObsolete) { descrTxt += ' (obsolete)'; } descrTxt += '
'; // Vendor var vendorLink = connRecord['vendor_link']; if ((vendorLink !== '') && (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 + '
'; } // Class specific information if (scriptLoaded) { //If starting from a link that is directly to the indentified part, the js file for this type of component may still be loading descrTxt += showSeriesDescription(shownConnSeries); // Defined in the file specific for this type of components } // Application and notes var applicationStr = connRecord['application_']; if (applicationStr !== '') { descrTxt += 'Application: ' + applicationStr + '
'; } var theNotes = connRecord['notes_']; if (theNotes !== '') { descrTxt += 'Notes: ' + theNotes + '
'; } // Pictures if (connRecord['appl_pict']) { // The DB specifies the component and application images in two separate fields // Extract just the first word in the series name, as the name of the pictures var pictName = shownConnSeries.split('('); // ) added it so that the text editor doesn't complain about an unmatched parenthesis pictName = pictName[0].trim(); pictName = pictName.replace('/',''); // Remove the slash used in some part numbers switch (connRecord['comp_pict']) { case 'Y': //compImgSrc = dbScriptNodePath + '/compimg/' + pictName + '.jpg'; compImgSrc = 'compimg/' + pictName + '.jpg'; break; case 'x': compImgSrc = 'img/obsolete.jpg'; break; default: compImgSrc = 'img/missing.jpg'; break; } switch (connRecord['appl_pict']) { case 'Y': //applImgSrc = dbScriptNodePath + '/applimg/' + pictName + '.jpg'; applImgSrc = 'applimg/' + pictName + '.jpg'; break; case 'x': applImgSrc = 'img/obsolete.jpg'; break; default: applImgSrc = 'img/missing.jpg'; break; } } else { // The DB specifies the component and application images in a single field switch (connRecord['pict_']) { case 'Y': // Extract just the first word in the series name, as the name of the pictures var pictName = shownConnSeries.split('('); // ) added it so that the text editor doesn't complain about an unmatched parenthesis pictName = pictName[0].trim(); pictName = pictName.replace('/',''); // Remove the slash used in some part numbers //compImgSrc = dbScriptNodePath + '/compimg/' + pictName + '.jpg'; //applImgSrc = dbScriptNodePath + '/applimg/' + pictName + '.jpg'; compImgSrc = 'compimg/' + pictName + '.jpg'; applImgSrc = 'applimg/' + pictName + '.jpg'; break; case 'x': compImgSrc = applImgSrc = 'img/obsolete.jpg'; break; case '.': case '': compImgSrc = applImgSrc = 'img/missing.jpg'; break; } } } // Show the description and the pictures descriptionTxtBox.innerHTML = descrTxt; prodImgBox.src = compImgSrc; applImgBox.src = applImgSrc; } // *** For classes with a database: update the component series list connectors; if the filter is on, use it // Update the list of components based on the present filter parameters // Called when done loading the script, the database, or both (as appropriate). Also called when the user changes a filter or clears the filters. // Returns a list of nodes that are represented in the database and, if the node uses a script and the filter is on, that pass the filter // Only called if a database is loaded function updtCompList( nodesThatPassTopFltrs) { console.log('updtCompList', nodesThatPassTopFltrs, arguments.callee.caller.name); var subItemCode, selNo, manufName, connSeries, manufNo, seriesNo; var selectorMap = []; // Prepare a map to translate from the class taxonomy to the selectors or pictures in the navigation pane if (!activeNodeIsFamily) { // For stages that use selectors // Sub item dictionary var subItemDict = activeNodeObj.ls; // Map the item codes to the order of its selector selNo = 0; for (subItemCode in subItemDict) { if(subItemDict.hasOwnProperty(subItemCode)) { selectorMap[subItemCode] = selNo; selNo++; } } } // Get the user selections var fltPar = {}; var manufNameFltr = ''; var applicationFltr = ''; if (filterSwitch.checked) { // Selected units, tolerance var selectedUnits = unitsSel.value; var dimsTolPct = pitchTolSel.value; // Show the units var dimensionUnitsSpans = document.getElementsByName('dimensionUnitsSpan'); var selectedUnitsHtml = '' + selectedUnits + ' ± ' + dimsTolPct + '%'; for (var spanNo = 0; spanNo < dimensionUnitsSpans.length; spanNo++) { dimensionUnitsSpans[spanNo].innerHTML = selectedUnitsHtml; } // Filters specific to this type of components if (activeNodeUses != 'db') { // Don't do it if this node doesn't use a filter fltPar = prepFilterParams(selectedUnits,dimsTolPct); } // Manufacturer, application manufNameFltr = manufNameSel.value; applicationFltr = applicationSel.value; } // Filter each series // Do each item in the database var subListCodeFieldName = TaxonomyFieldNames[nodeAncestorTable[activeNodeCode].length +1]; // +1 to get the sublist var activeStage = activeNodeObj.ancestorList.length; // 0 = root; 1 = class; 2 = subclass, etc. var subNodesStage = activeStage + 1; // Stage level for the subnodes of the active node var nodesRepresented = []; // List of nodes that were found in the database among the parts that pass all the filters for (manufNo = 0; manufNo < sortedManufList.length; manufNo++ ) { // For each manufacturer // Do this manufacturer manufName = sortedManufList[manufNo]; // Defaults manufDict[manufName][ManufHasPartsX] = false; // Assume that this manufacturer doesn't have any series that are in the class or meet the filter // Do each series that this manufacturer makes var nodeAncestorList = [...nodeAncestorTable[activeNodeCode]]; // Make a shallow copy of the list of ancestor of this node nodeAncestorList.push(activeNodeCode); // Add the node itself to the list var manufSeriesList = manufDict[manufName][SeriesNameListX]; for (seriesNo = 0; seriesNo < manufSeriesList.length; seriesNo++ ) { // Do this series from this manufacturer connSeries = manufSeriesList[seriesNo]; var connRecord = connSeriesData[connSeries]; // Assume that this series doesn't meet the filter manufDict[manufName][SeriesPassesFilterListX][seriesNo] = false; // Manufacturer dictionary connSeriesData[connSeries]['passes_fltr'] = false; // Component dictionary // Filter by taxonomy: show only the components that belong to the active node and its subnodes // (Because the database also has components that belong to other nodes.) // Done whether or not the filter is on var showComponent = true; for (var aStage = 1; aStage < nodeAncestorList.length; aStage++) { if (connRecord[TaxonomyFieldNames[aStage]] !== nodeAncestorList[aStage]){ showComponent = false; break; } } if (!showComponent) {continue;} if (filterSwitch.checked) { // The filter is on // Filter by taxonomy: show only the components that belong to the subnodes that pass the common filters // Done only if the filter is on if ((nodesThatPassTopFltrs != 'All') && (activeStage < FamilyX) ) { var itsNodePassesTopFltr = false; for (var levelNo = subNodesStage; levelNo <= FamilyX; levelNo++) { var taxCode = connRecord[TaxonomyFieldNames[levelNo]]; if (nodesThatPassTopFltrs.includes(taxCode)) { itsNodePassesTopFltr = true; break; } } if (!itsNodePassesTopFltr) { continue } } // Filter by manufacturer, application if (manufNameFltr !== '') { var isMultiBrand = (manufNameFltr.indexOf(ANY_BRAND_STR) !== -1); if (isMultiBrand) { var fltrManufNameFirstWord = manufNameFltr.replace(ANY_BRAND_STR,''); var dbManufNameWordList = connRecord['manuf_name'].split(' '); if (dbManufNameWordList.length > 1) { var dbManufNameFirstWord = dbManufNameWordList[0]; if (fltrManufNameFirstWord != dbManufNameFirstWord) {continue;} } else { continue; } } else { if (connRecord['manuf_name'] != manufNameFltr) {continue;} } } if (applicationFltr !== '') { if (connRecord['application_'] !== applicationFltr) {continue;} } // Apply filters specific to this type of component if (activeNodeUses != 'db') { // Don't do it if this node doesn't use a filter if (!filterSeries(connSeries, connRecord, fltPar)) {continue;} // Implemented in the script for nodes that use a script } } // This series is included manufDict[manufName][SeriesPassesFilterListX][seriesNo] = true; manufDict[manufName][ManufHasPartsX] = true; // Flag that this manufacturer has at least one part that meets the filter connSeriesData[connSeries]['passes_fltr'] = true; // Flag if this sub-item is included in the database if (!activeNodeIsFamily) { // For stages that use selectors subItemCode = connRecord[subListCodeFieldName]; // Get the code for this item if (!(nodesRepresented.includes(subItemCode))) { // If not in the list nodesRepresented.push(subItemCode); // Add it } } } // End of "for each series for a manufacturer" } // End of "for each manufacturer" // List the nodes that pass the top filters and that have components that pass the node-specific filters // That is what we will return var nodesThatPass = []; if (nodesThatPassTopFltrs = 'All') { nodesThatPass = nodesRepresented; } else { for (var aNode of nodesThatPassTopFltrs) { if (nodesRepresented.includes(aNode)) { nodesThatPass.push(aNode); } } } // Show the list of component series if (dataBaseLoaded) { // Fill the select // Start the select // 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 var seriesListHTML = ''; var noOfSeries = 0; componentCopyList = '* MANUF. - SERIES\n'; var showManuf = showManufChkBx.checked; if (showManuf) { // List by manufacturer for (manufNo = 0; manufNo < sortedManufList.length; manufNo++) { // For each manufacturer // Do this manufacturer manufName = sortedManufList[manufNo]; if (manufDict[manufName][ManufHasPartsX]) { // If this manufacturer has at least one part that meets the filter // Add an option for this manufacturer seriesListHTML += '
' + manufName + '
'; // Do each series for this manufacturer var seriesNameList = manufDict[manufName][SeriesNameListX]; for (seriesNo = 0; seriesNo < seriesNameList.length; seriesNo++) { // Do this series if (manufDict[manufName][SeriesPassesFilterListX][seriesNo]) { // If this series passes the filter // Count this series, so we can show how many results noOfSeries++; // Add an option for this series. connSeries = seriesNameList[seriesNo]; var seriesNameOption = document.createElement('option'); seriesListHTML += '
' + connSeries + '
'; // Clipboard list var connRecord = connSeriesData[connSeries]; var manufLink = manufName; var manufURL = connRecord['manuf_link']; if ((manufURL !== '') && (manufURL.indexOf('http') === 0)) { manufLink = '[' + manufName + '](' + manufURL + ')'; // Social media link, not HTML link } var seriesLink = connSeries; var pdfURL = connRecord['pdf_link']; if ((pdfURL !== '') && (pdfURL.indexOf('http') === 0)) { seriesLink = '[' + seriesLink + '](' + pdfURL + ') (pdf)'; // Social media link, not HTML link } var vendorLink = ''; var vendorURL = connRecord['vendor_link']; if ((vendorURL !== '') && (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 = '[' + vendorName + '](' + vendorURL + ')'; // Social media link, not HTML link } componentCopyList += '* ' + [manufLink, seriesLink, vendorLink].filter(Boolean).join(' - ') + '\n'; // .filter(Boolean) removes blank strings } } } } } else { // List without manufacturers for (compNo = 0; compNo < sortedCompList.length; compNo++ ) { // For each component // Do this component var connSeries = sortedCompList[compNo]; if (connSeriesData[connSeries]['passes_fltr']) { // If this series passes the filter // Add an option for this series. var seriesNameOption = document.createElement('option'); seriesListHTML += '
' + connSeries + '
'; noOfSeries++; } } } // Show the the list or note that there are no results if (valInList(activeNodeUses, ['db','both'])) { if (noOfSeries === 0) { // No results componentSeriesBox.innerHTML = 'Nothing matches the filter'; activeCompSeries = ''; } else { // There are results componentSeriesBox.innerHTML = seriesListHTML; } } else { componentSeriesBox.innerHTML = 'No components database'; } // Show the the number of items that passes the filter noOfSeriesSpan.innerHTML = noOfSeries; } // If this class doesn't use a database, clear the series selection // <<< is this required? if (!valInList(activeNodeUses, ['db', 'both'])) { activeCompSeries = ''; } // Return either 'all' or a list of nodes that are represented in the database and, if the node uses a script and the filter is on, that pass the filter return nodesThatPass; } // updtCompList // ************ USER ACTIONS IN MAIN WINDOW ************ // *** 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 a component series is selected: stay in this same family node if (activeCompSeries == '') { // No series is selected var 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(newNode); var subItemDict = downLvlNodeDef.ls; if (subItemDict) { var subNodeList = Object.keys(subItemDict); 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 series function backBtnClick( ) { console.log('backBtnClick'); if (backNodesStack.length > 0) { // There are items to go back to // Save this node or component series in the forward stack in case the user wants to go back to it var pushItem = (activeCompSeries == '')? activeNodeCode : [activeNodeCode, activeCompSeries]; // If a component series, push a list with the node code and the series name fwdNodesStack.push(pushItem); goFwdBtn.src = 'img/gofwd.gif'; // Enable the navigation button // Go to the previously visited node or component series 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 series, this clears activeCompSeries } else { // It's a component series. Go back to it // Extract the node code and the component series name from the list of 2 items var nodeCode = backItem[0]; goToNode(nodeCode, doStackNode); // This clears activeCompSeries activeCompSeries = backItem[1]; // We'll go to that series after the database is loaded } } } // *** 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 series var fwdItem = fwdNodesStack.pop(); if (typeof fwdItem == 'string') { // It's a node, Go back to it activeCompSeries = ''; // Deselect any selected component series var doStackNode = true; // Push this node into the stack goToNode(fwdItem, doStackNode); } else { // It's a component series. Go back to it var nodeCode = fwdItem[0]; activeCompSeries = fwdItem[1]; goToSelectedSeries(); } // 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( ) { console.log('filterSwitchClick', activeCompSeries); //if (mainTab_N.checked) { // <<< Only for the navigate mode // Sow or hide the filter filterBox.style.display = filterSwitch.checked? 'block' : 'none'; // Deselect any selected component series if (activeCompSeries == '') { // 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 updtFiltering(); // Update the URI updateURI(); } else { // A component is selected // Go to the family node, deselecting the component goToNode(activeNodeCode); } //} } // *** The user opened the filter settings box function openFilterSettingsClick( ) { filterSettingsDialog.style.display = 'block'; // Update the URI updateURI(); } // *** The user closed the filter settings box function closeFilterSettingsClick( ) { filterSettingsDialog.style.display = 'none'; // Update the URI updateURI(); } // *** The user want 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; } } } // Update selectChanged(); } // *** The user want to close all the selectors in a box function closeSelectors( selectorBox) { console.log('closeSelectors'); var triangleCheckBoxes = selectorBox.querySelectorAll('input[type="checkbox"]'); for (var triangleCheckBox of triangleCheckBoxes) { triangleCheckBox.checked = false; } } // *** The user cleared the filter box function clearFilterBoxClick( ) { console.log('clearFilterBoxClick'); // Clear all the selects var filterSelects = filterSetBox.getElementsByTagName('select'); for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { filterSelects[selectNo].selectedIndex = 0; } // Clear all the radio buttons, text boxes, and checkboxes var filterInputs = scriptAndDbFilterBox.getElementsByTagName('input'); for (var inputNo = 0; inputNo < filterInputs.length; inputNo++) { if (filterInputs[inputNo].type === 'radio') { if (filterInputs[inputNo].id.includes('None')) { filterInputs[inputNo].checked = true; } } if (filterInputs[inputNo].type === 'text') { filterInputs[inputNo].value = ''; } if (filterInputs[inputNo].type === 'checkbox') { filterInputs[inputNo].checked = false; } } // Initialize the visibility of the filter controls if (['script','both'].includes(activeNodeUses)) { // This node uses a script initFilterControls(); // Defined in the individual node scripts } // Update the list of components (if this node uses a database) and enable the selectors, based on the present filter parameters updtFiltering(); // Update the URI updateURI(); } // *** The user selected an item in the list of components series function compSeriesClick( compSeriesName){ console.log('compSeriesClick', compSeriesName) // Go to that component series activeCompSeries = compSeriesName; goToSelectedSeries(); } // *** The user clicked the Navigation, Browse, or Search tab function selectMainTabClick( clickedTab) { console.log('selectMainTabClick'); // Update the navigation tree and the selection tabs // updtNavigTree(); updtMainTabs(); // Update the URI updateURI(); } // *** The user clicked a "Navigate" tab: Names, Traits, Pictures, Flowchart function navigateModeTabClick( ){ console.log('navigateModeTabClick'); // Update the navigation tabs updtNavigTabs(); // Update the URI updateURI(); } // *** Show the image and description for a parameter for a radio button function showParam( imgName, descrTxt) { // Description descriptionTxtBox.innerHTML = descrTxt; // Image var imgSrc = 'img/dot.png'; // Default image if (imgName != '') { imgSrc = 'img/select/' + imgName + '.jpg'; } prodImgBox.src = imgSrc; applImgBox.src = 'img/dot.png'; } // *** Clear the image and description function clearParamDescr( ) { // Description descriptionTxtBox.innerHTML = ''; // Image prodImgBox.src = 'img/dot.png'; } // *** See if a node or its children nodes pass filters, recursively function filterChildNodesRecurs( nodeCode, fltrObj, anyNodesThatPassA) { var nodePasses = true; var anyNodesThatPass = []; // First check this node var resultList = filterNode(nodeCode, fltrObj, 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) { var 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, fltrObj, 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 localFltrObj = JSON.parse(JSON.stringify(fltrObj)); // 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(localFltrObj)) { var filterVal = localFltrObj[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 localFltrObj[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(localFltrObj).length == 0); // Return: resultFlg localFltrObj 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, localFltrObj, localFoundAttrs]; } // *** Recursive function to find which nodes pass the select filters function selectConnRecrsv( nodeCode, fltrObj, superNodeFoundAttr) { var resultList = filterNode(nodeCode, fltrObj, superNodeFoundAttr); var resultFlg = resultList[0]; var nodeObj = resultList[1]; var subNodeFiltrObj = resultList[2]; var nodeFoundAttrs = resultList[3]; var thisNodesThatPass = []; // resultFlg == 0: This node fails a filter that it was given if (resultFlg == 1) { // This node passes all the filters that it was given thisNodesThatPass.push(nodeCode); } if (resultFlg == -1) { var subNodesThatPass = []; var subNodesAllPass = false; // This node doesn't pass all the filters that it was given if (nodeObj.ls) { // This node has subnodes // Do each subnode for (var subNodeCode in nodeObj.ls) { var subNodesThatPassSubNode = selectConnRecrsv( subNodeCode, subNodeFiltrObj, nodeFoundAttrs); if (subNodesThatPassSubNode.length > 0) { subNodesThatPass = subNodesThatPass.concat(subNodesThatPassSubNode); } } // If all of the subnodes pass, add this node to the list of nodes that pass; else, add the subnodes that pass subNodesAllPass = (subNodesThatPass.length == Object.keys(nodeObj.ls).length); if (subNodesAllPass) { for (var aNodeThatPasses of subNodesThatPass) { if (!(aNodeThatPasses in nodeObj.ls)) { subNodesAllPass = false; break; } } } } else { // Error: a family should never require to look at its subnodes // That means that the tree file did not define this attribute for this branch of the tree console.log('//** MISSING ** ', nodeCode, fltrObj); } //if (nodeCode == 'rectangular_conn') { console.log('^^^', subNodesAllPass, subNodesThatPass.length, Object.keys(nodeObj.ls).length)} if (subNodesAllPass) { thisNodesThatPass.push(nodeCode); } else { thisNodesThatPass = thisNodesThatPass.concat(subNodesThatPass); } } return thisNodesThatPass; } // *** The user made a selection function selectChanged( dontUpdURI) { console.log('selectChanged', dontUpdURI, arguments.callee.caller.name); // Main attributes // Get the selectors' values and prepare a filter object var rootFiltrObj = {}; // Test /* for (var applCode of ApplCodes) { rootFiltrObj[applCode] = ''; } for (var attrCode of AttrCodes) { rootFiltrObj[attrCode] = ''; } */ // By application for (var applCode of ApplCodes) { var applSelVal = document.querySelector('input[name=' + applCode + 'ApplBtns]:checked').value; if (applSelVal != '-') { rootFiltrObj[applCode] = applSelVal; } } // By attributes for (var attrCode of AttrCodes) { var attrSelVal = document.querySelector('input[name=' + attrCode + 'AttrBtns]:checked').value; if (attrSelVal != '-') { rootFiltrObj[attrCode] = attrSelVal; } } // Find which nodes pass the select filters, recursively var rootFoundAttrs = []; // To store filters that we found var rootNodesThatPass = selectConnRecrsv( 'classes', rootFiltrObj, rootFoundAttrs) /* // Prepare a list of the nodes that pass all the filters // Do each class var rootFoundAttrs = []; // To store filters that we found // Check the root node var resultList = filterNode('classes', rootFiltrObj, rootFoundAttrs); var resultFlg = resultList[0]; var rootNodeObj = resultList[1]; var classFiltrObj = resultList[2]; var classFoundAttrs = resultList[3]; var rootNodesThatPass = []; if (resultFlg == 1) { rootNodesThatPass.push('classes'); } if (resultFlg == -1) { // Do each class var classNodesThatPass = []; for (var classCode in rootNodeObj.ls) { // This class passed. Check this class resultList = filterNode(classCode, classFiltrObj, classFoundAttrs); // If doesn't pass, returns an empty object resultFlg = resultList[0]; var classNodeObj = resultList[1]; var subClassFiltrObj = resultList[2]; var subClassFoundAttrs = resultList[3]; // If this class passes the filter. add it to the list of nodes that pass if (resultFlg == 1) { classNodesThatPass.push(classCode); } if (resultFlg == -1) { // This class passes some of the filters that it was given. Do each subclass in this class var subClassNodesThatPass = []; for (var subClassCode in classNodeObj.ls) { // Check this subclass resultList = filterNode(subClassCode, subClassFiltrObj, subClassFoundAttrs); resultFlg = resultList[0]; var subClassNodeObj = resultList[1]; var orderFiltrObj = resultList[2]; var orderFoundAttrs = resultList[3]; // If this subclass passes the filter. add it to the list of nodes that pass if (resultFlg == 1) { subClassNodesThatPass.push(subClassCode); } if (resultFlg == -1) { // This subclass passes some of the filters that it was given. Do each order in this subclass var orderNodesThatPass = []; for (var orderCode in subClassNodeObj.ls) { // Check this order resultList = filterNode(orderCode, orderFiltrObj, orderFoundAttrs); resultFlg = resultList[0]; var orderNodeObj = resultList[1]; var familyFiltrObj = resultList[2]; var familyFoundAttrs = resultList[3]; // If this order passes the filter. add it to the list of nodes that pass if (resultFlg == 1) { orderNodesThatPass.push(orderCode); } if (resultFlg == -1) { // This order passes some of the filters that it was given. Do each family in this order var familyNodesThatPass = []; for (var familyCode in orderNodeObj.ls) { // Check this family resultList = filterNode(familyCode, familyFiltrObj, familyFoundAttrs); resultFlg = resultList[0]; // If this family passes the filter. add it to the list of nodes that pass if (resultFlg == 1) { familyNodesThatPass.push(familyCode); } if (resultFlg == -1) { // This family doesn't pass all the filters that it was given. That's wrong. // List the undefined attributes var undefinedAttrsObj = resultList[2]; var undefinedAttrs = Object.keys(undefinedAttrsObj) if (undefinedAttrs.length > 0) { for (var undefinedAttr of undefinedAttrs) { console.log('//** MISSING ** ' + undefinedAttr + ' ' + familyCode); } } } } // If all of the subnodes pass, add this node to the list of nodes that pass; else, add the subnodes that pass var orderSubNodesAllPass = (familyNodesThatPass.length == Object.keys(orderNodeObj.ls).length); if (orderSubNodesAllPass) { orderNodesThatPass.push(orderCode); } else { orderNodesThatPass = orderNodesThatPass.concat(familyNodesThatPass); } } } // If all of the subnodes pass, add this node to the list of nodes that pass; else, add the subnodes that pass var subClassSubNodesAllPass = (orderNodesThatPass.length == Object.keys(subClassNodeObj.ls).length); if (subClassSubNodesAllPass) { subClassNodesThatPass.push(subClassCode); } else { subClassNodesThatPass = subClassNodesThatPass.concat(orderNodesThatPass); } } } // If all of the subnodes pass, add this node to the list of nodes that pass; else, add the subnodes that pass var classSubNodesAllPass = (subClassNodesThatPass.length == Object.keys(classNodeObj.ls).length); if (classSubNodesAllPass) { classNodesThatPass.push(classCode); } else { classNodesThatPass = classNodesThatPass.concat(subClassNodesThatPass); } } } // If all of the subnodes pass, add this node to the list of nodes that pass; else, add the subnodes that pass var rootSubNodesAllPass = (classNodesThatPass.length == Object.keys(rootNodeObj.ls).length); if (rootSubNodesAllPass) { rootNodesThatPass.push('classes'); } else { rootNodesThatPass = rootNodesThatPass.concat(classNodesThatPass); } } */ // Display the list of the nodes that pass all the filters noOfNodesSpan.innerHTML = rootNodesThatPass.length; var boxHTML = (rootNodesThatPass.length == 0)? 'None': ''; for (var nodeCode of rootNodesThatPass) { var nodeObj = nodesDict[nodeCode]; var nodeName = nodeObj.nm; boxHTML += '
' + nodeName + '
'; } nodeList.innerHTML = boxHTML; // Update the URI if (typeof dontUpdURI == 'undefined') { updateURI(); } } // *** 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'; } // ************ USER ACTIONS FROM NODE SCRIPTS ************ // *** The user changed a filter parameter // Update the list of components (if this node uses a database) and enable the selectors, based on the present filter parameters // Called by node scripts for nodes that also use a database // Temporary, until all the node scripts are corrected to use filterChanged function updCompList( ) { console.log('updCompList *** UPDATE this node script ***', arguments.callee.caller.name); updtFiltering(); // Update the URI updateURI(); } // *** The user changed a filter parameter // Called by node scripts for nodes that do not use a database function filterChanged( ) { console.log('filterChanged', arguments.callee.caller.name); updtFiltering(); // Update the URI updateURI(); } // *** The user changed a filter parameter // Update the list of components (if this node uses a database) and enable the selectors, based on the present filter parameters // 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 updtFiltering( ) { console.log('updtFiltering', arguments.callee.caller.name); var subNodesThatPass = 'All'; // List of nodes at this sub-level that pass the filter (to enable the selectors on the left) var nodesThatPassTopFltrs = 'All'; // List of nodes at any level that pass the top filter (to select the components in the list) var descendantNodesThatPass = 'All'; // List of nodes at any level that pass the top filter if (filterSwitch.checked) { // Top filters, common to all nodes // Get the selectors' values and prepare a filter object var filtrObj = {}; // Applications //var applNames = []; for (var selCode of ApplCodes) { var selectObj = document.getElementById(selCode + 'ApplFltrSel'); var selectIndex = selectObj.selectedIndex; if (selectIndex > 0) { var selectVal = selectObj.options[selectIndex].value; if (selectVal != '-') { filtrObj[selCode] = selectVal; var applName = selectObj.options[selectIndex].text; //applNames.push(applName); } } } // Attributes for (var selCode of AttrCodes) { var selectObj = document.getElementById(selCode + 'AttrFltrSel'); var selectIndex = selectObj.selectedIndex; if (selectIndex > 0) { var selectVal = selectObj.options[selectIndex].value; if (selectVal != '-') { filtrObj[selCode] = selectVal; } } } /* // If possible, select the database application selector the same as the application selectors in the top filter // E.g., if the "Product" select is set for "RC model", and the database application selector has an "RC model" item, select it if (applicationSel) { for (var selNo = 1; selNo < applicationSel.options.length; selNo++) { if (applNames.includes(applicationSel.options[selNo].text)) { applicationSel.selectedIndex = selNo; break; } } }*/ if (Object.keys(filtrObj).length > 0) { // The user selected some filters // Check this node's ancestors var resultList = retNodeObj = classFiltrObj = null; var ancestorList = activeNodeObj.ancestorList; var doneChecking = false; subNodesThatPass = []; for (var ancestorNodeCode of ancestorList) { resultList = filterNode(ancestorNodeCode, filtrObj, subNodesThatPass, []); resultFlg = resultList[0]; if (resultFlg == 1) { // This ancestor node passes all the filters. // Therefore, the active node and all its children pass the filter subNodesThatPass = 'All'; doneChecking = true; break; } if (resultFlg == 0) { // This ancestor node fails a filter. // Therefore, no nodes pass the filter doneChecking = true; break; } // if resultFlg is -1, this node passes some of the filters; therefore, we can check some more } // If not all filters were checked, check the active node if (!doneChecking) { resultList = filterNode(activeNodeCode, filtrObj, subNodesThatPass, []); resultFlg = resultList[0]; doneChecking = (resultFlg != -1); // If the active node passes only some of the filters that it was given, flag that we're not done if (resultFlg == 1) { // The active node passes all the filters. subNodesThatPass = 'All'; // Therefore, the active node and all its children pass the filter } } // So far, the nodes that pass the top filter are the same as the sub-nodes: either none or all nodesThatPassTopFltrs = subNodesThatPass; // If not all filters were checked, check the active node's children if (!doneChecking) { // Find which children nodes pass the select filters, recursively var descendantNodesThatPass = selectConnRecrsv(activeNodeCode, filtrObj, []); if (descendantNodesThatPass == activeNodeCode) { descendantNodesThatPass = 'All'; } // Find which subnodes pass the select filters, recursively var childrenDict = activeNodeObj.ls; if (childrenDict) { // This is not a family node for (var childNodeCode in childrenDict) { var resultList = filterChildNodesRecurs(childNodeCode, filtrObj, nodesThatPassTopFltrs); var childPasses = resultList[0]; var childNodesThatPass = resultList[1]; if (childPasses) { subNodesThatPass.push(childNodeCode); } } } } } // Filters specific to the active node var nodesThatPassNodeSpec = 'All'; if (activeNodeUses == 'script') { // This node uses only a script. // Ask it for a list of which nodes pass the filter if (scriptLoaded) { if (typeof getNodesThatPass != 'undefined') { nodesThatPassNodeSpec = getNodesThatPass(); } else { // Temporary, until all the node scripts are corrected to use filterParamChange console.log('getFilteredClassCodes *** UPDATE this node script ***'); nodesThatPassNodeSpec = getFilteredClassCodes(); } } } else if (activeNodeUses != 'none') { // This node uses a database, (and may use a script) // Update the list of components based on the present filter parameters // Also, get a list of which nodes pass the filter if(dataBaseLoaded) { // Don't do it before receiving the database // Update the component series list connectors nodesThatPassNodeSpec = updtCompList(descendantNodesThatPass); // subNodesThatPass } } // Find the common set between the nodes that pass the top filter and those that pass the filters specific to the active node // Do nothing: // All, All -> All (do nothing) // [...], All -> [...] (do nothing) // [], All - [] (do nothing) // [], [...] - [] (do nothing) // [], [] - [] (do nothing) // Do something: // All, [...] -> [...] (swap) // (any), [] - [] (clear) // [...], [...] -> common set if ((nodesThatPassNodeSpec != 'All') && (subNodesThatPass != [])) { // Do something if (subNodesThatPass == 'All') { // Only the node-specific filters have a limited list subNodesThatPass = nodesThatPassNodeSpec; // Swap } else if (nodesThatPassNodeSpec == []) { // the node-specific filters have 0 result subNodesThatPass = []; // Clear } else { // Both the general filters and the node-specific filters have a limited list // Find the common set of nodes that pass var commonNodesThatPass = []; for (var aNode of subNodesThatPass) { if (nodesThatPassNodeSpec.indexOf(aNode) != -1) { commonNodesThatPass.push(aNode); } } subNodesThatPass = commonNodesThatPass; } } } else { // The filter switch is off if(dataBaseLoaded) { // Don't do it before receiving the database updtCompList('All'); } } // Enable only the selectors in the left column for nodes that are applicable given the script and/or the database var subItemCode, selNo, manufName, connSeries, manufNo, seriesNo, selectorID; var selectorMap = []; // Sub item dictionary var preFix = (navigTab_P.checked)? 'selPict' : ((navigTab_N.checked)? 'selNamesBtn' : 'selTraitsBtn'); // Do each selector var selectorNo = 0; var subItemDict = activeNodeObj.ls; for (subItemCode in subItemDict) { if(subItemDict.hasOwnProperty(subItemCode)) { var classPassesFilter = (valInList(subItemCode,subNodesThatPass) || (subNodesThatPass == 'All')); // (subNodesThatPass.includes(subItemCode)) doesn't work in IE var selectorEnabled = (!filterSwitch.checked) || classPassesFilter; if (navigTab_F.checked) { if (typeof flowchartObj != 'undefined') { // Flowchart selectorID = flowchartObj.contentDocument.getElementById(subItemCode); // <<< 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 !!!!!!',subItemCode) } } else { // Menu item or picture 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'); } } } selectorNo++; } } } // ************ UPDATE THE DISPLAY FUNCTIONS ************ // *** Update the navigation tree and the selection tabs // Called if the node changes, or the user selects a component series function updtNavigTree( connSeries) { console.log('updtNavigTree', connSeries, arguments.callee.caller.name); // Update the navigation tree at the top // Left arrow var thisNodeAncestors = nodeAncestorTable[activeNodeCode]; downLevelArrow.style.visibility = (thisNodeAncestors.length == 0)? 'hidden' : 'visible'; // Links to the ancestor nodes var nodeAncestorList = [...nodeAncestorTable[activeNodeCode]]; // Shallow copy, so we don't affect the table var nodeDepth = nodeAncestorList.length; var itemHTML, navTreeLbl, clickHTML; for (var navTreeLblNo = 0; navTreeLblNo < nodeDepth; navTreeLblNo++) { var nodeCode = nodeAncestorList[navTreeLblNo]; var nodeDict = nodesDict[nodeCode]; var nodeName = nodeDict.nm; clickHTML = 'onClick="goToNode(\'' + nodeCode + '\');"'; itemHTML = '' + nodeName + ''; navTreeLbl = eval('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 series is selected var thisNodeName = activeNodeObj.nm; navTreeLbl = eval('navTreeLbl' + nodeDepth); navTreeLbl.style.display = 'inline-block'; if (activeCompSeries == '') { // No component series selected: show the present node without a link clickHTML = ''; navTreeLbl.style.cursor = 'auto'; navTreeLbl.style.textDecoration = 'none'; // Clear the name of the selected components series navTreeLbl5.innerHTML = ''; navTreeLbl5.style.display = 'none'; } else { // A component series is selected (this must be a family level node): show the present node with a link clickHTML = 'onClick="goToNode(\'' + activeNodeCode + '\');"'; // Deselect the component series navTreeLbl.style.cursor = 'pointer'; navTreeLbl.style.textDecoration = 'underline'; nodeDepth = 5; // Name of the selected components series navTreeLbl5.innerHTML = activeCompSeries; navTreeLbl5.style.display = 'inline-block'; } navTreeLbl.innerHTML = '' + thisNodeName + ''; if (typeof connSeries == 'undefined') { // Not browsing the component list // Hide the items that haven't yet been selected for (var navTreeLblNo = nodeDepth +1; navTreeLblNo <= SeriesX; navTreeLblNo++) { var navTreeLbl = eval('navTreeLbl' + navTreeLblNo); navTreeLbl.style.display = 'none'; } // Arrows for (var arrowNo = 1; arrowNo <= 5; arrowNo++) { var navTreeArrow = eval('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 connRecord = connSeriesData[connSeries]; for (var navTreeLblNo = nodeDepth +1; navTreeLblNo <= SeriesX-1; navTreeLblNo++) { var taxonomyFieldName = TaxonomyFieldNames[navTreeLblNo]; var nodeCode = connRecord[taxonomyFieldName]; var lvlName = ''; try { var lvlDict = nodesDict[nodeCode]; lvlName = lvlDict.nm; } catch (e) { lvlName = '' + nodeCode + ' missing'; } var navTreeLbl = eval('navTreeLbl' + navTreeLblNo); navTreeLbl.innerHTML = lvlName; navTreeLbl.style.display = 'inline-block'; navTreeLbl.style.cursor = 'auto'; navTreeLbl.style.textDecoration = 'none'; } navTreeLbl5.innerHTML = connSeries; navTreeLbl5.style.display = 'inline-block'; // Show all the arrows for (var arrowNo = 1; arrowNo <= 5; arrowNo++) { var navTreeArrow = eval('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 (var 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 = nodeAncestorTable[activeNodeCode].length - ClassX; var chopWidth = extraWidth / noOfElementsToShrink; for (navTreeLblNo = 1; navTreeLblNo < noOfElementsToShrink +1; navTreeLblNo++) { var navTreeLbl = eval('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 selects a component series function updtMainTabs( connSeries) { console.log('updtMainTabs', connSeries, arguments.callee.caller.name); // Show the selection level stageNoTxt.innerHTML = nodeAncestorTable[activeNodeCode].length; // 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 var goHome = true; // Navigate mode if (mainTab_N.checked) { updtNavigTabs(); goHome = false; } if (mainTab_B.checked) { // Browse by pictures mode updtBrowseByPictsTab(); } // Select by function mode if (mainTab_S.checked) { updtSelectTabs(); } // Find by term mode if (mainTab_F.checked) { updtFindTabs(); } // For some modes, if at some intermediate level, go home if (goHome && (activeNodeCode != 'classes')) { // Don't do goToNode('classes') because that in turn calls this function again // Change the node and store global info about it activeNodeCode = 'classes'; activeNodeObj = nodesDict[activeNodeCode]; activeNodeIsFamily = false; // Finish storing global info about this node activeNodePath = getPathForNode(activeNodeCode); // Show or hide the "identified" box scriptAndDbFilterBox.style.display = 'none'; noFilterDiv.style.display = 'block'; activeNodeObj = nodesDict[activeNodeCode]; activeNodeUses = 'none'; dbScriptNodeCode = activeNodeCode; dbScriptNodePath = getPathForNode(dbScriptNodeCode); // Show the info for this node showNodeInfo(activeNodeCode, -1); // -1 = show info on the present node updtNavigTree(); // Remove any old script from the document if (scriptLoaded) { // If the class script is loaded document.body.removeChild[scriptDOM_Node]; scriptLoaded = false; // Flag that there is no script } // Flag that we don't have a database dataBaseLoaded = false; connSeriesData = []; // Clear the dictionary of components dbFilterBox.style.display = 'none'; // Hide the filter for manufacturer and application componentSeriesBox.innerHTML = 'No components database'; } // Change the name of the page depending on whether identifying or selecting a connector identTitleSpan.style.display = (mainTab_N.checked || mainTab_F.checked)? 'inline' : 'none'; browseTitleSpan.style.display = (mainTab_B.checked)? 'inline' : 'none'; selectTitleSpan.style.display = (mainTab_S.checked)? 'inline' : 'none'; } // *** Set the state of the filter switch function setFilterSwState( switchState) { console.log('setFilterSwState~~~~', switchState, arguments.callee.caller.name); switch (switchState) { case 'disabled': filterSwitch.disabled = true; filterSwitch.checked = false; filterBox.style.display = 'none'; filterLabel.style.opacity = '50%'; break; case 'off': filterSwitch.disabled = false; filterSwitch.checked = false; filterBox.style.display = 'none'; filterLabel.style.opacity = '100%'; break; case 'on': filterSwitch.disabled = false; filterSwitch.checked = true; filterBox.style.display = 'block'; filterLabel.style.opacity = '100%'; break; } } // *** Update the navigate tab // Called at init if the selection mode is "navigate" or if the user changes to the navigate selection mode function updtNavigTabs( ) { console.log('updtNavigTabs', arguments.callee.caller.name); if (navigTab_N.checked) { // Update the select by names tab // If this is the first time this tab was opened, fill its contents if (navigationNamesSelectorBox.innerHTML == '') { navigationNamesSelectorBox.innerHTML = updtNamesOrTraits(false); // False = names mode; } } if (navigTab_T.checked) { // Update the select by traits tab // If this is the first time this tab was opened, fill its contents if (navigationTraitsSelectorBox.innerHTML == '') { navigationTraitsSelectorBox.innerHTML = updtNamesOrTraits(true); // Tue = traits mode; } } 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'); var boxHTML = ''; if (!activeNodeIsFamily) { // Below family level // Instructions var nodeEnglishName = TaxonomyEnglishNames[nodeAncestorTable[activeNodeCode].length +1]; boxHTML = 'Identify the ' + nodeEnglishName + '. Click on a picture.'; // Find the dictionaries and the path to the image, depending on the stage var subItemDict = activeNodeObj.ls; var imgPath = getPathForNode(activeNodeCode); // Create a list of pictures and show it var selNo = 0; for (var subItemCode in subItemDict) { if(subItemDict.hasOwnProperty(subItemCode)) { if (subItemCode.substr(0,4) !== 'sel_') { // Except for the helper categories in the Class stage var itemName = ''; var itemDict = eval(subItemCode); if (itemDict) { itemName = itemDict.nm; } var imgSrc = imgPath + subItemCode + '/i.jpg'; boxHTML += '' + itemName + ''; selNo++; } } } boxHTML = '
' + 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'); var boxHTML = ''; if (!activeNodeIsFamily) { // Below family level // Instructions var nodeEnglishName = TaxonomyEnglishNames[nodeAncestorTable[activeNodeCode].length +1]; boxHTML = 'Identify the ' + nodeEnglishName + '. 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) { updtFiltering(); } */ } // updtNavigTabs // *** Update the selection box for the names or traits mode // Returns the HTML for the tab function updtNamesOrTraits( isTraits) { console.log('updtNamesOrTraits', arguments.callee.caller.name, isTraits); var boxHTML = ''; if (!activeNodeIsFamily) { // Below family level // Instructions var nodeEnglishName = TaxonomyEnglishNames[nodeAncestorTable[activeNodeCode].length +1]; var traitsInstrStr = ''; if (isTraits) { traitsInstrStr = activeNodeObj.tq; } boxHTML = 'Identify the ' + nodeEnglishName + '.' + traitsInstrStr; // Create the selector list and show it var subItemDict = activeNodeObj.ls; if (subItemDict) { var selNo = 0; for (var subItemCode in subItemDict) { if(subItemDict.hasOwnProperty(subItemCode)) { var itemText = ''; if (isTraits) { // Traits selectors itemText = subItemDict[subItemCode]; } else { // Name selectors var itemDict = eval(subItemCode); if (itemDict) { itemText = itemDict.nm; } } var displayNo = selNo + 1; var btnIDbase = isTraits? 'selTraitsBtn' : 'selNamesBtn'; boxHTML += '
' + displayNo + ': ' + itemText + '
'; selNo++; } } } } // Return the HTML for the tab return boxHTML; } // *** 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('classes'); var classListDict = rootDef.ls; var boxHTML = ''; for (var classCode in classListDict) { if(classListDict.hasOwnProperty(classCode)) { // For every class var classDef = eval(classCode); var subClassListDict = classDef.ls; for (var subClassCode in subClassListDict) { if(subClassListDict.hasOwnProperty(subClassCode)) { // For every subclass in this class var subClassDef = eval(subClassCode); var orderListDict = subClassDef.ls; for (var orderCode in orderListDict) { if(orderListDict.hasOwnProperty(orderCode)) { // For every order in this subclass var orderDef = eval(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(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 Select tab function updtSelectTabs( ){ console.log('updtSelectTabs', arguments.callee.caller.name); // Application if (initApplURI != '') { for (var applCodeNo = 0; applCodeNo < ApplCodes.length; applCodeNo++) { var applCode = ApplCodes[applCodeNo]; var radioBtns = document.getElementsByName(applCode + 'ApplBtns'); var applURIltr = initApplURI[applCodeNo]; if (applURIltr != '-') { for (var radioBtn of radioBtns) { if (radioBtn.value == applURIltr) { radioBtn.checked = true; break; } } // Open this selector var radioBtnName = 'applFltrChkBox' + (applCodeNo + 1); document.getElementById(radioBtnName).checked = true; } } // Flag that we used the URI by clearing it initApplURI = ''; } // Attributes if (initAttrURI != '') { for (var attrCodeNo = 0; attrCodeNo < AttrCodes.length; attrCodeNo++) { var attrCode = AttrCodes[attrCodeNo]; var radioBtns = document.getElementsByName(attrCode + 'AttrBtns'); var attrURIltr = initAttrURI[attrCodeNo]; if (attrURIltr != '-') { for (var radioBtn of radioBtns) { if (radioBtn.value == attrURIltr) { radioBtn.checked = true; break; } } // Open this selector var radioBtnName = 'attrFltrChkBox' + (attrCodeNo + 1); document.getElementById(radioBtnName).checked = true; } } // Flag that we used the URI by clearing it initAttrURI = ''; } } // *** 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 = sortList(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 URI specifies a keyword, enter it if (initFindURI != '') { searchTextBox.value = initFindURI; searchText(initFindURI); } // If the Family level, hide the gallery //searchBoxContents.style.display = (activeNodeIsFamily)? 'none' : 'block'; } // ************ URI FUNCTIONS ************ // *** Update the URI function updateURI( ) { console.log('updateURI', arguments.callee.caller.name); // Prepare a new query // Selection mode var selectionMode = getSelectedTabCode('MainSelTabs'); var modeCode = 'm=' + selectionMode // Node and component var nodeCode = ((activeNodeCode != '') && (activeNodeCode != 'classes'))? 'n=' + activeNodeCode : ''; var compCode = (activeCompSeries != '')? 's=' + encodeURIComponent(activeCompSeries) : ''; // Selectors var filterCode = applParams = attrParams = findParams = ''; switch (selectionMode) { case 'N': // Navigatemode: filter modeCode += getSelectedTabCode('NavigSelTabs'); if (filterSwitch.checked) { filterCode = '&fl=' + getFilterCtrlsState(); } break; case 'S': // Select mode: application and attributes // By application applParams = 'a='; for (var applCode of ApplCodes) { var checkedSel = document.querySelector('input[name=' + applCode + 'ApplBtns]:checked'); if (checkedSel) { applParams += checkedSel.value; } else { applParams += '-'; } } // By attributes attrParams = 'c='; for (var attrCode of AttrCodes) { var checkedSel = document.querySelector('input[name=' + attrCode + 'AttrBtns]:checked'); if (checkedSel) { attrParams += checkedSel.value; } else { attrParams += '-'; } } break; case 'F': // Find mode: search box text // By Find if (searchTextBox.value != '') { findParams = 'k=' + searchTextBox.value; } break; } // Remove the old query var uri = window.location.href; if (uri.indexOf('?') > 0) { uri = uri.substring(0, uri.indexOf('?')); } // Prepare the new query uri += '?' + [modeCode, nodeCode, compCode, filterCode, applParams, attrParams, findParams].filter(Boolean).join('&'); // .filter(Boolean) removes blank strings // Replace the URL in the URL bar, without refreshing the page window.history.replaceState({}, document.title, uri); } // *** Copy the component list to the clipboard function copyComponentList( ) { var tempTextArea = document.createElement('textarea'); componentSeriesBox.appendChild(tempTextArea); tempTextArea.innerHTML = componentCopyList; tempTextArea.focus(); tempTextArea.select(); document.execCommand('copy'); tempTextArea.remove(); } // *** Get the filter state function getFilterCtrlsState( ) { // Do the selects var selectIndexList = ''; var CodeA = 'A'.charCodeAt(0); var filterSelects = filterBox.getElementsByTagName('select'); for (var selectNo = 0; selectNo < filterSelects.length; selectNo++) { var selectedIndex = filterSelects[selectNo].selectedIndex; var selectCode = selectedIndex.toString(); if (selectedIndex > 9) { selectCode = String.fromCharCode(CodeA + (selectedIndex - 10)); } selectIndexList += selectCode; } // Do the radio buttons, text boxes, and checkboxes var textState = ''; var checkedValList = 0; var filterInputs = filterBox.getElementsByTagName('input'); for (var inputNo = 0; inputNo < filterInputs.length; inputNo++) { switch (filterInputs[inputNo].type) { case 'checkbox': case 'radio': checkedValList *= 2; if (filterInputs[inputNo].checked) { checkedValList++; } break; case 'text': textState += '|'; textState += filterInputs[inputNo].value; break; } } // Return a string with the state of the filter return selectIndexList + '-' + checkedValList.toString(16) + '-' + textState; } // ************ 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 = ''; // If the dictionary of applications does not exist, start compiling it by creating a list of databases var xhr = new XMLHttpRequest(); var applDictURL = 'classes/applDict.json'; xhr.open('HEAD', applDictURL, false); xhr.send(); createApplDict = (xhr.status == '404'); // If the json file with the applications file does not exist, flag that we need to create one // 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([], 'classes', {}, seeAlsoCheckList, dbNodesList); var classListDict = rootNodeObj.ls; for (var classCode in classListDict) { // Compile data for this class var classNodeObj = compileNodeData(['classes'], classCode, rootNodeObj, seeAlsoCheckList, dbNodesList); // Do each subclass in this order var subClassListDict = classNodeObj.ls; for (var subClassCode in subClassListDict) { // Compile data for this subclass var subClassNodeObj = compileNodeData(['classes',classCode], subClassCode, classNodeObj, seeAlsoCheckList, dbNodesList); // Do each order in this subclass var orderListDict = subClassNodeObj.ls; for (var orderCode in orderListDict) { // Compile data for this order var orderNodeObj = compileNodeData(['classes',classCode, subClassCode], orderCode, subClassNodeObj, seeAlsoCheckList, dbNodesList); // Do each family in this order var familyListDict = orderNodeObj.ls; for (var familyCode in familyListDict) { // Compile data for this family compileNodeData(['classes',classCode, subClassCode,orderCode], familyCode, orderNodeObj, seeAlsoCheckList, dbNodesList); } } } } // Now, we take care of case #3 // Get any missing attributes from children nodes // No need to do the families, because they already have their attributes set (either defined in the tree file, or acquired from their parents) // Do the orders first, because all of their children have attributes. // When that's none, all orders have attributes, so we can do the subClasses // Then do the Classes and finally the root node for (var levelNo = OrderX; levelNo >= StartX; levelNo --) { compileAttrFromChildren(levelNo); } // 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 nodeAncestorTable)) { 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, dbNodesList) { // Create an object for this node var nodeObj = eval(nodeCode); if (typeof nodeObj == 'undefined') { // The tree file did not define this node debugText.innerHTML = 'Missing class: ' + 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; // Attributes // Each node has two copies of an attribute: // - The original one which may be specified in the tree file. Used when selecting all nodes with a given attribute. // - A copy that is taken from the tree file (if specified), its parents, or compiled from its children. Used when displaying the attributes of a single node. for (var attrName of AttrNames) { var fullAttrName = attrName + FullAttrPostFix; if (attrName in nodeObj) { // The tree file specifies this attribute for this node // Duplicate it as this node's full attribute nodeObj[fullAttrName] = nodeObj[attrName]; // Detect unnecessary data in the tree file: none of this node's ancestors should specify this attribute for (var ancestorCode of nodeAncestorList) { var ancestorObj = nodesDict[ancestorCode]; if (attrName in ancestorObj) { // An ancestor has this attribute unnecessarily console.log('//**** EXTRA ATTRIBUTE **** ' + attrName + ' ' + nodeCode); } } } else { if (fullAttrName in parentNodeObj) { // The tree file specifies this attribute for one of this node's acestors, which by now has propagated to this node's parent's full attribute // Save it as this node's full attribute nodeObj[fullAttrName] = parentNodeObj[fullAttrName]; } } } // 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]); } } // If we are creating a list of applications, if this node has a database, add it to the list if (createApplDict) { if (['db','both'].includes(nodeObj.uses)) { // This node uses a database dbNodesList.push(nodeCode); } } // Store the object for this node nodesDict[nodeCode] = nodeObj; } return nodeObj; } // *** Compile attributes from child nodes // Done at init; once for each order, then for each subclass, then for each class, then the root node function compileAttrFromChildren( levelNo) { // Each node has two copies of an attribute: // - The original one which may be specified in the tree file. Used when selecting all nodes with a given attribute. // - A copy that is taken from the tree file (if specified), its parents, or compiled from its children. Used when displaying the attributes of a single node. for (var nodeCode of Object.keys(nodesDict)) { var nodeObj = nodesDict[nodeCode]; if (nodeObj.ancestorList.length == levelNo) { // This node is at the specified level for (var attrName of AttrNames) { var fullAttrName = attrName + FullAttrPostFix; if (!(fullAttrName in nodeObj)) { // This node doesn't yet have a value for this full attribute // Get it by compiling it from its children, save it as this node's full attribute var attrStr = ''; for (var childCode in nodeObj.ls) { // Do this child var childNodeObj = nodesDict[childCode]; if (fullAttrName in childNodeObj) { var childAttrStr = childNodeObj[fullAttrName]; if (attrStr == '') { // First child: take its attribute string attrStr = childAttrStr; } else { // Following children: append any more letters that the first child didn't already have for (var attrCode of childAttrStr) { // For each letter in the child attribute string if (!attrStr.includes(attrCode)) { // If this letter is not already in the compiled string attrStr += attrCode; // Append this letter to the compiled string } } } } else { console.log('//**** MISSING ATTRIBUTE **** ',nodeCode, attrName) } } // Assign the compiled attribute as this node's full attribute nodeObj[fullAttrName] = attrStr; } } } } } // *** Initialize // Called only once at init function init( ) { console.log('init'); // From the tree file, compile the paths, pictures, and keywords for each node compileAllData(); // Read the query from the URI var urlParams = new URLSearchParams(window.location.search); // Selection mode var selectionMode = 'N'; var navigationSelectMode = 'N'; if (urlParams.has('m')) { var modeCode = urlParams.get('m'); selectionMode = modeCode[0]; if (selectionMode == 'N') { navigationSelectMode = modeCode[1]; } } // Node var initNodeCode = 'classes'; if (urlParams.has('n')) { var preselectNodeCode = urlParams.get('n'); if (preselectNodeCode in nodeAncestorTable) { initNodeCode = preselectNodeCode; } } // Component series if (urlParams.has('s')) { activeCompSeries = urlParams.get('s'); } // Filter if (urlParams.has('fl')) { initFilterURI = urlParams.get('fl'); // This is completed after the database is loaded } if (urlParams.has('f')) { initFilterURI = urlParams.get('f'); // This is completed after the database is loaded } if (initFilterURI != '') { setFilterSwState('on'); } // Application if (urlParams.has('a')) { initApplURI = urlParams.get('a'); // This is completed after the database is loaded } // Attribute if (urlParams.has('c')) { initAttrURI = urlParams.get('c'); // This is completed after the database is loaded } // Search keyword initFindURI = ''; if (urlParams.has('k')) { initFindURI = urlParams.get('k'); // This is completed after the database is loaded } // Select the tabs for the specified selection mode document.getElementById('mainTab_' + selectionMode).checked = true; document.getElementById('navigTab_'+ navigationSelectMode).checked = true; // Go to the initial node var doStackNode = false; // Don't push this node into the stack var doUpdtURI = false; // Don't update the URI // For backward compatibility, if the URI has a class name that is no longer used, go home instead if (!(initNodeCode in nodeAncestorTable)) { initNodeCode = 'classes'; initFilterURI = ''; } // However, if required, start compiling the applications by requesting the first database if (createApplDict) { initNodeCode = dbNodesList.pop(); } goToNode(initNodeCode, doStackNode, doUpdtURI); // For the Select By Function, select the list if (mainTab_S.checked) { selectChanged(); } // 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); } }); } // Initialize init();