if (window.Element && !Element.prototype.closest) { Element.prototype.closest = function (s) { var matches = (this.document || this.ownerDocument).querySelectorAll(s), i, el = this; do { i = matches.length; while (--i >= 0 && matches.item(i) !== el) { }; } while ((i < 0) && (el = el.parentElement)); return el; }; } (function (w, d) { var widgetUrl = "https://widget.trustist.com"; var assetUrl = "https://wassets.trustist.com/assets"; var businessKey = "w2r8wjop"; var locationKey = "vq01c7bk"; var tenantKey = ""; var tag = ""; var activeWidgets = []; var accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbnRpdHlJZCI6InZxMDFjN2JrIiwiZXhwIjoxNzIwODE2MjA4LCJpc3MiOiJUcnVzdGlzdCIsImF1ZCI6IldpZGdldHMifQ.2iV1z4lcoyPl8W-6yadFMVt8wYGrug8301i60bNkdGg"; var accessTokenExpiry = "1720816208000"; var refreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbnRpdHlJZCI6InZxMDFjN2JrIiwianRpIjoiNzQ0NDc0MTktZmQ3Yi00NzZmLTg1YmEtNDNjMzI5MTk1MzhlIiwiZXhwIjoxNzIwOTg4NDA4LCJpc3MiOiJUcnVzdGlzdCIsImF1ZCI6IldpZGdldHMifQ.uxoD5hdjUEXFn-lHl7eLtCpxGiXRi8oqs1zzYMww1lo"; var template = '
'; var paginator = ''; function start(f) { /in/.test(document.readyState) ? setTimeout(start, 5, f) : f(); } // add tags tsAddCssTag(assetUrl + "/css/cleanslate.css"); tsAddCssTag(assetUrl + "/css/reviewlist.css"); start(function () { // iterate over all widgets on the page and render var divs = d.querySelectorAll("[ts-reviewListWidget],div.ts-reviewListWidget"); if (divs.length === 0) { return; } for (var i = 0; i < divs.length; i++) { tsDisplayWidget(divs[i], tenantKey, businessKey, locationKey); } }); function tsDisplayWidget(target, tenantKey, businessKey, locationKey) { // get options before cleanslate var options = tsReadAttributes(target); // see if the widget requested a different location var lkey = options.locationKey || locationKey; var bkey = options.businessKey || businessKey; var tkey = options.tenantKey || tenantKey; var tagkey = options.tag || tag; // set the target's identifiers for later drawing target.setAttribute("ts-location", lkey); target.setAttribute("ts-business", bkey); target.setAttribute("ts-tenant", tkey); target.setAttribute("ts-tag", tagkey); // store this widget for later activeWidgets.push({ locationKey: lkey, businessKey: bkey, tenantKey: tkey, tag: tagkey, currentPage: 1, pageSize: options.pageSize, currentReviewCount: 0, totalReviewCount: 0 }); // put the container on the page target.innerHTML = template .replace("{{font-family}}", options.fontFamily) .replace("{{color}}", options.color); // draw summary var xhr2 = new XMLHttpRequest(); var url = tsBuildSummaryUrl(widgetUrl, tkey, bkey, lkey, tagkey); xhr2.open("GET", url); xhr2.onload = function () { if (xhr2.status === 200 && xhr2.responseText !== "") { var summary = JSON.parse(xhr2.responseText); // set the review count on the active widget tsSetActiveWidgetValue(tenantKey, businessKey, locationKey, tagkey, "totalReviewCount", summary.reviewCount); // load and draw reviews tsLoadReviews(target, tenantKey, businessKey, locationKey, tagkey, options); } }; xhr2.send(); } function tsGetActiveWidget(tenantKey, businessKey, locationKey, tag) { var filtered = activeWidgets.filter(function (o) { return o.tenantKey === tenantKey && o.businessKey === businessKey && locationKey === locationKey && tag === tag; }); return filtered[0]; } function tsSetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, key, value) { var activeWidget = tsGetActiveWidget(tenantKey, businessKey, locationKey, tag); activeWidget[key] = value; } function tsGetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, key) { var activeWidget = tsGetActiveWidget(tenantKey, businessKey, locationKey, tag); return activeWidget[key]; } function tsAddToActiveWidgetValue(tenantKey, businessKey, locationKey, tag, key, value) { var activeWidget = tsGetActiveWidget(tenantKey, businessKey, locationKey, tag); activeWidget[key] += value; } function shouldRefreshToken() { var currentTime = new Date().getTime(); var oneMinuteInMilliseconds = 60000; return currentTime > (accessTokenExpiry - oneMinuteInMilliseconds); } function tsRefreshAccessToken(tenantKey, businessKey, locationKey, callback) { if (!shouldRefreshToken()) { callback(); return; } var url = widgetUrl + "/refreshToken?refreshToken=" + refreshToken; if (typeof tenantKey !== "undefined" && tenantKey !== "") url += "&tenantKey=" + tenantKey; if (typeof businessKey !== "undefined" && businessKey !== "") url += "&businessKey=" + businessKey; if (typeof locationKey !== "undefined" && locationKey !== "") url += "&locationKey=" + locationKey; var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function () { if (xhr.status === 200 && xhr.responseText !== "") { var json = JSON.parse(xhr.responseText); accessToken = json.accessToken.accessToken; accessTokenExpiry = json.accessToken.expiry; refreshToken = json.refreshToken.accessToken; } callback(); }; xhr.send(); } function tsLoadReviews(target, tenantKey, businessKey, locationKey, tag, options) { tsRefreshAccessToken(tenantKey, businessKey, locationKey, () => { var xhr = new XMLHttpRequest(); var currentPage = tsGetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, "currentPage"); var pageSize = tsGetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, "pageSize"); var url = tsBuildUrl(widgetUrl, currentPage, pageSize, tenantKey, businessKey, locationKey, tag, options); xhr.open("GET", url); xhr.onload = function () { if (xhr.status === 200 && xhr.responseText !== "") { // update local count var reviews = JSON.parse(xhr.responseText); tsAddToActiveWidgetValue(tenantKey, businessKey, locationKey, tag, "currentReviewCount", reviews.length); // draw the reviews tsDrawReviews(target, reviews, tenantKey, businessKey, locationKey, tag, options); } else { } }; xhr.send(); }); } function tsBuildUrl(widgetUrl, currentPage, pageSize, tenantKey, businessKey, locationKey, tag, options) { var url = widgetUrl + "/reviewlistdata?v=1&accessToken=" + accessToken + "&pageNum=" + currentPage + "&pageSize=" + pageSize; if (typeof tenantKey !== "undefined" && tenantKey !== "") url += "&tenantKey=" + tenantKey; if (typeof businessKey !== "undefined" && businessKey !== "") url += "&businessKey=" + businessKey; if (typeof locationKey !== "undefined" && locationKey !== "") url += "&locationKey=" + locationKey; if (typeof tag !== "undefined" && tag !== "") url += "&tag=" + tag; if (options.ratingsAbove > 0) url += "&ratingFrom=" + options.ratingsAbove; if (options.exclude > '') url += "&exclude=" + options.exclude; if (options.displayFullName) url += "&displayFullName=true"; if (options.displayAdditionalData) url += "&includeAdditionalData=true"; return url; } function tsBuildSummaryUrl(widgetUrl, tkey, bkey, lkey, tagkey) { var url = widgetUrl + "/reviewsummary?"; if (typeof tkey !== "undefined" && tkey !== "") url += "&tenantKey=" + tkey; if (typeof bkey !== "undefined" && bkey !== "") url += "&businessKey=" + bkey; if (typeof lkey !== "undefined" && lkey !== "") url += "&locationKey=" + lkey; if (typeof tagkey !== "undefined" && tagkey !== "") url += "&tag=" + tagkey; return url; } function tsDrawReviews(target, reviews, tenantKey, businessKey, locationKey, tag, options) { var reviewContent = ""; var totalReviews = tsGetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, "totalReviewCount"); var currentReviews = tsGetActiveWidgetValue(tenantKey, businessKey, locationKey, tag, "currentReviewCount"); var btnSubmit; reviews.forEach(function (review) { reviewContent += tsRenderReview(review, options) .replace(new RegExp("{{font-family}}", "g"), options.fontFamily) .replace(new RegExp("{{color}}", "g"), options.color); }); // add the reviews to the page but retain the comment for additional reviews to be added later target.innerHTML = target.innerHTML.replace("", reviewContent + ""); if (currentReviews < totalReviews) { if (target.innerHTML.indexOf("") > 0) { // add the paginator if it doesn't already exist target.innerHTML = target.innerHTML.replace("", paginator); // now hook up to the event btnSubmit = target.querySelector("#tsLoadMore"); btnSubmit.setAttribute("ts-location", locationKey); btnSubmit.setAttribute("ts-business", businessKey); btnSubmit.setAttribute("ts-tenant", tenantKey); btnSubmit.setAttribute("ts-tag", tag); btnSubmit.addEventListener("click", tsLoadMore); } else { // still more reviews to show so reenable the show more button btnSubmit = target.querySelector("#tsLoadMore"); btnSubmit.addEventListener("click", tsLoadMore); // reconnect the disconnected event listener btnSubmit.disabled = false; } } else { // shown all the reviews, remove the button btnSubmit = target.querySelector("#tsLoadMore"); if (btnSubmit) { btnSubmit.style.setProperty("display", "none", "important"); } } } function tsLoadMore(evnt) { // scoped to the button evnt.preventDefault(); this.disabled = true; var tenant = this.getAttribute("ts-tenant"); var business = this.getAttribute("ts-business"); var location = this.getAttribute("ts-location"); var tag = this.getAttribute("ts-tag"); var target = this.closest("[ts-reviewlistwidget],div.ts-reviewListWidget"); var options = tsReadAttributes(target); tsAddToActiveWidgetValue(tenant, business, location, tag, "currentPage", 1); tsLoadReviews(target, tenant, business, location, tag, options); } function tsRenderReview(review, options) { var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; var d = new Date(review.datePublished); var curr_date = d.getDate(); var curr_month = d.getMonth(); //Months are zero based var curr_year = d.getFullYear(); var dateString = curr_date + " " + months[curr_month] + " " + curr_year; var reviewContent = review.reviewBody || ""; var reviewBy = "Customer Review"; var reviewDate = ""; var reviewReply = ""; var truncateLength = Number(options.truncateLength); var reviewId = review.datePublished.replace(new RegExp(":", "g"), "-"); if (isNaN(truncateLength) === false && truncateLength > 0 && reviewContent.length > truncateLength) { reviewContent = reviewContent.substr(0, truncateLength) + '... read more' + '' + reviewContent.substr(truncateLength, reviewContent.length - truncateLength) + ''; } if (options.showLocation) { reviewBy += " for " + review.subject; } if (options.suppressReviewNames !== "true") { reviewBy += " by " + review.author.name; } if (options.suppressReviewNames !== "true" && options.suppressReviewDates !== "true") { reviewDate = " left on " + dateString; } var reviewLine1 = options.suppressTitle === "false" ? reviewBy + reviewDate : ""; if (review.reply && review.reply !== "") { var replyText = "Reply"; if (options.suppressReviewDates !== "true") { var replyDate = new Date(review.replyDate); var replyDateString = replyDate.getDate() + " " + months[replyDate.getMonth()] + " " + replyDate.getFullYear(); replyText += " left " + replyDateString; } reviewReply = '' + replyText + ':  ' + review.reply + ''; } var verified = ''; if (options.suppressVerified === "false" && review["@type"].toUpperCase() === "9A9B0D66-E568-474D-939B-F1859A3769D9") { // review.verified && verified = '   Verified Review'; } var reply = ''; var content = ''; var answers = ''; if (options.displayAdditionalData && review.answers) { for (i = 0; i < review.answers.length; i++) { answers += '
' + review.answers[i].answer + '
'; } } if (options.suppressReplies === false) { reply = '
' + reviewReply + '
'; } if (options.suppressReviewLinks === "true" || options.suppressReviewLinks === "all" || options.suppressReviewLinks.toUpperCase().indexOf(review["@type"].toUpperCase()) > 1) { if (reviewLine1 !== "") { reviewLine1 = '
' + reviewLine1 + "
"; } content = '
' + '
' + reviewLine1 + answers; if (options.suppressStars === "false") content += '
' + verified; content += '
' + reviewContent + '
' + reply + '
'; } else { if (reviewLine1 !== "") { reviewLine1 = '
' + '' + reviewLine1 + "
"; } content = '
' + reviewLine1 + answers; if (options.suppressStars === "false") content += '' + '
' + verified; content += '
' + reviewContent + '
' + reply + '
'; } return content; } function tsReadAttributes(target) { return { locationKey: target.getAttribute("ts-location"), businessKey: target.getAttribute("ts-business"), tag: target.getAttribute("ts-tag"), fontFamily: target.getAttribute("ts-font-family") || "'Roboto', sans-serif", color: target.getAttribute("ts-color") || "#5a6a74", pageSize: target.getAttribute("ts-page-size") || 20, suppressStars: target.getAttribute("ts-suppress-stars") || "false", suppressReplies: target.getAttribute("ts-suppress-replies") || false, suppressSummary: target.getAttribute("ts-suppress-summary") || false, suppressReviewLinks: target.getAttribute("ts-suppress-review-links") || "none", suppressReviewDates: target.getAttribute("ts-suppress-review-dates") || false, suppressReviewNames: target.getAttribute("ts-suppress-review-names") || false, suppressTitle: target.getAttribute("ts-suppress-title") || "false", suppressVerified: target.getAttribute("ts-suppress-verified") || "false", linkTarget: target.getAttribute("ts-link-target") || "_self", showLocation: target.getAttribute('ts-show-location') === "true", ratingsAbove: parseInt(target.getAttribute("ts-ratings-above")) || 0, truncateLength: target.getAttribute("ts-truncate-length") || "0", exclude: target.getAttribute("ts-exclude") || "", displayFullName: target.getAttribute("ts-display-full-name") || null, displayAdditionalData: target.getAttribute("ts-display-additional-data") || null, }; } function tsAddCssTag(url) { var includedAlready = tsCheckIfIncluded(url); var head = d.getElementsByTagName("head")[0]; if (!includedAlready) { var tag = d.createElement("link"); tag.rel = "stylesheet"; tag.type = "text/css"; tag.href = url; head.appendChild(tag); } } function tsCheckIfIncluded(file) { var links = d.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { if (links[i].href?.substr(-file.length) === file) { return true; } } var scripts = d.getElementsByTagName("script"); for (var j = 0; j < scripts.length; j++) { if (scripts[j].src?.substr(-file.length) === file) { return true; } } return false; } })(window, document);