Example page layout: Bad Boys for life

Example JSON: Bad Boys for life

{ "nid": "bad-boys-for-life", "imdb": "tt1502397", "type": "single", "originalTitle": "Bad Boys for Life", "title": "Bad Boys For Life", "releaseYear": "2020", "normalizedScore": "5.58", "reviews": [ { "link": "http://cinemazone.dk/review.asp?id=11799&area=1", "source": { "id": 0, "name": "CinemaZone.dk", "rank": 10, "language": "DA" }, "score": "4", "maxScore": "6", "displayScore": "4/6", "normalizedScore": "6.67", "quotes": [ { "text": "Smith & Lawrence er fortsat fine sammen, og de nye på holdet vokser med filmen." } ] } ] }

How to use

Plain JavaScript (click to expand)

const MY_GUID = "[your GUID]"; // The GUID of your installation const IMDB_ID = "tt1502397"; // The IMDB ID for the desired program const SEASON_NUMBER = undefined; window.onload = function() { fetch(`https://api.lucidity.tv/json/${MY_GUID}/${IMDB_ID}.json`) .then(response => response.json()) .then(json => addReviewsToPage({ seasonNumber: SEASON_NUMBER, data: json })) .catch(error => { // No reviews available }); }; const REQUIRED_SCORE_FOR_AVERAGE = 5; // The average score will only be displayed if it is at least 5 or higher. const REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE = 5; // The average score will only be displayed if we have at least 5 reviews with scores available. const AVERAGE_EXPLANATION_TEXT = "Average score based on local reviews."; // The text displayed along with the average score. const REQUIRED_REVIEW_SCORE = 2;// Only reviews with a normalised score of at least 2 will be displayed. const NR_OF_QUOTES = 3; // We will display 3 quotes including scores if available. const NR_OF_SCORES = 5; // We will display 5 scores if available. const INCLUDED_LANGUAGES = ["DA", "EN"]; // Only include reviews from sources with these languages function addReviewsToPage({ seasonNumber, data = {} }) { let review = data; // For series, there will be separate objects for each season, identified by the season number if (seasonNumber !== undefined && Array.isArray(data)) { review = data.find(r => r.seasonNumber === seasonNumber) ?? {}; } const { normalizedScore, reviews = [] } = review; if (!reviews?.length) { return null; } const showNormalizedScore = parseFloat(normalizedScore) >= REQUIRED_SCORE_FOR_AVERAGE && reviews.length >= REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE; const reviewsWithQuotes = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && r.quotes?.length).slice(0, NR_OF_QUOTES); const otherReviews = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && !reviewsWithQuotes.includes(r)).slice(0, NR_OF_SCORES); const container = _createElement("div", { className: "june-lucidity-program-reviews" }); if (showNormalizedScore) { const scoreWrapper = _createElement("div", { className: "normalized-score" }); scoreWrapper.appendChild(_createElement("span", { innerText: roundToHalf(normalizedScore) })); scoreWrapper.appendChild(_createElement("span", { innerText: AVERAGE_EXPLANATION_TEXT })); container.appendChild(scoreWrapper); } reviewsWithQuotes.forEach(r => container.appendChild(getReviewElement(r, true))); otherReviews.forEach(r => container.appendChild(getReviewElement(r, false))); const productInfoContainer = document.querySelector("#Movie > div.Product__info > div > div > div.col-sm-9"); productInfoContainer.insertBefore(container, productInfoContainer.firstChild); } function getReviewElement(review, includeQuote) { const reviewElement = _createElement("div", { className: "review" }); if (includeQuote) { reviewElement.classList.add("include-quote"); reviewElement.appendChild(_createElement("span", { className: "quote", innerText: `"${review.quotes[0].text}"` })); } const scoreWrapper = _createElement("div", { className: "score-source" }); scoreWrapper.appendChild(getReviewScore(review)); scoreWrapper.appendChild(getReviewSource(review)); reviewElement.appendChild(scoreWrapper); return reviewElement; } function getReviewScore(review) { const image = review.assets?.find(a => a.format === "h30px"); const tagName = image ? "img" : "span"; return _createElement(tagName, { className: "score", src: image?.url, innerText: !image && review.displayScore, }); } function getReviewSource(review) { const sourceUrl = review.source.assets?.find(a => a.format === "h30px")?.url; const element = _createElement("a", { className: "source", target: "_blank", href: review.link, }); if (sourceUrl) { element.appendChild(_createElement("img", { className: "source-image", src: sourceUrl, })); } else { element.innerText = review.source.name; } return element; } function roundToHalf(num) { return Math.round(parseFloat(num) * 2) / 2; } function _createElement(tagName, properties) { const element = document.createElement(tagName); Object.entries(properties).forEach(([key, value]) => element[key] = value); return element; }

React (click to expand)

// This React example does not include fetching the data from the JSON file. // Please see the "Plain JavaScript" example above to see an example of how that can be done. const REQUIRED_SCORE_FOR_AVERAGE = 5; // The average score will only be displayed if it is at least 5 or higher. const REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE = 5; // The average score will only be displayed if we have at least 5 reviews with scores available. const AVERAGE_EXPLANATION_TEXT = "Average score based on local reviews."; // The text displayed along with the average score. const REQUIRED_REVIEW_SCORE = 2;// Only reviews with a normalised score of at least 2 will be displayed. const NR_OF_QUOTES = 3; // We will display 3 quotes including scores if available. const NR_OF_SCORES = 5; // We will display 5 scores if available. const INCLUDED_LANGUAGES = ["DA", "EN"]; // Only include reviews from sources with these languages export function renderProgramReviews({ seasonNumber, data = {} }) { let review = data; // For series, there will be separate objects for each season, identified by the season number if (seasonNumber !== undefined && Array.isArray(data)) { review = data.find(r => r.seasonNumber === seasonNumber) ?? {}; } const { normalizedScore, reviews = [] } = review; if (!reviews?.length) { return null; } const showNormalizedScore = parseFloat(normalizedScore) >= REQUIRED_SCORE_FOR_AVERAGE && reviews.length >= REQUIRED_NR_OF_REVIEWS_FOR_AVERAGE; const reviewsWithQuotes = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && r.quotes?.length).slice(0, NR_OF_QUOTES); const otherReviews = reviews.filter(r => INCLUDED_LANGUAGES.includes(r.source.language) && parseFloat(r.normalizedScore) >= REQUIRED_REVIEW_SCORE && !reviewsWithQuotes.includes(r)).slice(0, NR_OF_SCORES); return ( <div className="program-reviews"> {showNormalizedScore && ( <div className="normalized-score"> <span>{roundToHalf(normalizedScore)}</span> <span>{AVERAGE_EXPLANATION_TEXT}</span> </div> )} {reviewsWithQuotes.map(r => renderReview(r, true))} <br/> {otherReviews.map(r => renderReview(r, false))} </div> ); } function renderReview(review, includeQuote) { return ( <div className={`review ${includeQuote ? "include-quote" : ""}`} key={review.source.name}> {includeQuote && <span className="quote">{review.quotes[0].text}</span>} <div className="score-source"> {renderReviewScore(review)} {renderSource(review)} </div> </div> ); } function renderReviewScore(review) { const image = review.assets?.find(a => a.format === "h30px"); return image ? <img className="score" src={image.url} /> : <span className="score">{review.displayScore}</span>; } function renderSource(review) { const sourceUrl = review.source.assets?.find(a => a.format === "h30px")?.url; const sourceImage = sourceUrl ? <img className="source-image" src={sourceUrl} /> : null; return ( <a className="source" target="_blank" href={review.link}>{sourceImage ?? review.source.name}</a> ); } function roundToHalf(num) { return Math.round(parseFloat(num) * 2) / 2; }

Icons

You can find links to icons for the scores and sources in the JSON file. The list looks like this:

"assets": [ { "name": "score", "format": "h30px", "url": "https://api.lucidity.tv/images/91/91_2_h30px.png" }, { "name": "score", "format": "h100px", "url": "https://api.lucidity.tv/images/91/91_2_h100px.png" }, { "name": "score", "format": "h300px", "url": "https://api.lucidity.tv/images/91/91_2_h300px.png" }, { "name": "score", "format": "h900px", "url": "https://api.lucidity.tv/images/91/91_2_h900px.png" } ]

The "format" field indicates the dimensions, h100px means that the image height is 100px.