{"version":3,"file":"render.mjs","sources":["src/common.mjs","src/render.mjs"],"sourcesContent":["// Common Utilities Class\r\nexport default class {\r\n\tconstructor(config) {\r\n\t\tthis.Mustache = config.Mustache;\r\n\t\tthis.componentLayout = config.componentLayout || 'default';\r\n\t\tthis.customSettingsData = config.customSettingsData || {};\r\n\t\tthis.template = config.template;\r\n\t\tthis.contentClient = config.contentClient;\r\n\t\tthis.model = {};\r\n\t\tthis.pageLocale = config.pageLocale;\r\n\t\tthis.mode = config.mode || 'render';\r\n\t}\r\n\r\n\tsetModel(model) {\r\n\t\tthis.model = model;\r\n\t}\r\n\r\n\tformatPhoneNumber(strPhoneNumber) {\r\n\t\t//sanitize phone numbers for consistency.\r\n\r\n\t\tlet intCleanedNumber = ('' + strPhoneNumber).replace(/\\D/g, '');\r\n\t\tlet arSplitNumber = intCleanedNumber.match(/^(\\d{3})(\\d{3})(\\d{4})$/);\r\n\t\tif (arSplitNumber) {\r\n\t\t\treturn `${arSplitNumber[1]}.${arSplitNumber[2]}.${arSplitNumber[3]}`;\r\n\t\t}\r\n\t\treturn null;\r\n\t}\r\n\r\n\tasync fetchLenderData(isCompile) {\r\n\t\t//Call server to get the needed assets and format the data to run the template\r\n\r\n\t\tconst strQuery = '(type eq \"MNH_Lender\")';\r\n\t\tconst strFields =\r\n\t\t\t'fields.headshot,fields.nmls_number,fields.first_name,fields.last_name,fields.lender_name,fields.logo,fields.phone_number,fields.extension,fields.email,fields.website,fields.address,fields.city,fields.state,fields.zip,fields.latitude,fields.longitude,fields.languages,fields.service_area,fields.service_type,taxonomies';\r\n\t\tconst arLenderData = await this.contentClient.queryItems({\r\n\t\t\tq: strQuery,\r\n\t\t\tfields: strFields,\r\n\t\t\tlimit: 10000,\r\n\t\t});\r\n\t\treturn this.compileLenderData(arLenderData, isCompile);\r\n\t}\r\n\r\n\tbuildBadgeMap() {\r\n\t\t//take in the badges from the custom settings data and format them for lookup in a map\r\n\t\t//using a map for lookup for efficiency, otherwise we'd be looping loops for all the records\r\n\t\tconst arBadgeList = this.customSettingsData.arBadges;\r\n\t\tconst mapBadgeData = new Map();\r\n\r\n\t\tif (arBadgeList) {\r\n\t\t\tfor (const arBadge of arBadgeList) {\r\n\t\t\t\tconst arBadgeData = arBadge.split(',');\r\n\t\t\t\tconst lastWord = arBadgeData[2].split(' ').pop();\r\n\t\t\t\tconst badgeYear = isNaN(lastWord) ? null : lastWord;\r\n\t\t\t\tconst badgeText = badgeYear\r\n\t\t\t\t\t? arBadgeData[2].substring(0, arBadgeData[2].lastIndexOf(' '))\r\n\t\t\t\t\t: arBadgeData[2];\r\n\r\n\t\t\t\tlet objBadge = {\r\n\t\t\t\t\tid: arBadgeData[0],\r\n\t\t\t\t\tparentId: arBadgeData[1],\r\n\t\t\t\t\tyear: badgeYear,\r\n\t\t\t\t\tname: badgeText,\r\n\t\t\t\t};\r\n\t\t\t\tmapBadgeData.set(objBadge.id, objBadge);\r\n\t\t\t}\r\n\r\n\t\t\treturn mapBadgeData;\r\n\t\t}\r\n\t}\r\n\r\n\tcustomExpand(text) {\r\n\t\ttry {\r\n\t\t\t// replace \"[[page-221]]\" with \"[!--$SCS_PAGE--]221[/!--$SCS_PAGE--]\"\r\n\t\t\tlet strOutput = text.replace(\r\n\t\t\t\t/\\[\\[page-(\\d+)\\]\\]/g,\r\n\t\t\t\t'[!--$SCS_PAGE--]$1[/!--$SCS_PAGE--]'\r\n\t\t\t);\r\n\r\n\t\t\t// replace \"[[asset-221]]\" with \"[!--$CEC_DIGITAL_ASSET--]221[/--$CEC_DIGITAL_ASSET--]\"\r\n\t\t\tstrOutput = strOutput.replace(\r\n\t\t\t\t/\\[\\[asset-(\\d+)\\]\\]/g,\r\n\t\t\t\t'[!--$CEC_DIGITAL_ASSET--]$1[/!--$CEC_DIGITAL_ASSET--]'\r\n\t\t\t);\r\n\r\n\t\t\tstrOutput = this.contentClient.expandMacros(strOutput);\r\n\t\t\treturn strOutput;\r\n\t\t} catch (e) {\r\n\t\t\tconsole.log(\r\n\t\t\t\t'Error: Failed to expand custom tags in: ' + import.meta.url,\r\n\t\t\t\t' using default text.'\r\n\t\t\t);\r\n\t\t\tconsole.log(e);\r\n\t\t\treturn text;\r\n\t\t}\r\n\t}\r\n\r\n\tcompileLenderData(arLenderData, isCompile) {\r\n\t\tconst {\r\n\t\t\tclearBtn,\r\n\t\t\tcolumnHeaders,\r\n\t\t\tlanguages,\r\n\t\t\tresultsHeader,\r\n\t\t\tsearch,\r\n\t\t\tsearchBtn,\r\n\t\t\tservices,\r\n\t\t\tdistanceText,\r\n\t\t\tmisc,\r\n\t\t\trehabHomeTooltip,\r\n\t\t\tfixUpHomeTooltip,\r\n\t\t} = this.customSettingsData.nls;\r\n\t\tconst mapBadgeData = this.buildBadgeMap();\r\n\r\n\t\t//the services text of the result is different than the services text in the filters\r\n\t\tconst servicesText = {\r\n\t\t\tbuy_first_home: services.buy_first_home.value,\r\n\t\t\tbuy_new_home: services.buy_new_home.value,\r\n\t\t\thome_improvement_loans: services.home_improvement_loans.value,\r\n\t\t\trefinance: services.refinance.value,\r\n\t\t\trehabilitation_emergency: services.rehabilitation_emergency.value,\r\n\t\t};\r\n\r\n\t\t//Clean up the data to work with page\r\n\t\tfor (const objResult of arLenderData.items) {\r\n\t\t\tobjResult.fields.pageNumber =\r\n\t\t\t\tMath.floor(arLenderData.items.indexOf(objResult) / 10) + 1;\r\n\r\n\t\t\tconst fields = objResult.fields;\r\n\t\t\tif (fields.phone_number) {\r\n\t\t\t\tfields.phone_number = this.formatPhoneNumber(fields.phone_number);\r\n\t\t\t}\r\n\r\n\t\t\tif (fields.headshot) {\r\n\t\t\t\tfields.headshot.url = this.contentClient.getRenditionURL({\r\n\t\t\t\t\tid: fields.headshot.id,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tif (fields.logo) {\r\n\t\t\t\tfields.logo.url = this.contentClient.getRenditionURL({\r\n\t\t\t\t\tid: fields.logo.id,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tif (fields.service_type) {\r\n\t\t\t\tlet servicesOffered = fields.service_type.map((service) => {\r\n\t\t\t\t\treturn servicesText[service];\r\n\t\t\t\t});\r\n\t\t\t\tfields.service_type = servicesOffered;\r\n\t\t\t}\r\n\r\n\t\t\tfields.badges = [];\r\n\r\n\t\t\tfields.languages = fields.languages || [];\r\n\t\t\t//When we get to the point of adding additional languages this will flag so the code only toggles on english version\r\n\t\t\tlet isEnglish = true;\r\n\t\t\tif (isEnglish) {\r\n\t\t\t\tconst numLangIndex = fields.languages.indexOf('English');\r\n\t\t\t\tif (numLangIndex > -1) {\r\n\t\t\t\t\tfields.languages.splice(numLangIndex, 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfields.languages.sort();\r\n\r\n\t\t\tconst taxonomyData = objResult.taxonomies.data || objResult.taxonomies.items || [];\r\n\t\t\tif (taxonomyData && mapBadgeData) {\r\n\t\t\t\tif (taxonomyData.length > 0) {\r\n\t\t\t\t\tlet arCategories = taxonomyData[0].categories;\r\n\t\t\t\t\tif (arCategories.items) {\r\n\t\t\t\t\t\tarCategories = arCategories.items;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tfor (const category of arCategories) {\r\n\t\t\t\t\t\tif (mapBadgeData.has(category.id)) {\r\n\t\t\t\t\t\t\tfields.badges.push(mapBadgeData.get(category.id));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Extract fields from the results\r\n\t\tconst arLenderResults = this.shuffleArray(arLenderData.items.map((item) => ({id: item.id, ...item.fields})));\r\n\t\tconst arLanguageFilters = this.buildLanguageFilters(arLenderResults);\r\n\t\tconst rehabTooltip = rehabHomeTooltip && this.customExpand(rehabHomeTooltip);\r\n\t\tconst fixUpTooltip = fixUpHomeTooltip && this.customExpand(fixUpHomeTooltip);\r\n\t\tconst tooltips = {\r\n\t\t\trehab: rehabTooltip,\r\n\t\t\tfixUp: fixUpTooltip,\r\n\t\t};\r\n\t\t//build the result model\r\n\t\tlet model = {};\r\n\t\tmodel.lenderData = arLenderResults;\r\n\t\tmodel.languageFilters = arLanguageFilters;\r\n\t\tmodel.languageFilterClass = arLanguageFilters.length < 6 ? 'single' : '';\r\n\t\tmodel.iWantFilters = this.buildWantFilters(services, tooltips);\r\n\t\tmodel.clearBtn = clearBtn;\r\n\t\tmodel.columnHeaders = columnHeaders;\r\n\t\tmodel.languages = languages;\r\n\t\tmodel.resultsHeader = resultsHeader;\r\n\t\tmodel.search = search;\r\n\t\tmodel.searchBtn = searchBtn;\r\n\t\tmodel.services = services;\r\n\t\tmodel.distanceText = distanceText;\r\n\t\tmodel.misc = misc;\r\n\t\tmodel.pageNumbers = Array.from(Array(Math.ceil(model.lenderData.length / 10)).keys()).map(\r\n\t\t\t(i) => Object.assign({}, { pageNumber: i + 1 })\r\n\t\t);\r\n\t\tmodel.numPages = model.pageNumbers.length;\r\n\r\n\t\treturn model;\r\n\t}\r\n\r\n\tshuffleArray(array) {\r\n\t\t/* Durstenfeld shuffle algorithm */\r\n\t\tfor (let i = array.length - 1; i > 0; i--) {\r\n\t\t\tconst j = Math.floor(Math.random() * (i + 1));\r\n\t\t\t[array[i], array[j]] = [array[j], array[i]];\r\n\t\t}\r\n\t\treturn array;\r\n\t}\r\n\r\n\tbuildWantFilters(services, tooltips) {\r\n\t\tconst {\r\n\t\t\tall,\r\n\t\t\tbuy_first_home,\r\n\t\t\tbuy_new_home,\r\n\t\t\thome_improvement_loans,\r\n\t\t\trefinance,\r\n\t\t\trehabilitation_emergency,\r\n\t\t} = services;\r\n\t\treturn [\r\n\t\t\t{\r\n\t\t\t\tvalue: all.value,\r\n\t\t\t\tdesc: all.description,\r\n\t\t\t\tchecked: 'checked',\r\n\t\t\t\tindex: 0,\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tvalue: buy_first_home.value,\r\n\t\t\t\tdesc: buy_first_home.description,\r\n\t\t\t\tindex: 1,\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tvalue: buy_new_home.value,\r\n\t\t\t\tdesc: buy_new_home.description,\r\n\t\t\t\tindex: 2,\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tvalue: refinance.value,\r\n\t\t\t\tdesc: refinance.description,\r\n\t\t\t\tindex: 3,\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tvalue: home_improvement_loans.value,\r\n\t\t\t\tdesc: home_improvement_loans.description,\r\n\t\t\t\ttooltip: tooltips.fixUp,\r\n\t\t\t\tindex: 4,\r\n\t\t\t},\r\n\t\t\t{\r\n\t\t\t\tvalue: rehabilitation_emergency.value,\r\n\t\t\t\tdesc: rehabilitation_emergency.description,\r\n\t\t\t\ttooltip: tooltips.rehab,\r\n\t\t\t\tindex: 5,\r\n\t\t\t},\r\n\t\t];\r\n\t}\r\n\r\n\tbuildLanguageFilters(arLenderData) {\r\n\t\t//Run through all the lenders and get the unique languages spoken for the language filter then build\r\n\t\t//an array that is alphabetized in such a way that I can use a css grid and keep them alphabetized(top to bottom not\r\n\t\t//left to right) in multiple columns\r\n\r\n\t\tconst setLangFilter = new Set();\r\n\t\tlet arLanguageList = [];\r\n\t\t//arrays for handling two column vertical alphabetization\r\n\t\tlet arLeftCol = [];\r\n\t\tlet arRightCol = [];\r\n\r\n\t\t//grab the spoken languages\r\n\t\tfor (const objLender of arLenderData) {\r\n\t\t\tif (objLender.languages.length > 0) {\r\n\t\t\t\tfor (const lender of objLender.languages) {\r\n\t\t\t\t\tsetLangFilter.add(lender.trim());\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t//alphabetize the list\r\n\t\tarLanguageList = Array.from(setLangFilter);\r\n\t\tarLanguageList.sort();\r\n\r\n\t\tif (arLanguageList.length > 5) {\r\n\t\t\t// evenly split the columns and chuck them in the two arrays\r\n\t\t\tconst intPerColumn = Math.ceil(arLanguageList.length / 2);\r\n\t\t\tarLeftCol = arLanguageList.slice(0, intPerColumn);\r\n\t\t\tarRightCol = arLanguageList.slice(intPerColumn, arLanguageList.length);\r\n\r\n\t\t\t//merge the two arrays back together so they are now in order\r\n\t\t\tconst zipMerge = (arLeft, arRight) =>\r\n\t\t\t\tarLeft.length ? [arLeft[0], ...zipMerge(arRight, arLeft.slice(1))] : arRight;\r\n\t\t\tarLanguageList = zipMerge(arLeftCol, arRightCol);\r\n\t\t}\r\n\r\n\t\treturn arLanguageList;\r\n\t}\r\n\r\n\tbuildNavigation(numCurrentPage, totalResults) {\r\n\t\t//Build the paging navigation\r\n\t\tconst numTotalResults = totalResults || this.model.lenderData.length;\r\n\r\n\t\tconst numTotalPages = Math.ceil(numTotalResults / 10);\r\n\t\t//slice is inclusive so we need to drop back one more\r\n\t\tlet numLowerBound = numCurrentPage - 2 - 1;\r\n\t\tlet numUpperBound = numCurrentPage + 2;\r\n\r\n\t\t//build an array of page numbers that we can slice from to get the paging bounds\r\n\t\tlet pagingArray = [];\r\n\t\tfor (let i = 0; i < numTotalPages; i++) {\r\n\t\t\tpagingArray.push(i + 1);\r\n\t\t}\r\n\t\t//special range cases (Offset too close to 0 or total results)\r\n\t\tif (numLowerBound <= 0) {\r\n\t\t\t//take the amount we're negative and add that many to the upper bound\r\n\t\t\tnumUpperBound = numCurrentPage + Math.abs(numLowerBound) + 2;\r\n\t\t\tnumLowerBound = 0;\r\n\t\t\t//make sure adding to the other side doesn't cause it to go over\r\n\t\t\tif (numUpperBound > numTotalPages) {\r\n\t\t\t\tnumUpperBound = numTotalPages;\r\n\t\t\t}\r\n\t\t} else if (numUpperBound >= numTotalPages) {\r\n\t\t\tconst numUBoundOffset = numUpperBound - numTotalPages;\r\n\t\t\tnumUpperBound = numTotalPages;\r\n\t\t\tnumLowerBound = numCurrentPage - (numOffset + numUBoundOffset) - 1;\r\n\t\t\t//make sure adding to the other side doesn't cause it to go over\r\n\t\t\tif (numLowerBound < 1) {\r\n\t\t\t\tnumLowerBound = 1;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t//update paging html\r\n\t\t// pagingArray.slice(numLowerBound, numUpperBound)\r\n\t\tthis.model.paginationResults = this.buildPagingHTML(\r\n\t\t\tpagingArray,\r\n\t\t\tnumCurrentPage,\r\n\t\t\tnumTotalPages\r\n\t\t);\r\n\t\treturn this.model.paginationResults;\r\n\t}\r\n\r\n\tbuildPagingHTML(arPages, numCurrentPage, numTotalPages) {\r\n\t\tif (this?.model?.lenderData) {\r\n\t\t\tconst resultCount = `Showing ${numCurrentPage * 10} - ${numCurrentPage * 10 + 10} of ${\r\n\t\t\t\tthis.model.lenderData.length\r\n\t\t\t}`;\r\n\t\t\tthis.model.currentResults = resultCount;\r\n\t\t}\r\n\r\n\t\t//Build out the html for the paging buttons\r\n\t\tlet strDisabledForward = numCurrentPage === arPages.slice(0) ? 'disabled' : '';\r\n\t\tlet strDisabledBackward = numCurrentPage === 1 ? 'disabled' : '';\r\n\t\tlet strClassList;\r\n\t\tlet strHtmlTemplate = `\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t