Create a search interface with the search widget

The search widget provides a customizable search interface for web applications. The widget only requires a small amount of HTML and JavaScript to implement and enables common search features such as facets and pagination. You can also customize portions of the interface with CSS and JavaScript.

If you need more flexibility than offered by the widget, consider using the Query API. For information on creating a search interface with the Query API, refer to Creating a search interface with the Query API .

Build a search interface

Building the search interface requires several steps:

  1. Configure a search application
  2. Generate a client ID for the application
  3. Add HTML markup for the search box and results
  4. Load the widget on the page
  5. Initialize the widget

Configure a search application

Each search interface must have an search application defined in the admin console. The search application provides additional information for the query, such as the data sources, facets, and search quality settings.

To create a search application, refer to Create a custom search experience .

Generate a client ID for the application

In addition to the steps in Configure access to the Google Cloud Search API , you also must generate a client ID for the web application.

Configure a project

When you configure the project:

  • Select the Web browserclient type
  • Provide the origin URI of your app.
  • Note of the client ID that was created. You will need the client ID to complete the next steps. The client secret is not required for the widget.

For additional information, see OAuth 2.0 for Client-side Web Application .

Add HTML markup

The widget requires a small amount of HTML to function. You must provide:

  • An input element for the search box.
  • An element to anchor the suggestion popup to.
  • An element to contain the search results.
  • (Optional) Provide an element to contain the facets controls.

The following HTML snippet shows the HTML for a search widget, where the elements to be bound are identified by their id attribute:

serving/widget/public/with_css/index.html
<div id="search_bar">
  <div id="suggestions_anchor">
    <input type="text" id="search_input" placeholder="Search for...">
  </div>
</div>
<div id="facet_results"></div>
<div id="search_results"></div>

Load the widget

The widget is dynamically loaded via a loader script. To include the loader, use the <script> tag as shown:

serving/widget/public/with_css/index.html
<!-- Google API loader -->
<script src="https://apis.google.com/js/api.js?mods=enable_cloud_search_widget&onload=onLoad" async defer></script>

You must provide an onload callback in the script tag. The function is called when the loader is ready. When the loader is ready, continue to load the widget by calling gapi.load() to load the API client, Google Sign-in, and Cloud Search modules.

serving/widget/public/with_css/app.js
 /** 
 * Load the cloud search widget & auth libraries. Runs after 
 * the initial gapi bootstrap library is ready. 
 */ 
 function 
  
 onLoad 
 () 
  
 { 
  
 gapi 
 . 
 load 
 ( 
 'client:auth2:cloudsearch-widget' 
 , 
  
 initializeApp 
 ) 
 } 

The initializeApp() function is called after all modules are loaded.

Initialize the widget

First, initialize the client library by calling gapi.client.init() or gapi.auth2.init() with your generated client ID and the https://www.googleapis.com/auth/cloud_search.query scope. Next, use the gapi.cloudsearch.widget.resultscontainer.Builder and gapi.cloudsearch.widget.searchbox.Builder classes to configure the widget and bind it to your HTML elements.

The following example shows how to initialize the widget:

serving/widget/public/with_css/app.js
 /** 
 * Initialize the app after loading the Google API client 
& * Cloud Search widget. 
 */ 
 function 
  
 initializeApp 
 () 
  
 { 
  
 // Load client ID & search app. 
  
 loadConfiguration 
 (). 
 then 
 ( 
 function 
 () 
  
 { 
  
 // Set API version to v1. 
  
 gapi 
 . 
 config 
 . 
 update 
 ( 
 'cloudsearch.config/apiVersion' 
 , 
  
 'v1' 
 ); 
  
 // Build the result container and bind to DOM elements. 
  
 var 
  
 resultsContainer 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 resultscontainer 
 . 
 Builder 
 () 
  
 . 
 setSearchApplicationId 
 ( 
 searchApplicationName 
 ) 
  
 . 
 setSearchResultsContainerElement 
 ( 
 document 
 . 
 getElementById 
 ( 
 'search_results' 
 )) 
  
 . 
 setFacetResultsContainerElement 
 ( 
 document 
 . 
 getElementById 
 ( 
 'facet_results' 
 )) 
  
 . 
 build 
 (); 
  
 // Build the search box and bind to DOM elements. 
  
 var 
  
 searchBox 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 searchbox 
 . 
 Builder 
 () 
  
 . 
 setSearchApplicationId 
 ( 
 searchApplicationName 
 ) 
  
 . 
 setInput 
 ( 
 document 
 . 
 getElementById 
 ( 
 'search_input' 
 )) 
  
 . 
 setAnchor 
 ( 
 document 
 . 
 getElementById 
 ( 
 'suggestions_anchor' 
 )) 
  
 . 
 setResultsContainer 
 ( 
 resultsContainer 
 ) 
  
 . 
 build 
 (); 
  
 }). 
 then 
 ( 
 function 
 () 
  
 { 
  
 // Init API/oauth client w/client ID. 
  
 return 
  
 gapi 
 . 
 auth2 
 . 
 init 
 ({ 
  
 'clientId' 
 : 
  
 clientId 
 , 
  
 'scope' 
 : 
  
 'https://www.googleapis.com/auth/cloud_search.query' 
  
 }); 
  
 }); 
 } 

The above example references two variables for configuration defined as:

serving/widget/public/with_css/app.js
 /** 
 * Client ID from OAuth credentials. 
 */ 
 var 
  
 clientId 
  
 = 
  
 "...apps.googleusercontent.com" 
 ; 
 /** 
 * Full resource name of the search application, such as 
 * "searchapplications/<your-id>". 
 */ 
 var 
  
 searchApplicationName 
  
 = 
  
 "searchapplications/..." 
 ; 

By default, the widget prompts users to sign-in and authorize the app at the time they begin typing a query. You can use Google Sign-in for Websites to offer a more tailored sign-in experience for users.

Authorize users directly

Use Sign In With Google to monitor the sign-in state of the user and sign-in or sign-out users as needed. For example, the following example observes the isSignedIn state to monitor sign-in changes and uses the GoogleAuth.signIn() method to initiate the sign-in from a button click:

serving/widget/public/with_signin/app.js
 // Handle sign-in/sign-out. 
 let 
  
 auth 
  
 = 
  
 gapi 
 . 
 auth2 
 . 
 getAuthInstance 
 (); 
 // Watch for sign in status changes to update the UI appropriately. 
 let 
  
 onSignInChanged 
  
 = 
  
 ( 
 isSignedIn 
 ) 
  
 = 
>  
 { 
  
 // Update UI to switch between signed in/out states 
  
 // ... 
 } 
 auth 
 . 
 isSignedIn 
 . 
 listen 
 ( 
 onSignInChanged 
 ); 
 onSignInChanged 
 ( 
 auth 
 . 
 isSignedIn 
 . 
 get 
 ()); 
  
 // Trigger with current status. 
 // Connect sign-in/sign-out buttons. 
 document 
 . 
 getElementById 
 ( 
 "sign-in" 
 ). 
 onclick 
  
 = 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 auth 
 . 
 signIn 
 (); 
 }; 
 document 
 . 
 getElementById 
 ( 
 "sign-out" 
 ). 
 onclick 
  
 = 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 auth 
 . 
 signOut 
 (); 
 }; 

For additional details, see Sign-In with Google .

You can further streamline the sign-in experience by pre-authorizing the application on behalf of users in your organization. This technique is also useful if using Cloud Identity Aware Proxy to guard the application.

For additional information, see Use Google Sign-In with IT Apps .

Customize the interface

You can change the appearance of the search interface through a combination of techniques:

  • Override the styles with CSS
  • Decorate the elements with an adapter
  • Create custom elements with an adapter

Override the styles with CSS

The search widget comes with its own CSS to style suggestion and result elements as well as the pagination controls. You can restyle these elements as needed.

During load, the search widget dynamically loads its default stylesheet. This occurs after application stylesheets are loaded, raising the priority of the rules. To ensure your own styles take precedence over the default styles, use ancestor selectors to increase the specificity of the default rules.

For example, the following rule has no effect if loaded in a static link or style tag in the document.

  . 
 cloudsearch_suggestion_container 
  
 { 
  
 font-size 
 : 
  
 14 
 px 
 ; 
 } 
 

Instead, qualify the rule with the ID or class of the ancestor container declared in the page.

  # 
 suggestions_anchor 
  
 . 
 cloudsearch_suggestion_container 
  
 { 
  
 font-size 
 : 
  
 14 
 px 
 ; 
 } 
 

For a list of support classes and example HTML produced by the widget, see the Supported CSS classes reference.

Decorate the elements with an adapter

To decorate an element prior to to rendering, create and reigster an adapter that implements one of the decorating methods such as decorateSuggestionElement or decorateSearchResultElement.

For example, the following adapters add a custom class to the suggestion and result elements.

serving/widget/public/with_decorated_element/app.js
 /** 
 * Search box adapter that decorates suggestion elements by 
 * adding a custom CSS class. 
 */ 
 function 
  
 SearchBoxAdapter 
 () 
  
 {} 
 SearchBoxAdapter 
 . 
 prototype 
 . 
 decorateSuggestionElement 
  
 = 
  
 function 
 ( 
 element 
 ) 
  
 { 
  
 element 
 . 
 classList 
 . 
 add 
 ( 
 'my-suggestion' 
 ); 
 } 
 /** 
 * Results container adapter that decorates suggestion elements by 
 * adding a custom CSS class. 
 */ 
 function 
  
 ResultsContainerAdapter 
 () 
  
 {} 
 ResultsContainerAdapter 
 . 
 prototype 
 . 
 decorateSearchResultElement 
  
 = 
  
 function 
 ( 
 element 
 ) 
  
 { 
  
 element 
 . 
 classList 
 . 
 add 
 ( 
 'my-result' 
 ); 
 } 

To register the adapter when initializing the widget, use the setAdapter() method of the respective Builder class:

serving/widget/public/with_decorated_element/app.js
 // Build the result container and bind to DOM elements. 
 var 
  
 resultsContainer 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 resultscontainer 
 . 
 Builder 
 () 
  
 . 
 setAdapter 
 ( 
 new 
  
 ResultsContainerAdapter 
 ()) 
  
 // ... 
  
 . 
 build 
 (); 
 // Build the search box and bind to DOM elements. 
 var 
  
 searchBox 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 searchbox 
 . 
 Builder 
 () 
  
 . 
 setAdapter 
 ( 
 new 
  
 SearchBoxAdapter 
 ()) 
  
 // ... 
  
 . 
 build 
 (); 

Decorators may modify the attributes of the container element as well as any child elements. Child elements may be added or removed during decoration. However, if making structural changes to the elements, consider creating the elements directly instead of decorating.

Create custom elements with an adapter

To create a custom element for a suggestion, facet container, or search result, create and register an adapter that implements createSuggestionElement , createFacetResultElement , or createSearchResultElement repsectively.

The following adapters illustrate creating custom suggestion and search results elements using HTML <template> tags.

serving/widget/public/with_custom_element/app.js
 /** 
 * Search box adapter that overrides creation of suggestion elements. 
 */ 
 function 
  
 SearchBoxAdapter 
 () 
  
 {} 
 SearchBoxAdapter 
 . 
 prototype 
 . 
 createSuggestionElement 
  
 = 
  
 function 
 ( 
 suggestion 
 ) 
  
 { 
  
 let 
  
 template 
  
 = 
  
 document 
 . 
 querySelector 
 ( 
 '#suggestion_template' 
 ); 
  
 let 
  
 fragment 
  
 = 
  
 document 
 . 
 importNode 
 ( 
 template 
 . 
 content 
 , 
  
 true 
 ); 
  
 fragment 
 . 
 querySelector 
 ( 
 '.suggested_query' 
 ). 
 textContent 
  
 = 
  
 suggestion 
 . 
 suggestedQuery 
 ; 
  
 return 
  
 fragment 
 . 
 firstElementChild 
 ; 
 } 
 /** 
 * Results container adapter that overrides creation of result elements. 
 */ 
 function 
  
 ResultsContainerAdapter 
 () 
  
 {} 
 ResultsContainerAdapter 
 . 
 prototype 
 . 
 createSearchResultElement 
  
 = 
  
 function 
 ( 
 result 
 ) 
  
 { 
  
 let 
  
 template 
  
 = 
  
 document 
 . 
 querySelector 
 ( 
 '#result_template' 
 ); 
  
 let 
  
 fragment 
  
 = 
  
 document 
 . 
 importNode 
 ( 
 template 
 . 
 content 
 , 
  
 true 
 ); 
  
 fragment 
 . 
 querySelector 
 ( 
 '.title' 
 ). 
 textContent 
  
 = 
  
 result 
 . 
 title 
 ; 
  
 fragment 
 . 
 querySelector 
 ( 
 '.title' 
 ). 
 href 
  
 = 
  
 result 
 . 
 url 
 ; 
  
 let 
  
 snippetText 
  
 = 
  
 result 
 . 
 snippet 
  
 != 
  
 null 
  
 ? 
  
 result 
 . 
 snippet 
 . 
 snippet 
  
 : 
  
 '' 
 ; 
  
 fragment 
 . 
 querySelector 
 ( 
 '.query_snippet' 
 ). 
 innerHTML 
  
 = 
  
 snippetText 
 ; 
  
 return 
  
 fragment 
 . 
 firstElementChild 
 ; 
 } 

To regsiter the adapter when initializing the widget, use the setAdapter() method of the respective Builder class:

serving/widget/public/with_custom_element/app.js
 // Build the result container and bind to DOM elements. 
 var 
  
 resultsContainer 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 resultscontainer 
 . 
 Builder 
 () 
  
 . 
 setAdapter 
 ( 
 new 
  
 ResultsContainerAdapter 
 ()) 
  
 // ... 
  
 . 
 build 
 (); 
 // Build the search box and bind to DOM elements. 
 var 
  
 searchBox 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 searchbox 
 . 
 Builder 
 () 
  
 . 
 setAdapter 
 ( 
 new 
  
 SearchBoxAdapter 
 ()) 
  
 // ... 
  
 . 
 build 
 (); 

Creating custom facet elements with createFacetResultElement is subject to several restrictions:

  • You must attach the CSS class cloudsearch_facet_bucket_clickable to the element users click on to toggle a bucket.
  • You must wrap each bucket in a containing element with the CSS class cloudsearch_facet_bucket_container .
  • You can't render the buckets in a different order than they appear in the response.

For example, the following snippet renders facets using links instead of check boxes.

serving/widget/public/with_custom_facet/app.js
 /** 
 * Results container adapter that intercepts requests to dynamically 
 * change which sources are enabled based on user selection. 
 */ 
 function 
  
 ResultsContainerAdapter 
 () 
  
 { 
  
 this 
 . 
 selectedSource 
  
 = 
  
 null 
 ; 
 } 
 ResultsContainerAdapter 
 . 
 prototype 
 . 
 createFacetResultElement 
  
 = 
  
 function 
 ( 
 result 
 ) 
  
 { 
  
 // container for the facet 
  
 var 
  
 container 
  
 = 
  
 document 
 . 
 createElement 
 ( 
 'div' 
 ); 
  
 // Add a label describing the facet (operator/property) 
  
 var 
  
 label 
  
 = 
  
 document 
 . 
 createElement 
 ( 
 'div' 
 ) 
  
 label 
 . 
 classList 
 . 
 add 
 ( 
 'facet_label' 
 ); 
  
 label 
 . 
 textContent 
  
 = 
  
 result 
 . 
 operatorName 
 ; 
  
 container 
 . 
 appendChild 
 ( 
 label 
 ); 
  
 // Add each bucket 
  
 for 
 ( 
 var 
  
 i 
  
 in 
  
 result 
 . 
 buckets 
 ) 
  
 { 
  
 var 
  
 bucket 
  
 = 
  
 document 
 . 
 createElement 
 ( 
 'div' 
 ); 
  
 bucket 
 . 
 classList 
 . 
 add 
 ( 
 'cloudsearch_facet_bucket_container' 
 ); 
  
 // Extract & render value from structured value 
  
 // Note: implementation of renderValue() not shown 
  
 var 
  
 bucketValue 
  
 = 
  
 this 
 . 
 renderValue 
 ( 
 result 
 . 
 buckets 
 [ 
 i 
 ]. 
 value 
 ) 
  
 var 
  
 link 
  
 = 
  
 document 
 . 
 createElement 
 ( 
 'a' 
 ); 
  
 link 
 . 
 classList 
 . 
 add 
 ( 
 'cloudsearch_facet_bucket_clickable' 
 ); 
  
 link 
 . 
 textContent 
  
 = 
  
 bucketValue 
 ; 
  
 bucket 
 . 
 appendChild 
 ( 
 link 
 ); 
  
 container 
 . 
 appendChild 
 ( 
 bucket 
 ); 
  
 } 
  
 return 
  
 container 
 ; 
 } 
 // Renders a value for user display 
 ResultsContainerAdapter 
 . 
 prototype 
 . 
 renderValue 
  
 = 
  
 function 
 ( 
 value 
 ) 
  
 { 
  
 // ... 
 } 

Customize search behavior

Search applicationsettings represent the default configuration for a search interface and are static. To implement dynamic filters or facets, such as allowing users to toggle data sources, you can override the search application settings by intercepting the search request with an adapter.

Implement an adapter with the interceptSearchRequest method to modify requests made to the search API prior to execution.

For example, the following adapter intercepts requests to restrict queries to a user-selected source:

serving/widget/public/with_request_interceptor/app.js
 /** 
 * Results container adapter that intercepts requests to dynamically 
 * change which sources are enabled based on user selection. 
 */ 
 function 
  
 ResultsContainerAdapter 
 () 
  
 { 
  
 this 
 . 
 selectedSource 
  
 = 
  
 null 
 ; 
 } 
 ResultsContainerAdapter 
 . 
 prototype 
 . 
 interceptSearchRequest 
  
 = 
  
 function 
 ( 
 request 
 ) 
  
 { 
  
 if 
  
 ( 
 ! 
 this 
 . 
 selectedSource 
  
 || 
  
 this 
 . 
 selectedSource 
  
 == 
  
 'ALL' 
 ) 
  
 { 
  
 // Everything selected, fall back to sources defined in the search 
  
 // application. 
  
 request 
 . 
 dataSourceRestrictions 
  
 = 
  
 null 
 ; 
  
 } 
  
 else 
  
 { 
  
 // Restrict to a single selected source. 
  
 request 
 . 
 dataSourceRestrictions 
  
 = 
  
 [ 
  
 { 
  
 source 
 : 
  
 { 
  
 predefinedSource 
 : 
  
 this 
 . 
 selectedSource 
  
 } 
  
 } 
  
 ]; 
  
 } 
  
 return 
  
 request 
 ; 
 } 

To register the adapter when initializing the widget, use the setAdapter() method when building the ResultsContainer

serving/widget/public/with_request_interceptor/app.js
 var 
  
 resultsContainerAdapter 
  
 = 
  
 new 
  
 ResultsContainerAdapter 
 (); 
 // Build the result container and bind to DOM elements. 
 var 
  
 resultsContainer 
  
 = 
  
 new 
  
 gapi 
 . 
 cloudsearch 
 . 
 widget 
 . 
 resultscontainer 
 . 
 Builder 
 () 
  
 . 
 setAdapter 
 ( 
 resultsContainerAdapter 
 ) 
  
 // ... 
  
 . 
 build 
 (); 

The following HTML is used to display a select box for filtering by sources:

serving/widget/public/with_request_interceptor/index.html
<div>
  <span>Source</span>
  <select id="sources">
    <option value="ALL">All</option>
    <option value="GOOGLE_GMAIL">Gmail</option>
    <option value="GOOGLE_DRIVE">Drive</option>
    <option value="GOOGLE_SITES">Sites</option>
    <option value="GOOGLE_GROUPS">Groups</option>
    <option value="GOOGLE_CALENDAR">Calendar</option>
    <option value="GOOGLE_KEEP">Keep</option>
  </select>
</div>

The following code listens for the change, sets the selection, and reexecutes the query if necessary.

serving/widget/public/with_request_interceptor/app.js
 // Handle source selection 
 document 
 . 
 getElementById 
 ( 
 'sources' 
 ). 
 onchange 
  
 = 
  
 ( 
 e 
 ) 
  
 = 
>  
 { 
  
 resultsContainerAdapter 
 . 
 selectedSource 
  
 = 
  
 e 
 . 
 target 
 . 
 value 
 ; 
  
 let 
  
 request 
  
 = 
  
 resultsContainer 
 . 
 getCurrentRequest 
 (); 
  
 if 
  
 ( 
 request 
 . 
 query 
 ) 
  
 { 
  
 // Re-execute if there's a valid query. The source selection 
  
 // will be applied in the interceptor. 
  
 resultsContainer 
 . 
 resetState 
 (); 
  
 resultsContainer 
 . 
 executeRequest 
 ( 
 request 
 ); 
  
 } 
 } 

You can also intercept the search response by implementing interceptSearchResponse in the adapter.

Pin the API version

By default the widget uses the latest stable version of the API. To lock in a specific version, set the cloudsearch.config/apiVersion configuration parameter to the preferred version prior to initializing the widget.

serving/widget/public/basic/app.js
 gapi 
 . 
 config 
 . 
 update 
 ( 
 'cloudsearch.config/apiVersion' 
 , 
  
 'v1' 
 ); 

The API version will default to 1.0 if unset or set to an invalid value.

Pin the widget version

To avoid unexpected changes to search interfaces, set the cloudsearch.config/clientVersion configuration parameter as shown:

  gapi 
 . 
 config 
 . 
 update 
 ( 
 'cloudsearch.config/clientVersion' 
 , 
  
 1.1 
 ); 
 

The widget version will default to 1.0 if unset or set to an invalid value.

Secure the search interface

Search results contain highly sensitive information. Follow best practices for securing web applications, particularly against clickjacking attacks.

For more information, see OWASP Guide Project

Enable debugging

Use interceptSearchRequest to turn on debugging for the search widget. For example:

   
 if 
  
 (! 
 request 
 . 
 requestOptions 
 ) 
  
 { 
  
 // 
  
 Make 
  
 sure 
  
 requestOptions 
  
 is 
  
 populated 
  
 request.requestOptions 
  
 = 
  
 { 
 } 
 ; 
  
 } 
  
 // 
  
 Enable 
  
 debugging 
  
 request 
 . 
 requestOptions 
 . 
 debugOptions 
  
 = 
  
 { 
 enableDebugging 
 : 
  
 true 
 } 
  
 return 
  
 request 
 ; 
 
Create a Mobile Website
View Site in Mobile | Classic
Share by: