PageSourceSearch

https://www.census.gov/etc.clientlibs/census/clientlibs/search.min.bc8c36a2d48742c6d573bcab45b10ff4.js

js census.gov collected 2026-09-24 06:23:45 UTC 38,912 bytes, 1,063 lines download raw bytes

1/*******************************************************************************
2 * Copyright 2020 Adobe
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16(function() {
17    "use strict";
18
19    window.CQ = window.CQ || {};
20    window.CQ.CoreComponents = window.CQ.CoreComponents || {};
21    window.CQ.CoreComponents.container = window.CQ.CoreComponents.container || {};
22    window.CQ.CoreComponents.container.utils = {};
23
24    /**
25     * Utilities for Container Components (accordion, tabs)
26     *
27     * @namespace
28     * @alias CQ.CoreComponents.container.utils
29     * @type {{}}
30     */
31    window.CQ.CoreComponents.container.utils = {
32
33        /**
34         * Removes the hash from the URL.
35         */
36        removeUrlHash: function() {
37            history.replaceState(undefined, undefined, " ");
38        },
39
40        /**
41         * Updates the URL hash with the panel ID without scrolling to it.
42         *
43         * @param {Object} component The container component (e.g. Accordion, Carousel, Tabs).
44         * @param {String} itemType The type of the item as defined in the component.
45         * @param {Number} index The index of the container item
46         */
47        updateUrlHash: function(component, itemType, index) {
48            if (component && component._elements && component._elements[itemType] &&
49                component._elements[itemType][index] && component._elements[itemType][index].id) {
50                var ID = component._elements[itemType][index].id;
51                history.replaceState(undefined, undefined, "#" + ID);
52            }
53        },
54
55        /**
56         * Returns the index of the component item (accordion, carousel, tabs) that:
57         * - either corresponds to the deep link in the URL fragment
58         * - or contains the element that corresponds to the deep link in the URL fragment
59         *
60         * @param {Object} component The container component (Accordion, Carousel or Tabs).
61         * @param {String} itemType The type of the item as defined in the component.
62         * @param {String} itemContentType The type of the item content as defined in the component.
63         * @returns {Number} the index within the items array if the item exists, -1 otherwise.
64         */
65        getDeepLinkItemIdx: function(component, itemType, itemContentType) {
66            if (window.location.hash) {
67                var deepLinkId = window.location.hash.substring(1);
68                if (deepLinkId && document.getElementById(deepLinkId) &&
69                    component && component._config && component._config.element &&
70                    component._elements[itemType] &&
71                    component._config.element.querySelector("[id='" + deepLinkId + "']")) {
72                    for (var i = 0; i < component._elements[itemType].length; i++) {
73                        var item = component._elements[itemType][i];
74                        var itemContentContainsId = false;
75                        if (component._elements[itemContentType]) {
76                            var itemContent = component._elements[itemContentType][i];
77                            itemContentContainsId = itemContent && itemContent.querySelector("[id='" + deepLinkId + "']");
78                        }
79                        if (item.id === deepLinkId || itemContentContainsId) {
80                            return i;
81                        }
82                    }
83                }
84                return -1;
85            }
86            return -1;
87        },
88
89        /**
90         * Returns the item of the container component (accordion, carousel, tabs) that:
91         * - either corresponds to the deep link in the URL fragment
92         * - or contains the element that corresponds to the deep link in the URL fragment
93         *
94         * @param {Object} component The Accordion or Tabs component.
95         * @param {String} itemType The type of the item as defined in the component.
96         * @param {String} itemContentType The type of the item content as defined in the component.
97         * @returns {Object} the item if it exists, undefined otherwise.
98         */
99        getDeepLinkItem: function(component, itemType, itemContentType) {
100            var idx = window.CQ.CoreComponents.container.utils.getDeepLinkItemIdx(component, itemType, itemContentType);
101            if (component && component._elements && component._elements[itemType]) {
102                return component._elements[itemType][idx];
103            }
104        },
105
106        /**
107         * Scrolls the browser on page reload (if URI contains URI fragment) to the item of the container component (accordion, tabs)
108           that corresponds to the deep link in the URI fragment.
109         * This method fixes the issue existent with Chrome and related browsers, which are not scrolling on page reload (if URI contains URI fragment)
110           to the element that corresponds to the deep link in the URI fragment.
111         * Small setTimeout is needed, otherwise the scrolling will not work on Chrome.
112         */
113        scrollToAnchor: function() {
114            setTimeout(function() {
115                if (window.location.hash) {
116                    var id = decodeURIComponent(window.location.hash.substring(1));
117                    var anchorElement = document.getElementById(id);
118                    if (anchorElement && anchorElement.offsetTop) {
119                        anchorElement.scrollIntoView();
120                    }
121                }
122            }, 100);
123        }
124    };
125}());
126
127var $ = jQuery.noConflict();
128
129$(document).ready(function(){
130    var $searchBox = $("#searchInput");
131    Shadowbox.init({
132            handleOversize: "drag",
133            modal: true
134        });        
135    // set StateGeo 
136    if(google.loader.ClientLocation){
137        var stateMap = {'ak':'alaska','al':'alabama','ar':'arkansas','az':'arizona','ca':'california','co':'colorado','ct':'connecticut','de':'delaware','fl':'florida','ga':'georgia','hi':'hawaii','ia':'iowa','id':'idaho','il':'illinois','in':'indiana','ks':'kansas','ky':'kentucky','la':'louisiana','ma':'massachusetts','md':'maryland','me':'maine','mi':'michigan','mn':'minnesota','mo':'missouri','ms':'mississippi','mt':'montana','nc':'north_carolina','nd':'north_dakota','ne':'nebraska','nh':'new_hampshire','nj':'new_jersey','nm':'new_mexico','nv':'nevada','ny':'new_york','oh':'ohio','ok':'oklahoma','or':'oregon','pa':'pennsylvania','ri':'rhode_island','sc':'south_carolina','sd':'south_dakota','tn':'tennessee','tx':'texas','ut':'utah','va':'virginia','vt':'vermont','wa':'washington','wi':'wisconsin','wv':'west_virginia','wy':'wyoming','dc':'district of columbia'}
138        $('#stateGeo').val(stateMap[google.loader.ClientLocation.address.region]);
139    }else{
140        $('#stateGeo').val("none");
141    }
142    
143    // Focus the cursor on the text box
144    $searchBox.focus();
145
146    // bring cursor to the end of the text
147    cursorToEnd();
148    
149    $searchBox.blur();
150    
151    // Submit the search form when the search button is clicked
152    $("#searchButton").click(function() { $("#searchForm").submit(); })
153    
154    // Disambiguation Dialog
155    $('#disambigSelect').click(function() {
156        $("#disambigDiv").toggle();
157        $('#disambigSelect > div').toggleClass("disambigArrowRight disambigArrowDown");
158        
159        return false;
160    });
161
162    $("#searchTooltip").dialog({ autoOpen:false, width: "auto", minHeight: "170px", resizable: false,
163                                dialogClass: "searchTooltipWidget", 
164                               position: { my: "left top", at: "right-365 top", of: "#searchFormContainer" } }); 
165
166
167    $("#qaAnnotationTooltip").dialog({ autoOpen:false, width: "auto", minHeight: "170px", resizable: false,
168                               dialogClass: "qaAnnotationTooltipWidget", 
169                               position: { my: "left top", at: "right-365 top", of: "#searchFormContainer" } });
170
171    $("#infoPanelAnnotationTooltip").dialog({ autoOpen:false, width: "auto", minHeight: "170px", resizable: false,
172                               dialogClass: "infoPanelAnnotationTooltipWidget", 
173                               position: { my: "left top", at: "right-365 top", of: "#searchFormContainer" } });
174
175
176});
177
178//bring cursor to the end of the text 
179function cursorToEnd(){
180    var $searchBox = $("#searchInput");
181    var val = $searchBox.val();
182    $searchBox.val('');
183    $searchBox.val(val);
184}
185
186// Note: Plan b for video search. Pull image via ajax
187// http://jquery-howto.blogspot.com/2009/02/how-to-get-youtube-video-screenshot.html
188var SearchDataService = (function ($) {
189	var DEFAULT_QUERY = 'united states';
190	var DEFAULT_GEO_CODE = '010XX00US';
191
192	// Root URL of API
193	var dataRoot;
194	var streams;
195
196	/** Maps out the structure of CEDSCI's API. Each object maps to a request for a piece of data, some of which come from the same endpoint.
197		Parameters needed per endpoint are also described.
198		var streams = {
199			entities: {
200				endpoint: 'search',
201				parameters: {
202					q: undefined,
203					services: 'search:featuredresults'
204				}
205			},
206			search: {
207				endpoint: 'search',
208				parameters: {
209					q: undefined,
210					services: 'search:featuredresults'
211				}
212			},
213			featuredresults: {
214				endpoint: 'search',
215				parameters: {
216					q: undefined,
217					services: 'search:featuredresults'
218				}
219			},
220			highlights: {
221				endpoint: 'profile/content/highlights',
222				parameters: {
223					g: undefined
224				}
225			},
226				// Subset of highlights data specifying the related geo
227			selectedProfile: {
228				endpoint: 'profile/content/highlights',
229				parameters: {
230					g: undefined
231				}
232			},
233			profiles: {
234				endpoint: 'search',
235				parameters: {
236					services: 'search:featuredresults'
237				}
238			},
239			typeahead: {
240				endpoint: 'typeahead'
241			}
242		};
243	**/
244
245	var initialized = false;
246	var promises = {};
247	var typeaheadSupportedGeosLevels = {};
248
249	// Intake root URL
250	function init(root, endpoints, rawTypeaheadSupportedGeos) {
251		typeaheadSupportedGeosLevels = convertGeoLevels(rawTypeaheadSupportedGeos);
252		if (!dataRoot) {
253			dataRoot = root;
254		}
255		if (!streams) {
256			streams = translateRawEndpoints(endpoints);
257		}
258
259		initQueries();
260	}
261
262	function convertGeoLevels(raw) {
263		let converted = {};
264		if (raw && raw !== 'null' && raw !== '[]') {
265			let parsed = JSON.parse(raw);
266
267			parsed.forEach(function (geo) {
268				converted[geo.code] = geo.level;
269			});
270		}
271
272		return converted;
273	}
274
275	function findLocation(entities) {
276		const supportedGeoLevels = {
277			'010': 'nation',
278			'040': 'state',
279			'050': 'county',
280			'160': 'city'
281		};
282
283		for (let i = 0; i < entities.length; i++) {
284			if (!entities[i].options) {
285				continue;
286			}
287
288			for (let j = 0; j < entities[i].options.length; j++) {
289				const entry = entities[i].options[j];
290				const locationType = entry?.type;
291				const locationCode = entry?.facets.geography && entry?.facets.geography[0].slice(0, 3);
292				const isSelectedExist = "selected" in entry ? (entry.selected) : null;
293				if (locationType === "geography" && supportedGeoLevels[locationCode] && (isSelectedExist === null || isSelectedExist)) {
294					return entry;
295				}
296			}
297		}
298	}
299
300	// Endpoints comes in as stringified JSON in a format stored by the dialog. 
301	// They need to be translated a bit into an object the brain can understand.
302	function translateRawEndpoints(rawEndpoints) {
303		if (rawEndpoints) {
304			var parsedEndpoints = JSON.parse(rawEndpoints);
305			var endpoints = {};
306
307			parsedEndpoints.forEach(function (parsedEndpoint) {
308				var dataObject = parsedEndpoint.dataObject;
309				delete parsedEndpoint.dataObject;
310				parsedEndpoint.parameters = convertParameters(parsedEndpoint.parameters);
311
312				endpoints[dataObject] = parsedEndpoint;
313			});
314
315			return endpoints;
316		}
317	}
318
319	function convertParameters(parameters) {
320		var formattedEndpoints = {};
321		parameters.forEach(function (parameter) {
322			formattedEndpoints[parameter.name] = parameter.value;
323		});
324
325		return formattedEndpoints;
326	}
327
328	// Initialize query objects 
329	function initQueries() {
330		// Only initialize once. This function may be called several times 
331		// due to the various components that can initialize it independently.
332		if (!initialized && streams) {
333			// Create list of unique endpoints to serve as structure of promises
334			var endpoints = [];
335			Object.keys(streams).forEach(function (stream) {
336				if (endpoints.indexOf(streams[stream].endpoint) === -1) {
337					endpoints.push(streams[stream].endpoint);
338				}
339			});
340
341			// Create an object with a promise and a progress flag to each endpoint
342			endpoints.forEach(function (endpointKey) {
343				promises[endpointKey] = {
344					callStarted: undefined,
345					promise: undefined
346				};
347			});
348
349			// Remove typeahead init since it will use a different system
350			delete promises.typeahead;
351
352			initialized = true;
353		}
354	}
355
356	// Entry point for components. Returns a promise of the requested data.
357	function subscribe(stream, query) {
358		// Use the override if it exists, otherwise pull the query from the q parameter
359		var q = query ? query : isolateQueryParam();
360
361		if (streams && streams[stream]) {
362			// Check to see if the requesting data has already been called
363			// If it has, return that as a resolved promise. If not, make the call.
364			var endpointKey = streams[stream].endpoint;
365			if (!promises[endpointKey][q] ||
366				!promises[endpointKey][q].callStarted) {
367
368				if (endpointKey === 'search') {
369					promises[endpointKey][q] = {
370						callStarted: true,
371						promise: queryService(stream, { q: q })
372					};
373
374					return filterPromise(endpointKey, q, stream);
375				} else {
376					// If the requested data is not from the main search point, further information is needed, usually a recognized geocode
377					// If the search query has not been kicked off already, do it before attaching the subsequent call
378					if (!promises.search[q]) {
379						promises.search[q] = {
380							callStarted: true,
381							promise: queryService('search', { q: q })
382						};
383					}
384
385					promises[endpointKey][q] = {
386						callStarted: true,
387						promise: filterPromise('search', q, 'entities').then(function (entities) {
388							// Determine which geo parameter to use based on endpoint
389							var parameters = {};
390
391							// Only locations support profiles
392							var location = findLocation(entities);
393							parameters.g = location ? location.facets.geography[0] : DEFAULT_GEO_CODE;
394
395							return queryService(stream, parameters);
396						})
397					};
398
399					return filterPromise(endpointKey, q, stream);
400				}
401			} else {
402				return filterPromise(endpointKey, q, stream);
403			}
404		} else {
405			// Return rejected promise to activate chained error handling callbacks
406			return Promise.reject();
407		}
408	}
409
410	// Filter raw response data to only return requested data
411	// Has to account for inconsistent response structures
412	function filterPromise(endpointKey, q, stream) {
413		return promises[endpointKey][q].promise.then(function (response) {
414			if (response && response.response) {
415				if (stream && response.response[stream]) {
416					return response.response[stream];
417				} else {
418					return response.response;
419				}
420			} else if (response) {
421				// Should only match highlights response structure
422				if (stream && response[stream]) {
423					return response[stream];
424				}
425			}
426		});
427	}
428
429	// Parse the query string and return the q value
430	// Doing it manually because IE doesn't support some of the one-line functions things like jQuery and higher forms of JS provide
431	function isolateQueryParam() {
432		var qS = window.location.search;
433		var query = {};
434		if (qS && qS.length > 0) {
435			// Remove leading ?
436			qS = qS.substring(1);
437
438			// Iterate over query string and build into object
439			var splitQS = qS.split('&');
440			splitQS.forEach(function (qSFragment) {
441				var splitQSF = qSFragment.split('=');
442				query[splitQSF[0]] = decodeURIComponent(splitQSF[1]);
443			});
444		}
445
446		// Return search parameter only
447		return query.q ? query.q : DEFAULT_QUERY;
448	}
449
450	// Get data from CEDSCI API
451	function queryService(stream, parameters) {
452		if (streams[stream]) {
453			// Clone parameter template and replace with incoming values
454			var clonedparameters = JSON.parse(JSON.stringify(streams[stream].parameters));
455			Object.keys(parameters).forEach(function (key) {
456				clonedparameters[key] = parameters[key];
457			});
458
459			var url = dataRoot + streams[stream].endpoint;
460			return $.get(url, clonedparameters).then(function (response) {
461				// Filter out various forms of pseudogeographies
462				// This function is probably better suited to be just before final return, but the logic gets more complicated there 
463				// and this works for now.
464				if (response && response.response) {
465					// Delete all keys that contain either pseudogeo delimiter
466					// Inelegant, may cause issues in the future.
467					Object.keys(response.response).filter(checkPseudoGeos).forEac
467h(function (pseudoKey) {
468						delete response.response[pseudoKey];
469					});
470
471					// Delete all returned pseudogeo location entities
472					if (response.response.entities && response.response.entities[0].options) {
473						var locations = response.response.entities.find(entry => entry.options[0].type === "geography")?.options || [];
474						locations.filter(function (location) {
475							const geoValue = location.facets.geography && location.facets.geography[0];
476							return geoValue && checkPseudoGeos(location.facets.geography[0]);
477						}).forEach(function (location) {
478							locations.splice(locations.indexOf(location), 1);
479						});
480
481						// Clean up
482						if (locations.length === 0) {
483							delete response.response.entities[0].options;
484						}
485					}
486				}
487
488				return response;
489			});
490		} else {
491			// Return rejected promise to activate chained error handling callbacks
492			return Promise.reject();
493		}
494	}
495
496	// Check to see if the incoming geocode is a pseudogeo, detectable by . (old syntax) or $ (new syntax) delimiters
497	function checkPseudoGeos(code) {
498		return code.indexOf('.') !== -1 || code.indexOf('$') !== -1;
499	}
500
501	// Handle typeahead separately since its structure is so different
502	function typeahead(query) {
503		// var unsupportedEntities = [ 'tables' ];
504		var unsupportedEntities = [];
505
506		var url = dataRoot + streams.typeahead.endpoint;
507
508		// Cancel existing call if it exists
509		if (promises.typeahead) {
510			promises.typeahead.abort();
511		}
512
513		// Make new call
514		var payload = {
515			request: {
516				text: query
517			}
518		};
519		promises.typeahead = $.ajax({
520			type: 'POST',
521			url: url,
522			data: JSON.stringify(payload),
523			contentType: 'application/json;charset=UTF-8'
524		});
525
526		// Filter tables results by allowed entity types
527		// Also check for pseudogeography markers
528		return promises.typeahead.then(function (response) {
529			if (response && response.response && response.response.options) {
530				return response.response.options.filter(function (suggestion) {
531					// Remove unsupported level
532					const geoValue = suggestion.facets.geography && suggestion.facets.geography[0];
533					if (geoValue && !typeaheadSupportedGeosLevels[(suggestion.facets.geography[0].substring(0, 3))]) {
534						return false;
535					}
536					// Remove unsupported entity types
537					if (unsupportedEntities.indexOf(suggestion.type) === -1) {
538						if (suggestion.type === 'geographies') {
539							// DO NOT return if pseudo markers are found. Inverse of normal logic of this function's usage.
540							return !checkPseudoGeos(suggestion.codes[0]);
541						} else {
542							return true;
543						}
544					}
545				});
546			}
547		});
548	}
549
550	return {
551		init: init,
552		subscribe: subscribe,
553		subscriptionOptions: function () { return Object.keys(streams); },
554		typeahead: typeahead,
555		getQuery: isolateQueryParam
556	};
557
558})($);
559var CensusDataTools = (function() {
560    var supportedGeoLevels = {
561		'040': 'state',
562        '050': 'county',
563        '160': 'city'
564	};
565
566    var dataTools = {
567        popClock: {
568            title: 'U.S. and World Population Clock',
569            description: 'See U.S. population by date, region, age and sex, and the top 10 areas by people and density. The world view has basic facts, trade, and projections by country.',
570            image: '/content/census/en/data/data-tools/population-clock.listitemgallery.png/1550273046380/popclock_bkg_260.png',
571            link: '/data/data-tools/population-clock.html',
572            ctaText: 'View POPCLOCK'
573        },
574        quickfacts: {
575            title: 'QuickFacts',
576            description: 'QuickFacts provides frequently requested Census Bureau information at the national, state, county, and city level.',
577            image: '/content/census/en/data/data-tools/quickfacts.listitemgallery.png/1550273049627/quickfacts_160x100.png',
578            link: '//www.census.gov/quickfacts/table/PST045215/',
579            ctaText: 'View QUICKFACTS'
580        }
581    };
582
583    function findLocation(entities) {
584        for (let i = 0; i < entities.length; i++) {
585            if ( entities[i].options ) {
586                for (let j = 0; j < entities[i].options.length; j++) {
587                    const entry = entities[i].options[j];
588                    const locationType = entry?.type;
589                    const locationCode = entry?.facets.geography && entry?.facets.geography[0].slice( 0, 3 );
590                    const isSelectedExist = "selected" in entry ? (entry.selected) : null;
591                    if (locationType === "geography" &&  supportedGeoLevels[locationCode] && (isSelectedExist === null || isSelectedExist)) {
592                        return entry;
593                    }
594                }
595            }
596        }
597    }
598
599    function init() {
600        SearchDataService.subscribe( 'entities' ).then( function( entities ) {
601            const location = findLocation(entities);
602            if (location) {
603          		var splitGeoCode = location.facets.geography[0].split( 'US' );
604                var FIPSCode = splitGeoCode.length > 1 ? splitGeoCode[1] : '';
605                buildQuickfacts( FIPSCode );     
606            } else {
607                  // Pop Clock is mutually exclusive to Quickstats
608                var query = SearchDataService.getQuery();
609                if ( query.toLowerCase().indexOf( 'population' ) !== -1 ) {
610                    buildDataTool( dataTools.popClock );
611                }
612            }
613        });
614    }
615
616    function buildQuickfacts( geoLevel ) {
617          // Clone quickfacts component and fully build link URL
618        var quickfactsConfig = JSON.parse( JSON.stringify( dataTools.quickfacts ) );
619        quickfactsConfig.link += geoLevel;
620
621        buildDataTool( quickfactsConfig );
622    }
623
624    function buildDataTool( config ) {
625        var $dataTool = $( '<div class="uscb-data-tool-box"></div>' );
626        $dataTool.html(       
627            '<div class="uscb-data-tool-box-top">' +
628            '    <h3 class="uscb-data-tool-box-title">' + config.title + '</h3>' + 
629            '    <p class="uscb-data-tool-box-teaser">' + config.description + '</p>' + 
630            '    <div class="uscb-data-tool-box-action-button">' +
631            '        <a class="btn btn-primary btn-md" href="' + config.link +'" onclick="linkClick(this, \'Data Tool Container Component\');">' + config.ctaText + '</a>' +
632            '    </div>' +
633            '</div>' +
634            '<div class="inner-box"></div>' );
635
636        $( '.datatoolcontainer' ).append( $dataTool ); 
637    }
638
639    return {
640        init: init
641    };
642})();
643var CensusSearchFeaturedResults = (function () {
644	'use strict';
645
646	function init(overrideQuery, uniqueID, dataTableLink, isEditMode) {
647		const ele = document.getElementById(uniqueID);
648		const loadingIndicator = getNextSibling(ele, '.uscb-loading-circular-indeterminate');
649
650		// jQuery dependency
651		SearchDataService.subscribe('featuredresults', overrideQuery)
652			.done((result) => {
653				loadingIndicator.classList.add('uscb-hide');
654				populateFeaturedResult(ele, result, dataTableLink, isEditMode);
655			}).fail((error) => {
656				console.error('[CensusSearchFeaturedResults] error', error);
657			}).always(() => {
658				loadingIndicator.classList.add('uscb-hide');
659			});
660	}
661
662	function populateFeaturedResult(ele, featuredResults, dataTableLink, isEditMode) {
663		const enhancedInsightCard = "enhanced-insight-card";
664		const classicInfoBlock = "classic-info-block";
665		const selectedContainerName = ele.parentElement.classList.contains('enhanced-insight-card') ? enhancedInsightCard : classicInfoBlock;
666		const containerEle = ele.querySelector(`.uscb-quick-answer-container__${selectedContainerName}`);
667
668		if (isJSONEmpty(featuredResults)) {
669			hideComponent(containerEle, true, isEditMode, 'No data returned from API');
670			return;
671		}
672
673		let sourceText = "";
674		if (featuredResults.hasOwnProperty('geoName')) {
675			if (selectedContainerName === enhancedInsightCard) {
676				sourceText = featuredResults.tableName?.includes('.') ? featuredResults.tableName.split('.')[1] : ''
677			}
678		} else {
679			hideComponent(containerEle, true, isEditMode, 'No featuredResults.geoName returned from API');
680			return;
681		}
682
683		if (featuredResults.hasOwnProperty('label')) {
684			if (selectedContainerName === enhancedInsightCard) {
685				containerEle.querySelector('.uscb-featured-results-quick-header').innerText = getShortTitle(featuredResults.label);
686				containerEle.querySelector('.uscb-data-quick-answer-label').innerText = featuredResults.label;
687				containerEle.querySelector('.uscb-featured-results-quick-col-1').innerHTML = getImageElement(featuredResults.label);
688			} else {
689				containerEle.querySelector('.uscb-data-quick-answer-label').innerHTML = `${featuredResults.label}<span class="uscb-featured-result-geography"> in ${featuredResults.geoName}</span>`;
690			}
691		} else {
692			hideComponent(containerEle, true, isEditMode, 'No featuredResults.label returned from API');
693			return;
694		}
695
696		if (featuredResults.hasOwnProperty('estimate') && featuredResults.hasOwnProperty('format')) {
697			let marginOfError = '';
698			if (featuredResults.hasOwnProperty('marginOfError')) {
699				marginOfError = featuredResults.marginOfError;
700			}
701
702			let formattedEstimate = featuredResults.estimate;
703			let formattedMoE = featuredResults.marginOfError;
704			const moeIsNumber = !isNaN(parseFloat(formattedMoE));
705
706			// If the last character of the value is not a number, treat is as a string.
707			// Otherwise, format it.
708			if (!isNaN(formattedEstimate[formattedEstimate.length - 1])) {
709				const estimateDecimals = determineDecimals(featuredResults.estimate);
710
711				switch (featuredResults.format) {
712					case 'commas,dollar':
713					case 'dollar':
714						formattedEstimate = formatCurrency(featuredResults.estimate, estimateDecimals);
715						if (moeIsNumber) {
716							formattedMoE = formatCurrency(featuredResults.marginOfError, estimateDecimals);
717						}
718						break;
719					case 'percent':
720						formattedEstimate = formatPercent(featuredResults.estimate, estimateDecimals);
721						if (moeIsNumber) {
722							formattedMoE = formatPercent(featuredResults.marginOfError, estimateDecimals);
723						}
724						break;
725					case 'commas':
726					default:
727						formattedEstimate = formatNumber(featuredResults.estimate, estimateDecimals);
728						if (moeIsNumber) {
729							formattedMoE = formatNumber(featuredResults.marginOfError, estimateDecimals);
730						}
731				}
732			}
733
734			if (formattedMoE && formattedMoE.length && (selectedContainerName !== enhancedInsightCard)) {
735				formattedMoE = '+/- ' + formattedMoE;
736				containerEle.querySelector('.uscb-margin-of-error').innerText = formattedMoE;
737			}
738
739			containerEle.querySelector('.uscb-data-quick-answer-value').innerText = formattedEstimate;
740		} else {
741			hideComponent(containerEle, true, isEditMode, 'No featuredResults.estimate/format returned from API');
742			return;
743		}
744
745		if (featuredResults.hasOwnProperty('sourceLink') && featuredResults.hasOwnProperty('dataSource')) {
746			if (dataTableLink === '') { dataTableLink = 'https://data.census.gov/all?q=population'; } // default to "Population"
747
748			const sourceLinkContainer = containerEle.querySelector('.uscb-data-quick-answer-source');
749
750			const sourceLink = document.createElement('a');
751			sourceLink.setAttribute('href', dataTableLink);
752
753			if (sourceLinkContainer) {
754				const sourceDescription = document.createElement('a');
755				sourceDescription.setAttribute('href', featuredResults.sourceLink);
756
757				if (selectedContainerName === enhancedInsightCard) {
758
759					sourceLink.classList.add('uscb-data-quick-answer-source__hyperlinks');
760					sourceLink.innerText = sourceText;
761					sourceLinkContainer.appendChild(sourceLink);
762
763					const separator = document.createElement('span');
764					separator.classList.add('uscb-data-quick-answer-source__separator');
765					separator.innerText = ' | ';
766					sourceLinkContainer.appendChild(separator);
767
768					sourceDescription.innerText = featuredResults.dataSource;
769
770					const sourceDescriptionWrap = document.createElement('span');
771					sourceDescriptionWrap.classList.add('uscb-data-quick-answer-source__description');
772					sourceDescriptionWrap.appendChild(sourceDescription);
773
774					sourceLinkContainer.appendChild(sourceDescriptionWrap);
775				} else {
776					sourceDescription.innerText = 'Source: ' + featuredResults.dataSource;
777					sourceLinkContainer.appendChild(sourceDescription);
778				}
779			}
780
781			const dataTableLinkContainer = containerEle.querySelector('.uscb-data-table');
782			if (dataTableLinkContainer) {
783				if (selectedContainerName === enhancedInsightCard) {
784					dataTableLinkContainer.remove(); // already included in sourceLinkContainer
785				} else {
786					sourceLink.innerText = 'Data Table';
787					dataTableLinkContainer.appendChild(sourceLink);
788				}
789			}
790		} else {
791			hideComponent(containerEle, true, isEditMode, 'No featuredResults.sourceLink/dataSource returned from API');
792			return;
793		}
794
795		containerEle.classList.remove('uscb-hide');
796	}
797
798	function hideComponent(containerEle, forceHide, isEditMode, errorMessage) {
799		// if it's publish mode and needs to hide
800		if (isEditMode === 'false' && forceHide) {
801			containerEle.classList.add('uscb-hide');
802		} else {
803			containerEle.classList.remove('uscb-hide');
804
805			if (isEditMode === 'true' && forceHide) {
806				containerEle.innerText = `Error loading results${errorMessage ? ': ' + errorMessage : ''}`;
807			}
808		}
809	}
810
811	function isJSONEmpty(obj) {
812		for (var key in obj) {
813			if (obj.hasOwnProperty(key)) {
814				return false;
815			}
816		}
817		return true;
818	}
819
820	function getNextSibling(elem, selector) {
821		const sibling = elem.nextElementSibling;
822		if (!selector) return sibling;
823
824		while (sibling) {
825			if (sibling.matches(selector)) return sibling;
826			sibling = sibling.nextElementSibling;
827		}
828
829		return null;
830	}
831
832	function determineDecimals(num) {
833		if (Math.floor(num) === num) {
834			return 0;
835		} else {
836			var split = num.toString().split('.');
837			if (split.length > 1) {
838				return split[1].length;
839			} else {
840				return 0;
841			}
842		}
843	}
844
845	//Assuming only dollars are used
846	function formatCurrency(amount, minimumDigits) {
847		if (amount) {
848			const formatter = new Intl.NumberFormat('en-US', {
849				style: 'currency',
850				currency: 'USD',
851				minimumFractionDigits: minimumDigits || 0
852			});
853
854			return formatter.format(amount);
855		}
856	}
857
858	function formatPercent(amount, minimumDigits) {
859		if (amount) {
860			return formatNumber(amount, minimumDigits) + '%';
861		}
862	}
863
864	function formatNumber(amount, minimumDigits) {
865		if (amount) {
866			const formatter = new Intl.NumberFormat('en-US', {
867				minimumFractionDigits: minimumDigits
868			});
869
870			return formatter.format(amount);
871		}
872	}
873
874	function getShortTitle(label) {
875		const titleMap = {
876			"total population": "Populations and People",
877			"median household income": "Income and Poverty",
878			"bachelor's degree or higher": "Education",
879			"employment rate": "Employment",
880			"total housing units": "Housing",
881			"without health care coverage": "Health",
882			"total employer establishments": "Business and Economy",
883			"total households": "Families and Living Arrangements",
884			"hispanic or latino (of any race)": "Race and Ethnicity"
885		};
886
887		return titleMap[label.toLowerCase()] || '';
888	}
889
890	function getImageElement(label) {
891		const iconMap = {
892			"business": [
893				'agriculture, forestry, fishing and hunting, and mining',
894				'total employer establishments',
895				'total manufactures shipments (in thousands)',
896			],
897			"education": [
898				'bachelor\'s degree or higher',
899				'high school graduate or higher',
900				'school enrolled population enrolled in kindergarten to 12th grade',
901			],
902			"employment": [
903				'employment rate',
904				'females in computer, engineering and science occupations',
905				'local, state, and federal government workers',
906				'mean usual hours worked for workers',
907				'total employment',
908			],
909			"family": [
910				'average family size',
911				'never married',
912				'same sex married couple households',
913				'total households',
914			],
915			'geography': [
916				'average travel time to work (in minutes)',
917			],
918			"health": [
919				'public health insurance',
920				'without health care coverage',
921			],
922			"housing": [
923				"median value for mobile homes",
924				'group quarters population',
925				'homeownership rate',
926				'less than $500',
927				'median gross rent',
928				'median housing value',
929				'moved 2021 or later into occupied housing unit',
930				'moved from a different state in the last year',
931				'occupied housing units',
932				'total housing units',
933				'vacant housing units',
934			],
935			"income": [
936				'female median year-round, full-time earnings',
937				'median earnings for workers',
938				'median household income',
939				'per capita income',
940				'poverty, all people',
941				'poverty, under 18 years old',
942				'total annual payroll (in thousands)',
943			],
944			"people": [
945				'65 years and older',
946				'disabled population',
947				'median age',
948				'total population',
949				'under 18 years old',
950				'veterans',
951				'women 15 to 50 years old',
952			],
953			"race": [
954				'american indian and alaska native alone',
955				'asian alone',
956				'black or african american alone',
957				'foreign-born population',
958				'hispanic or latino (of any race)',
959				'language other than english spoken at home',
960				'native hawaiian and other pacific islander alone',
961				'some other race alone',
962				'spanish speakers',
963				'two or more races',
964				'white alone',
965				'white alone, not hispanic or latino',
966				'without an internet subscription',
967			],
968		};
969
970		let iconName;
971		for (const icon of Object.keys(iconMap)) {
972			const labels = iconMap[icon];
973			if (labels.includes(label.toLowerCase().trim())) {
974				iconName = icon;
975			}
976		}
977
978		if (iconName) {
979			return `<img
980				src="/etc.clientlibs/census/clientlibs/cedsci-icons/resources/${iconName}.svg"
981				alt="Icon"/>`;
982		}
983
984		console.warn(`[search-feature-results] No icon for ${label}`);
985		return '';
986	}
987
988	return {
989		init: init
990	}
991})(jQuery);
992
993var CensusSearchProfile = ( function() {
994	'use strict';
995	
996	var supportedGeoLevels = {};
997
998	function init( queryOverride, uniqueID, rawSupportedGeoLevels ) {
999		supportedGeoLevels = convertGeoLevels( rawSupportedGeoLevels );
1000		SearchDataService.subscribe( 'profiles', queryOverride ).then( function( response ) {
1001			  // Hide the loading indicator
1002			var $parentElement = $( '#' + uniqueID );
1003			$parentElement.find( '.uscb-loading-circular-indeterminate' ).toggleClass( 'uscb-hide' );
1004
1005              // Only display first profile. Results are ranked by relevance, but multiple may return for searches without specific geographies.
1006			if ( response.length > 0 ) {
1007                var profile = findFirstNonTableResult( response );
1008				if ( profile ) {
1009                    buildProfile( profile, $parentElement );
1010                }
1011			}
1012		}, function( error ) {
1013			  // Until we can ignore IE, we have to do it this way and not use .finally() which would be cleanest
1014			  	// Hide the loading indicator
1015			var $parentElement = $( '#' + uniqueID );
1016			$parentElement.find( '.uscb-loading-circular-indeterminate' ).toggleClass( 'uscb-hide' );
1017		});
1018    }
1019
1020	function convertGeoLevels( raw ) {
1021		var converted = {};
1022		  // Java returns null as a *string* if the object doesn't exist so that needs to be checked explicitly as it is technically a truthy value
1023		if ( raw && raw !== 'null' ) {
1024			var parsed = JSON.parse( raw );
1025			parsed.forEach( function( geo ) {
1026				converted[ geo.code ] = geo.level;
1027			});
1028		}
1029
1030		return converted;
1031	}
1032
1033    function findFirstNonTableResult( profiles ) {
1034        for ( var i = 0; i < profiles.length; i++ ) {
1035            if ( profiles[ i ].suggestionType != 'table' && profiles[ i ].suggestionType != 'tables' ) {
1036                return profiles[ i ];
1037            }
1038        }
1039    }
1040
1041	function buildProfile( profile, $parentElement ) {
1042        const geoValue = profile.facets.geography && profile.facets.geography[0];
1043        if ( supportedGeoLevels && geoValue && supportedGeoLevels[ ( profile.facets.geography[0].substring(0, 3) ) ] ) {
1044            var html = '' +
1045                '<div>' +
1046                    '<div class="uscb-profile-result-container" tabindex="0">' +
1047                        '<div class="uscb-profile-result-title">' + profile.title + ' Profile' + '</div>' +
1048                        '<div class="uscb-profile-result-description-container">' +
1049                            '<div class="uscb-profile-result-description-text">' + profile.teaser + '</div>' +
1050                        '</div>' +
1051                        '<a class="uscb-profile-result-view-profile" href="' + 'https://data.census.gov/cedsci/profile?g=' + geoValue + '">View Profile <i class="o-angle-right-circle-2"></i> </a>' +
1052                    '</div>' +
1053                '</div>';
1054
1055            $parentElement.append( html );
1056        }
1057	}
1058
1059	return {
1060        init: init
1061    }
1062})(jQuery);
1063

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.