{"version":3,"file":"blog.min.js","sources":["scripts/blog.min.js"],"sourcesContent":["/**************************************************************************\nName: blog.js\nDescription: Blog tile script. Includes lazy loading.\nDate Created: 06/03/2022 by Jenna Hart\nModified: 2022-10-12 by Owen Haggerty - Added blog search\n**************************************************************************/\n\nconst MIN_SEARCH_CHARS = 3;\t//The minimum number of characters needed to perform a blog search\nconst MIN_SEARCH_CHARS_STR = 'three';\nconst MAX_SEARCH_PREV = 5;\t//The maximum number of preview results to show\n\njQuery.noConflict();\n\nvar blogSearchData = [];\nvar hasBlogSearch = false;\nvar hasBlogError = false;\nvar blogSearchWorker; // initialized in document ready function\nvar hasBlogSearchWorker = true;\n\nvar blogOptions = blogOptions || (function(){\n\tvar thisData = {};\n\n\t// Data\n\tthisData.blogData; // Query - Blog post data\n\tthisData.type; // String - Diplay type (all, category, topic)\n\tthisData.callback; // String - If provided, calls the function provided after init\n\tthisData.category; // String - Category filter for the main blog landing\n\n\tvar isLoading = false;\n\n\tjQuery( document ).ready( function( )\n\t{\n\t\tjQuery(window).on( 'orientationchange resize', function() {\n\t\t\tsetWrapperHeight(false);\n\t\t});\n\t\t\n\t\tjQuery('body').on('click', '.load-posts:not(.is-loading)', function() {\n\t\t\tisLoading = true;\n\t\t\tjQuery(this).addClass('is-loading');\n\n\t\t\tsetTimeout(function() {\n\t\t\t\tgetBlogTiles();\n\t\t\t}, 100);\n\t\t});\n\n\t\tjQuery(window).on('scroll', function() {\n\t\t\tif(!isLoading) {\n\t\t\t\tsetLoadMore();\n\t\t\t\tsetWrapperHeight(false);\n\t\t\t}\n\t\t});\n\n\t\tjQuery('#category').on('change', function() {\n\t\t\tthisData.category = jQuery(this).val();\n\n\t\t\tresetOption();\n\t\t});\n\t});\n\n\tfunction getBlogTiles(end, callback)\n\t{\n\t\t/* Gets blog post tiles and adds them to the blog wrapper */\n\t\tisLoading = true;\n\n\t\tvar lastTile = jQuery('.blog-tile').last();\n\t\tvar perPage = thisData.perPage != undefined ? thisData.perPage - 1 : 11;\n\t\tvar dataObject = { method : 'getBlogTileHTMLAjax' }\n\n\t\tdataObject.data = JSON.stringify(thisData.blogData);\n\t\tdataObject.startRow = lastTile.length > 0 ? parseInt(lastTile.attr('data-row')) + 1 : 1;\n\t\tdataObject.endRow = end != undefined && end > 0 ? end : dataObject.startRow + perPage;\n\t\tdataObject.perPage = perPage;\n\t\tdataObject.type = thisData.type != undefined ? thisData.type : 'all';\n\t\tdataObject.category = thisData.category != undefined ? thisData.category : 'all';\n\n\t\tjQuery.ajax( {\n\t\t\ttype : 'post',\n\t\t\turl : '/cfcs/blogGW.cfc',\n\t\t\tcache : false,\n\t\t\tasync : true,\n\t\t\tdataType : 'json',\n\t\t\tdata : dataObject,\n\t\t\tsuccess : function( data ){\n\t\t\t\tvar wrapper = jQuery('.blog-wrapper');\n\n\t\t\t\treduceWrapperHeight();\n\n\t\t\t\tif(data) {\n\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\tjQuery(data).appendTo(wrapper);\n\n\t\t\t\t\t\tjQuery('.blog-loading').hide();\n\n\t\t\t\t\t\tsetWrapperHeight();\n\t\t\t\t\t}, 200);\n\t\t\t\t}\n\n\t\t\t\t// Wait until transitions have finished\n\t\t\t\tif(callback) {\n\t\t\t\t\tsetTimeout(function() {\n\t\t\t\t\t\tcallback();\n\t\t\t\t\t}, 1420);\n\t\t\t\t}\n\t\t\t},\n\t\t\terror : function( data ) {\n\t\t\t\tshowErrorNotification();\n\t\t\t}\n\t\t});\n\t}\n\n\tvar setLoadMore = debounce(function()\n\t{\n\t\tif(!isLoading) {\n\t\t\tvar countContainer = jQuery('.post-count'),\n\t\t\t\tcount = countContainer.attr('data-count'),\n\t\t\t\tcountTotal = countContainer.attr('data-totalcount'),\n\t\t\t\tlastTile = jQuery('.blog-tile').last(),\n\t\t\t\tlastTileVisible = lastTile.length > 0 && lastTile.visible(true) ? true : false,\n\t\t\t\tcountContainerVisible = countContainer.visible(true) ? true : false;\n\n\t\t\tif((lastTileVisible || countContainerVisible) && count != countTotal) {\n\t\t\t\tisLoading = true;\n\t\t\t\tjQuery('.load-posts').addClass('is-loading');\n\n\t\t\t\tvar tileDebounce = debounce(function(){\n\t\t\t\t\tgetBlogTiles();\n\t\t\t\t}, 300);\n\n\t\t\t\ttileDebounce();\n\t\t\t}\n\t\t}\n\t}, 100);\n\n\tvar showErrorNotification = function()\n\t{\n\t\tvar modal = jQuery('.modal-notify'),\n\t\t\tfocusElement = modal.find('.close-modal-button'),\n\t\t\tmessage = 'Unexpected issue, please try again',\n\t\t\tdelay = jQuery('.modal.active').length > 0 ? 510 : 0;\n\n\t\tsetTimeout(function() {\n\t\t\topenModal(modal, focusElement, message);\n\t\t}, delay);\n\t}\n\n\tvar reduceWrapperHeight = function()\n\t{\n\t\tvar transitionWrapper = jQuery('.blog-transition-wrapper'),\n\t\t\twrapper = jQuery('.blog-wrapper'),\n\t\t\tremoveOnLoad = jQuery('.remove-on-load'),\n\t\t\twrapperHeight = transitionWrapper.height(),\n\t\t\tloadHeight = removeOnLoad.height(),\n\t\t\tnewHeight = wrapperHeight - loadHeight;\n\n\t\ttransitionWrapper.addClass('is-quick').css('height', newHeight + 'px');\n\n\t\tsetTimeout(function() {\n\t\t\ttransitionWrapper.removeClass('is-quick');\n\t\t\tremoveOnLoad.remove();\n\t\t}, 300);\n\t}\n\n\tvar setWrapperHeight = debounce(function(setLoading) {\n\t\tif(setLoading == undefined)\n\t\t\tvar setLoading = true;\n\n\t\tvar transitionWrapper = jQuery('.blog-transition-wrapper'),\n\t\t\twrapper = jQuery('.blog-wrapper');\n\n\t\tsetTimeout(function() {\n\t\t\ttransitionWrapper.css('height', wrapper.height() + 'px');\n\t\t\tif(setLoading)\n\t\t\t\tisLoading = false;\n\t\t}, 210);\n\t}, 100);\n\n\tfunction resetOption()\n\t{\n\t\tjQuery('.blog-transition-wrapper').css('height', 0);\n\n\t\tsetTimeout(function(){\n\t\t\tjQuery('.blog-loading').show();\n\t\t}, 450);\n\n\t\tsetTimeout(function(){\n\t\t\tjQuery('.blog-wrapper').html('');\n\n\t\t\tgetBlogTiles();\n\t\t}, 550);\n\t}\n\n\treturn {\n\t\tinit : function(data) {\n\t\t\tthisData = data;\n\n\t\t\tif(thisData.callback != undefined)\n\t\t\tgetBlogTiles(0, thisData.callback);\n\t\telse\n\t\t\tgetBlogTiles();\n\t\t}\n\t};\n}());\n\njQuery(document).ready(function(){\n\tjQuery('.blog-menu').addClass('is-loaded');\n\tjQuery('.blog-menu-toggle').on('click', toggleMenu);\n\n\t//Find-as-you-type for blog search\n\tjQuery(\".blog-search-input\").on(\"keyup\", populateBlogFayt);\n\tjQuery(\".blog-search-input\").on(\"keyup\", clearHiddenBlogForm);\n\tjQuery(\".blog-search-input,.blog-search-button\").on(\"blur\", blogSearchBlur);\n\tjQuery(\".blog-search-input\").on(\"focus\", populateBlogFayt);\n\tjQuery(\".blog-search-button\").on(\"click\", catchEmptyBlogSearch);\n\n\t//Validation before submitting the blog search\n\tjQuery(\".blog-search-form\").on(\"submit\", validateBlogSearch);\n\n\t// Set up web worker\n\t// This allows FuseJS to search \"in the background\".\n\t// It isn't possible to do this any other way because the FuseJS library is written\n\t// completely synchronously, so there is no way to utilize async methods.\n\t// The web worker file imports fuse.min.js so we don't even need to attach it to the DOM.\n\tvar path = window.location.protocol + '//' + window.location.host;\n\tif(typeof(Worker) !== \"undefined\") {\n\t\tif(typeof(blogSearchWorker) == \"undefined\") {\n\t\t\tvar dataObject = {method: 'getMinLocAJAX', asset:\"/scripts/blogWorker.js\"}\n\n\t\t\tjQuery.ajax( {\n\t\t\t\ttype : 'GET',\n\t\t\t\turl : '/cfcs/assetManager.cfc',\n\t\t\t\tcache : false,\n\t\t\t\tasync : false,\n\t\t\t\tdataType : 'json',\n\t\t\t\tdata : dataObject,\n\t\t\t\tsuccess : function(assetLoc){\n\t\t\t\t\tblogSearchWorker = new Worker(path + assetLoc);\n\t\t\t\t},\n\t\t\t\terror : function( data ) {\n\t\t\t\t\tblogSearchWorker = new Worker(path + '/scripts/blogWorker.js');\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tblogSearchWorker.addEventListener('message', function(e) {\n\t\t\tvar result = e.data.result;\n\t\t\tvar val = jQuery('.blog-search-input:visible').val().trim();\n\n\t\t\tif(hasBlogSearch == false) {\n\t\t\t\t//Clean up the search value\n\t\t\t\tvar val_fixed = cleanSearchValue(val);\n\t\t\t}\n\t\t\telse\n\t\t\t\tval_fixed = val;\n\n\t\t\tif(result.length > 0 || hasBlogSearch == true) {\n\t\t\t\tsetBlogFaytResults(result);\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// if there are no search results, search again with useExtendedSearch turned off\n\t\t\t\thasBlogSearch = true;\n\t\t\t\tgetBlogResults(val_fixed);\n\t\t\t}\n\t\t});\n\t\tblogSearchWorker.addEventListener('error', function(e) {\n\t\t\t// stop web worker\n\t\t\tblogSearchWorker.terminate();\n\n\t\t\tif(e.message.includes(\"Import Error\")) {\n\t\t\t\thasBlogSearchWorker = false;\n\t\t\t\tjQuery.getScript('/scripts/fuse/dist/fuse.min.js');\n\t\t\t} else {\n\t\t\t\thasBlogError = true;\n\t\t\t\tsendErrorEmail(e.message, e.filename, e.lineno);\n\t\t\t}\n\t\t});\n\t} else {\n\t\t// no web worker support\n\t\thasBlogSearchWorker = false;\n\t\tjQuery.getScript('/scripts/fuse/dist/fuse.min.js');\n\t}\n});\n\nvar toggleMenu = function() {\n\tvar menu = jQuery('.blog-menu'),\n\tarticle = jQuery('article.mainContent');\n\n\tmenu.toggleClass('is-open');\n\t\n\tif(menu.hasClass('is-open'))\n\t{\n\t\tvar navHeight = jQuery('.blog-nav').outerHeight(false) + 60;\n\t\t\n\t\tarticle.css('min-height', navHeight + 'px');\n\t} else {\n\t\tarticle.css('min-height', 0);\n\t}\n}\n\nfunction blogSearchBlur(evt){\n\t/* Hides and clears the blog FAYT preview window when the focus is not within the blog search form */\n\t//Check if the newly focused element is in the blog search form\n\tif(jQuery(evt.relatedTarget).closest(\".blog-search-form\").length === 0 || jQuery(evt.relatedTarget).is(\"body\")){\n\t\t//Hide and clear the blog FAYT preview window\n\t\tjQuery(\".blog-search-fayt-window:visible\").removeClass(\"is-active is-loading\").css(\"height\",\"\").html(\"\");\n\t}\n}\n\nfunction catchEmptyBlogSearch(evt){\n\t/* Prevents the default HTML5 required field warning from showing */\n\tif(jQuery(\".blog-search-input:visible\").val().length === 0){\n\t\t//The required field is empty, prevent the warning from showing\n\t\tevt.preventDefault();\n\t\t//Check if the click event was actually a key press\n\t\tlet origEvent = evt.originalEvent;\n\t\tif(origEvent.type === \"click\" && (origEvent.clientX !== 0 || origEvent.clientY !== 0)){\n\t\t\t//Show the minimum character count warning\n\t\t\tlet faytWindow = jQuery(\".blog-search-fayt-window:visible\");\n\t\t\tif(faytWindow.length === 0){\n\t\t\t\t//Add the FAYT preview window if it doesn't already exist\n\t\t\t\tfaytWindow = jQuery(\"
\");\n\t\t\t\tfaytWindow.insertAfter(\".blog-search-input:visible\");\n\t\t\t}\n\t\t\tfaytWindow.addClass(\"is-active\").removeClass(\"is-loading\");\n\t\t\tfaytWindow.html(`
Minimum of ${MIN_SEARCH_CHARS_STR} characters required
`);\n\t\t\tsetBlogFaytHeight();\n\t\t}\n\t}\n}\n\nfunction clearHiddenBlogForm(){\n\t/* Clears the hidden post IDs from the blog form */\n\tjQuery(\".blog-search-form:visible .blog-search-posts\").val(\"\");\n}\n\nlet populateBlogFayt = debounce(function(){\n\t/* Populates the FAYT preview window and the hidden search form as the user types */\n\t//Select or create the FAYT preview window\n\tlet faytWindow = jQuery(\".blog-search-fayt-window:visible\");\n\tlet searchTerm = jQuery(\".blog-search-input:visible\").val().trim();\n\tif(faytWindow.length === 0){\n\t\t//Add the FAYT preview window if it doesn't already exist\n\t\tfaytWindow = jQuery(\"
\");\n\t\tfaytWindow.insertAfter(\".blog-search-input:visible\");\n\t}\n\t//Clear any existing results from the FAYT window\n\tfaytWindow.html(\"\").css(\"height\",\"\");\n\n\t//Don't perform the search if there are not enough characters in the search term\n\tif(searchTerm.length > 0 && searchTerm.length < MIN_SEARCH_CHARS){\n\t\tfaytWindow.html(`
Minimum of ${MIN_SEARCH_CHARS_STR} characters required
`);\n\t\tfaytWindow.addClass(\"is-active\").removeClass(\"is-loading\");\n\t\tsetBlogFaytHeight();\n\t\treturn;\n\t}\n\telse if(searchTerm.length === 0){\n\t\t//Hide the FAYT window\n\t\tfaytWindow.removeClass(\"is-active is-loading\");\n\t\treturn;\n\t}\n\n\t//Delay adding the classes so that the css transition is triggered\n\twindow.setTimeout(function(){\n\t\tfaytWindow.addClass(\"is-active is-loading\");\n\t},5);\n\n\tgetBlogResults(encodeURIComponent(searchTerm));\n\n\t//Request the posts from the back end\n\t// jQuery.ajax({\n\t// \ttype: \"GET\",\n\t// \turl: \"/cfcs/blogGW.cfc\",\n\t// \tdataType: \"json\",\n\t// \tdata: {\n\t// \t\tmethod: \"getFaytAjax\",\n\t// \t\tsearchTerm: encodeURIComponent(searchTerm)\n\t// \t},\n\t// \tsuccess: function(res){\n\t// \t\t/* Displays the FAYT search results and populates the hidden search form */\n\t// \t\tfaytWindow.removeClass(\"is-loading\");\n\t// \t\tif(!Array.isArray(res)){\n\t// \t\t\t//The response is in the wrong format, handle the error gracefully\n\t// \t\t\tfaytWindow.html(\"
Error performing search, please try again
\");\n\t// \t\t\tsetBlogFaytHeight();\n\t// \t\t\treturn;\n\t// \t\t}\n\t// \t\t//Populate the hidden search form\n\t// \t\tlet postIds = res.map(x => x.ID);\n\t// \t\tjQuery(\".blog-search-form:visible .blog-search-posts\").val(postIds.join(\",\"));\n\n\t// \t\t//Display the search preview\n\t// \t\tlet previewCount = Math.min(MAX_SEARCH_PREV, res.length);\n\t// \t\tif(previewCount === 0){\n\t// \t\t\t//No search results were found, show the no search results message\n\t// \t\t\tfaytWindow.html(\"
No matching posts were found
\");\n\t// \t\t\tsetBlogFaytHeight();\n\t// \t\t\treturn;\n\t// \t\t}\n\t// \t\t//Generate a regular expression to style exact matches of the search term in search results\n\t// \t\tlet searchTermRegex = new RegExp(`(${searchTerm.replace(/[?*/\\\\+\\-()\\[\\]\\{\\}.^$|]/g,function(char){\n\t// \t\t\t//Escape special characters to use in the regular expression\n\t// \t\t\treturn \"\\\\\" + char;\n\t// \t\t})})`, 'gi');\n\t// \t\tfor(let i=0; i\");\n\t// \t\t\t//Escape the blog title for html\n\t// \t\t\tlet tmpDiv = document.createElement(\"div\");\n\t// \t\t\ttmpDiv.innerText = res[i].TITLE;\n\t// \t\t\tlet escapedTitle = tmpDiv.innerHTML;\n\t// \t\t\t//Wrap the search term in a span\n\t// \t\t\tresLink.html(escapedTitle.replace(searchTermRegex, function(match){\n\t// \t\t\t\treturn `${match}`;\n\t// \t\t\t}));\n\t// \t\t\tresLink.attr(\"href\", `/about/blog/${res[i].LINK}`);\n\t// \t\t\tfaytWindow.append(resLink);\n\t// \t\t}\n\t// \t\t//Add a button to prompt for a full search\n\t// \t\tif(res.length > MAX_SEARCH_PREV){\n\t// \t\t\tlet moreButton = jQuery(\"