Custom Search

Data Rendering

About

This document describes how to customize the display of search results delivered via the Custom Search Engine. This documentation is designed for Custom Search Engine developers who are familiar with JavaScript programming and object-oriented programming concepts.

Table of Contents

Introduction

In addition to using themes to customize the look and feel of your Custom Search Engine, you can write HTML to change the rendering of result elements. The specification that allows you to customize results is a minimal form of the template languages used by OpenSocial and jstemplates.

By default, the Custom Search Engine renders search results with a CSS base class of webResult (for example, gs-webResult). In order to customize how results render, you need to override the defaults by calling addOverride before the draw call in the onLoad function that initiates your Custom Search Engine, and insert a new <div> structure into the body of your document. This new structure customizes search results using the result properties and custom data attributes defined in this guide.

The following sample uses a Custom Search Engine to display results and metadata into results for Scribd. (View the live sample)

By  -  pages -  views  - last modified 

The source code for this sample is as follows:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>Custom Search Engine</title>

  <script src="https://www.google.com/jsapi" type="text/javascript"></script>
  <script type="text/javascript">
    // Load the Search API
    google.load('search', '1');

    // Set a callback to load the Custom Search Element when you page loads
    google.setOnLoadCallback(
      function(){
        var customSearchControl = new google.search.CustomSearchControl('000458029060823693019:rufl7axsp9k');

        // Use "mysite_" as a unique ID to override the default rendering.
        google.search.Csedr.addOverride("mysite_");

        // Draw the Custom Search Control in the div named "CSE"
        customSearchControl.draw('cse');

        // Execute an initial search
        customSearchControl.execute("ajax");
      },
    true);
  </script>
</head>
<body>

  <div style="display:none">

    <!-- Create 48px x 48px thumbnails.-->
    <div id="mysite_thumbnail">
      <div data-if="Vars.thumbnail" class="gs-image-box gs-web-image-box">
        <a class="gs-image" data-attr="{href:url, target:target}">
          <img class="gs-image" data-attr="{src:thumbnail.src, width:48, height: 48}"/>
        </a>
      </div>
    </div>

    <!-- Return the unescaped result URL.-->
    <div id="mysite_webResult">
      <div class="gs-webResult gs-result"
        data-vars="{longUrl:function() {
          var i = unescapedUrl.indexOf(visibleUrl);
          return i < 1 ? visibleUrl : unescapedUrl.substring(i);}}">

        <!-- Build the result data structure.-->
        <table>
          <tr>
            <td valign="top">
              <div data-if="Vars.richSnippet" data-attr="0"
                data-body="render('thumbnail',richSnippet,{url:unescapedUrl,target:target})"></div>
            </td>
            <td valign="top">

              <!-- Append results within the table cell.-->
              <div class="gs-title">
                <a class="gs-title" data-attr="{href:unescapedUrl,target:target}"
                  data-body="html(title)"></a>
              </div>
              <div class="gs-snippet" data-body="html(content)"></div>
              <div class="gs-visibleUrl gs-visibleUrl-short" data-body="longUrl()"></div>
              <div style="color:#676767" data-if="Vars.richSnippet && Vars.richSnippet.document">

                <!-- Insert an icon denoting the result file type.-->
                <img data-attr="{src:Vars.richSnippet.document.filetypeImage}">

                <!-- Insert the author name for this Scribd post.-->
                By <span data-body="Vars.richSnippet.document.author"></span>  - 

                <!-- Insert the number of pages in this Scribd result.-->
                <span data-body="Vars.richSnippet.document.pageCount"></span> pages - 
               
                <!-- Insert the number of times this Scribd post has been viewed.-->
                <span data-body="Vars.richSnippet.document.viewCount"></span> views
               
                <!-- Insert the last modified date-->
                 - last modified  <span data-body="Vars.richSnippet.document.timeAgo"></span>
              </div>

              <!-- Render results.-->
              <div data-if="Vars.richSnippet && Vars.richSnippet.action" class="gs-actions"
                data-body="render('action',richSnippet,{url:unescapedUrl,target:target})"></div>

            </td>
          </tr>
        </table>
      </div>
    </div>
  </div>

  <!-- Div container for the searcher.-->
  <div id="cse"></div>
</body>
</html>

Result data structure

The Custom Search Engine returns search results according to the standard webResult CSS structure. Using this CSS structure, you can customize Custom Search Engine results by calling the following result properties in conjunction with custom data attributes.

Common result properties

The following result properties are available by default:

Property Description

title

Supplies the search result title based on the page title. This contains sanitized html, so that it may be safely wrapped in a html() call.

content

Supplies a snippet of content from the search result. Contains sanitized HTML.

target

Provides the target frame for the links, as specified by SearchControl.setLinkTarget

unescapedUrl

Supplies the full, unmodified URL of the result.

visibleUrl

Supplies a shortened version of the URL associated with the result.

Rich snippet result properties

If your site uses rich snippets, you can use any property available in the PageMap.

When Rich Snippet properties render in the JSON data structure, the PageMap attribute name is translated into camelCase (for example, user_count becomes userCount). If you cannot find an attribute name, you can use JSON.stringify to diagnose the issue as follows:

<span data-body="JSON.stringify(richSnippet)"></span>

If your data structure calls a Rich Snippet object that does not exist, the searcher returns an error. If you are uncertain whether a Rich Snippet exists, you can use the data-if attribute in which the if statement defines the ambiguous attribute as follows:

<span id="richsnippet_display" data-if="typeof richSnippet != 'undefined'">
        contents go here</span>

The following table summarizes the most common Rich Snippet properties:

Rich Snippet Property

Description

richSnippet.thumbnail.src

Source URL of the thumbnail image.

richSnippet.thumbnail.width

Width of the thumbnail image.

richSnippet.thumbnail.height

Height of the thumbnail image.

richSnippet.action.url

Destination URL when the action is clicked.

If multiple actions are present in the PageMap, they will be stored in an array and you can access them using action[0].url, action[1].url, etc. The foreach iterator automatically detects arrays, so that data-foreach="action" works with or without an array.

richSnippet.action.label

Text label for the action, eg "Download".

richSnippet.action.class

Class name used for styling the action and providing an icon (for example, download ).  Icons are available for the following class names:

  • cart
  • star
  • rss
  • download
  • email
  • mobile
  • share
  • fullscreen
  • generic
  • link

Inserting result content

Custom result rendering relies on the use of custom data attributes embedded in the custom search engine page. All custom data attributes have a data- prefix in compliance with the HTML5 specification, and the value of each attribute is supplied by a JavaScript expression. This expression can be a simple field name, or a more complex computation or function call. The available custom data attributes are:

Custom Data Attribute Description

data-attr=
"dict"

Sets attributes inside the current element. Its value must be a JavaScript dictionary, e.g., { attr1: value1, attr2: value2 ...}.

Warning: Make sure that attribute values come from trusted sources. The content and title properties are safe for inclusion as HTML, but not necessarily as attributes. For example, onclick:"alert('" + content + "')" can lead to an XSS attack.

data-body=
"string"

Inserts into the body of the current element. If given a string, it ​inserts raw text. You can insert HTML using the html() and render() functions.

  • html(string) converts an HTML string into a document element.

    Warning: Do not insert HTML from sources you do not trust. Inserting HTML from untrusted third parties can lead to XSS attacks.

  • render(node, arguments) builds a document element by passing arguments to the given node (see rendering below).

data-if=
"test
"

Inserts the current tag only if test=true.

data-elif=
"test"

Performs a secondary test only if previous tests fail. Must follow a previous if or elif.

data-foreach=
"array"

Repeats the current tag once for each value in an array. It sets three variables for each iteration:

  1. Index counts from 0
  2. Cur holds the current element
  3. Foreach holds the entire array.

Note: Index, Cur, and Foreach must start with an upper-case letter.

data-vars
="dict"

Sets new variables, which can be used by sub-elements. The special variable Vars holds the current variables. For example, you can access the variable content as Vars.content or Vars['content'].

Example: Using data attributes to insert content

The following table provides examples of how to use some data attributes to insert content:

This Code Renders as
<a data-attr=
"{href:document.location}">...</a>
<a href="http://...">...</a>
<span data-body=
"html('<b>Bold</b>')">Ignored</span>
<span><b>Bold</b></span>
<div data-body="'Label:' + richSnippet.action.label"
data-attr="0"></div>

Label: My action

Adding data-attr="0" renders the body without the containing tag and its attributes. By default, data-body adds children to the current element, replacing any existing children, and also renders the current tag and any provided attributes. This example shows how to drop the enclosing <div> tag from rendered attributes.

Example: Using data attributes for conditionals, iteration, and local variables

The following three examples demonstrate the use of data field conditionals, iteration, and local variables, respectively.

  1. Using nested if conditionals. In this example, elif becomes the else statement for the second if statement because it is nested at the same level inside the containing <div>.
    <div data-if="shouldShowTruncation(Vars)">
      <span data-if="content.length > 20">...etc..</span>
      <span data-elif="Vars.content.length == 0">[No content]</span>
    </div>
  2. Using iteration. This example uses the foreach attribute to create the three divs containing the text "red", "green", and "blue" respectively.
    <div data-foreach="['red', 'green', 'blue']" data-body="Cur"></div>
  3. Using local variables. This example uses the data-vars attribute to create a local variable "four" such that the contained <div> contains the number 4.
    <div data-vars="{four:2+2}">
      <div data-body="four"></div>
    </div>

Rendering

The render(node, arguments) function builds a new document element by copying the given node and expanding the attributes it finds. The node may either be a DOM node or an ID. The arguments must be a dictionary of values (for example, the entire Vars variable). The following example shows how to render the result of a simple expression using conditionals:

<div id="showSum">
  <div data-if="Vars.sum" data-body="'The sum is ' + sum"></div>
  <div data-elif="1">Sum was zero, or not given</div>
</div>
<div data-body="render('showSum', {sum: 2 + 2})"></div>

The render call can also take any number of additional arguments, which will augment or override the first arguments. A useful idiom is render(node, Vars, {oneMore:value}), which passes all the current variables, and adds oneMore.

Note: When rendering errors occur, users are shown a JavaScript error instead of rendered HTML results.

A common error is accessing an undefined variable. To avoid such errors, use Vars.xyz to access xyz. This evaluates to undefined without showing an error.

Overriding the default rendering

The default code to render web results is:

<!-- Code for the default result layouts

// Children of the referenced node are copied recursively.
// These keywords are checked, in this order:
//   data-if/elif=  boolean-expr     inserts node conditionally
//   data-foreach=  array-expr       inserts node repeatedly
//   data-body=     string/dom-expr  sets node body
//   data-attr=     dict-expr        sets node attributes
//   data-attr=     false-expr       suppresses node tag: inserts children only
//   data-vars=     dict-expr        sets local variables
//
// Expressions:
//   The variable Vars holds all available variables: Vars.x == x == Vars['x']
//   Within a foreach, we have three state variables: Cur == Foreach[Index]
//   html(string) builds dom content from the given html string, for data-body
//   render(domId, dict, ..) builds dom content for data-body. Multiple dicts ok

-->

<div style="display:none">

  <div id="base_webResult">
    <div class="gs-webResult gs-result"
      data-vars="{longUrl:function() {
        var i = unescapedUrl.indexOf(visibleUrl);
        return i < 1 ? visibleUrl : unescapedUrl.substring(i);}}">
      <div data-if="Vars.richSnippet" data-attr="0"
        data-body="render('thumbnail',richSnippet,{url:unescapedUrl,target:target})"></div>
      <div class="gs-title">
        <a class="gs-title" data-attr="{href:unescapedUrl,target:target}"
          data-body="html(title)"></a>
      </div>
      <div class="gs-snippet" data-body="html(content)"></div>

      <div class="gs-visibleUrl gs-visibleUrl-short" data-body="visibleUrl"></div>
      <div class="gs-visibleUrl gs-visibleUrl-long" data-body="longUrl()"></div>
      <div data-if="Vars.richSnippet && Vars.richSnippet.action" class="gs-actions"
        data-body="render('action',richSnippet,{url:unescapedUrl,target:target})"></div>
    </div>
  </div>

  <div id="base_thumbnail">
    <div data-attr="0" data-vars="{tn:Vars.thumbnail && thumbnail.src
        ? thumbnail : {src:Vars.document && document.thumbnailUrl}}">
      <div data-if="tn.src" class="gs-image-box gs-web-image-box">
        <a class="gs-image" data-attr="{href:url,target:target}">
          <img class="gs-image" src=""
            data-attr="{src:tn.src, width:tn.width || 100, height: tn.height}"/>
        </a>
      </div>
    </div>
  </div>

  <div id="base_action">
    <div data-foreach="Vars.action" data-attr="0">
      <div data-attr="{'class': 'gs-action ' + Cur['class']}"
          data-if="Cur.url && Cur.label">
        <a class="gs-action" data-attr="{href:Cur.url,target:target}"
          data-body="Cur.label"></a>
      </div>
    </div>
  </div>
</div>

The webResult element generates a complete result structure, but you can replace any element with your own code. Your IDs must be labeled with a unique prefix, such as mysite_, which you pass to addOverride. The default list of overrides is ["base_", ""], and addOverride adds to the start of this list.

Here is a simple example of overriding, showing the standalone use of render (without a search engine):

<div id="old_test">Old version</div>
<div id="new_test">New version calls old as follows:
  <div data-body="render('old_test')"></div>
</div>
<div id="testMe" data-body="render('test')"></div>
<div id="target"></div>
<script>
  google.search.Csedr.addOverride('new_');
  var nodes = google.search.Csedr.render('testMe');
  document.getElementById('target').appendChild(nodes);
</script>

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.