I'm trying to develop a tabbed search results page using the Google Programmable Search Engine (PSE) and their JSON API in order to have the same appearance as Google's hosted search engine, which shows search and images results in two tabs, i.e. like this sample engine https://cse.google.com/cse?cx=b67b93cd338f948e5
The hosted search, above, shows the results for the search term in both the Web and the Image tabs. That's what I want to do with a search based on the JSON API.
I'm using this answer as a starting point for using the JSON API, Is there a working sample of the Google custom search rest API? and it works.
With the JSON API, I can show search results with the thumbnail and snippet, or only images, using the image only search.
My question: How do I show results from both types of searches at the same time in their respective tabs? Like the hosted search?
The problem I'm having is the hndlr(response)
which appends the script to the document. How do I add two hndlrs
? Or is that the wrong approach?
The hndlr
JSON response shows the thumbnail and snippet with a URL like this:
https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&q=test&callback=hndlr
And this URL shows image only results when &searchType=image is appended to the URL string, like this:
https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&q=test&callback=hndlr&searchType=image
One problem is that the JSON response.items
are not the same between the standard search and the image only search, so I can't simply append &searchType=image
to the URL.
Do I need to run two complete hndlr
loops? But if so, how do I append each to the document body without conflicts and to display in each tab?
So how can I get both thumbnail and snippet results in the Search Results tab and image only results in the Image Results tab at the same time and for the same query?
I can't do a JSFiddle, because the JSON API keys are limited to 100 queries, and the code references a URL, which won't work in a Fiddle.
(CSS and some Javascript response.items omitted for clarity. Both HTML and Javascript are in the same index.html file)
index.html:
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<form action="searchresults.html" id="cse-search-box">
<div>
<input class="" name="q" type="text">
<input class="" type="submit">
</div>
</form>
<ul class="nav nav-tabs" id="mytabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="tab-0" data-bs-toggle="tab" href="#tabpanel-0" role="tab" aria-controls="tabpanel-0" aria-selected="true">Search Results</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tab-1" data-bs-toggle="tab" href="#tabpanel-1" role="tab" aria-controls="tabpanel-1" aria-selected="false">Image Results</a>
</li>
</ul>
<div class="tab-content pt-5" id="tab-content">
<div class="tab-pane active" id="tabpanel-0" role="tabpanel" aria-labelledby="tab-0">
<div class="gsc-result-info" id="resultmeta"></div>
<div id="searchresults"></div>
</div>
<div class="tab-pane" id="tabpanel-1" role="tabpanel" aria-labelledby="tab-1">
<div class="gsc-result-info" id="resultmeta"></div>
<div id="images"></div></div>
</div>
<script> (Javascript below)
Javascript:
function hndlr(response) {
//Search results load time
document.getElementById("resultmeta").innerHTML = "About " + response.searchInformation.formattedTotalResults + " results (" + response.searchInformation.formattedSearchTime + " seconds)";
// Clear the divs first
document.getElementById("searchresults").innerHTML = "";
document.getElementById("images").innerHTML = "";
// Loop through each item in search results
for (var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var content = "";
var imagesonly = "";
// Search Results output simplified for clarity
// Title Link
content += "<a class='gs-title' href='" + item.link + "'>" + item.htmlTitle + "</a><br />";
// Thumbnail image for search results
content += "<a href='" + item.link + "'><img src='" + item.pagemap.cse_thumbnail[0].src + "'></a>";
}
// Pagination
var totalPages = Math.ceil(response.searchInformation.totalResults / 10);
console.log(totalPages);
var currentPage = Math.floor(start / 10 + 1);
console.log(currentPage);
var pageControls = "<div class='gsc-results'><div class='gsc-cursor-box gs-bidi-start-align' dir='ltr'><div class='gsc-cursor'>";
//Page change controls, 10 max.
for (var x = 1; x <= totalPages && x<=10; x++) {
pageControls += "<div class='gsc-cursor-page";
if (x === currentPage)
pageControls += " gsc-cursor-current-page";
var pageLinkStart = x * 10 - 9;
pageControls+="'><a href='test.html?start="+pageLinkStart+"&q="+query+"'>"+x+"</a></div>";
}
pageControls += "</div></div></div>";
document.getElementById("searchresults").innerHTML += pageControls;
document.getElementById("images").innerHTML += pageControls;
}
//Get search text from query string.
var query = document.URL.substr(document.URL.indexOf("q=") + 2);
var start = document.URL.substr(document.URL.indexOf("start=") + 6, 2);
if (start === "1&" || document.URL.indexOf("start=") === -1)
start = 1;
//Load the script src dynamically to load script with query to call.
// DOM: Create the script element
var gsearchresults = document.createElement("script");
// set the type attribute
gsearchresults.type = "application/javascript";
// make the script element load file
gsearchresults.src = "https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&start="+start+"&q=" +query +"&callback=hndlr";
// finally insert the element to the body element in order to load the script
document.body.appendChild(gsearchresults);
</script>
</body>
</html>
There's probably a cleaner and more efficient way to do this, but this works by running two API queries and two item loops, and changing the name of the function and variable so they don't collide. You'll need to add your HTML markup in each loop, but the basics are here. Load Bootstrap CSS and Javascript as needed.
test.html
<html>
<body>
<form action="test.html" id="cse-search-box">
<div>
<input class="searchform" name="q" type="text">
<input class="search" value="Search" type="submit">
</div>
</form>
<ul class="nav nav-tabs" id="mytabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="tab-0" data-bs-toggle="tab" href="#tabpanel-0" role="tab" aria-controls="tabpanel-0" aria-selected="true">Search Results</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tab-1" data-bs-toggle="tab" href="#tabpanel-1" role="tab" aria-controls="tabpanel-1" aria-selected="false">Image Results</a>
</li>
</ul>
<div class="tab-content pt-2" id="tab-content">
<div class="tab-pane active" id="tabpanel-0" role="tabpanel" aria-labelledby="tab-0">
<div class="gsc-result-info" id="resultmeta"></div>
<div id="searchresults"></div>
<script>
function hndlr(response) {
document.getElementById("resultmeta").innerHTML = "About " + response.searchInformation.formattedTotalResults + " results";
// Clear the div
document.getElementById("searchresults").innerHTML = "";
// Loop through each item in search results
for (var i = 0; i < response.items.length; i++) {
var item = response.items[i];
var content = "";
// Title Link
content += "<div class='gs-title-container'><a class='gs-title' href='" + item.link + "'>" + item.htmlTitle + "</a></div>";
// Thumbnail image for search results
content += "<div class='gs-image-container'><a class='gs-image' href='" + item.link + "'><img class='gs-image' src='" + item.pagemap.cse_thumbnail[0].src + "'></a></div>";
// Snippet text
content += "<div class='gs-snippet-container'>" + item.htmlSnippet + "</div>";
document.getElementById("searchresults").innerHTML += content;
}
// Pagination
var totalPages = Math.ceil(response.searchInformation.totalResults / 10);
console.log(totalPages);
var currentPage = Math.floor(start / 10 + 1);
console.log(currentPage);
var pageControls = "<div class='gsc-results'><div class='gsc-cursor-box gs-bidi-start-align' dir='ltr'><div class='gsc-cursor'>";
//Page change controls, 10 max.
for (var x = 1; x <= totalPages && x<=10; x++) {
pageControls += "<div class='gsc-cursor-page";
if (x === currentPage)
pageControls += " gsc-cursor-current-page";
var pageLinkStart = x * 10 - 9;
pageControls+="'><a href='test.html?start="+pageLinkStart+"&q="+query+"'>"+x+"</a></div>";
}
pageControls += "</div></div></div>";
document.getElementById("searchresults").innerHTML += pageControls;
}
//Get search text from query string.
var query = document.URL.substr(document.URL.indexOf("q=") + 2);
var start = document.URL.substr(document.URL.indexOf("start=") + 6, 2);
if (start === "1&" || document.URL.indexOf("start=") === -1)
start = 1;
//Load the script src dynamically to load script with query to call.
// DOM: Create the script element
var jsElm = document.createElement("script");
// set the type attribute
jsElm.type = "application/javascript";
// make the script element load file
jsElm.src = "https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&start="+start+"&q=" +query +"&callback=hndlr";
// finally insert the element to the body element in order to load the script
document.getElementById("searchresults").appendChild(jsElm)
</script>
<div class="tab-pane" id="tabpanel-1" role="tabpanel" aria-labelledby="tab-1">
<div class="gsc-result-info" id="resultmetaimages"></div>
<div id="imagesonly"></div>
<script>
function hndlrimages(response) {
document.getElementById("resultmetaimages").innerHTML = "About " + response.searchInformation.formattedTotalResults + " results";
// Clear the div
document.getElementById("imagesonly").innerHTML = "";
// Loop through each item in search results
for (var i = 0; i < response.items.length; i++) {
var imageitem = response.items[i];
var imagecontent = "";
// Image only results
document.getElementById("imagesonly").innerHTML += "<div class='container images-only'><a href='" + imageitem.link + "'><img class='img-fluid' src='" + imageitem.link + "'></a></div></div>";
}
// Pagination
var totalPages = Math.ceil(response.searchInformation.totalResults / 10);
var currentPage = Math.floor(start / 10 + 1);
var pageControls = "<div class='gsc-results'><div class='gsc-cursor-box gs-bidi-start-align' dir='ltr'><div class='gsc-cursor'>";
//Page change controls, 10 max.
for (var x = 1; x <= totalPages && x<=10; x++) {
pageControls += "<div class='gsc-cursor-page";
if (x === currentPage)
pageControls += " gsc-cursor-current-page";
var pageLinkStart = x * 10 - 9;
pageControls+="'><a href='test.html?start="+pageLinkStart+"&q="+query+"'>"+x+"</a></div>";
}
pageControls += "</div></div></div>";
document.getElementById("imagesonly").innerHTML += pageControls;
}
//Get search text from query string.
var query = document.URL.substr(document.URL.indexOf("q=") + 2);
var start = document.URL.substr(document.URL.indexOf("start=") + 6, 2);
if (start === "1&" || document.URL.indexOf("start=") === -1)
start = 1;
//Load the script src dynamically to load script with query to call.
// DOM: Create the script element
var jsElm = document.createElement("script");
// set the type attribute
jsElm.type = "application/javascript";
// make the script element load file
jsElm.src = "https://www.googleapis.com/customsearch/v1?key=KEY&cx=CX&start="+start+"&q=" +query +"&searchType=image&callback=hndlrimages";
// finally insert the element to the body element in order to load the script
document.getElementById("imagesonly").appendChild(jsElm)
</script>
</body>
</html>