javascriptjsongoogle-custom-searchgoogle-search-api

How can I get tabbed site and image search results using Google Programmable Search?


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

enter image description here

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>

Solution

  • 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>