Tim Dietrich

Custom Software Developer

Home Services Portfolio Blog About Contact Newsletter

Apple TV: Implementing Search With TVML, TVJS, and searchTemplate

I'm in the process of developing several Apple TV apps at the moment. I'm developing some of them as custom apps with Swift. For the others, I'm using TVML and TVJS.

A couple of the TVML / TVJS apps need to provide users with a search function. So I spent some time experimenting with the search template that TVML supports. It took some effort, but I eventually managed to get search fully implemented, and it turned out rather nicely.

Here's a GIF that shows the search function in action.

Included below are details on how I implemented the search function. There are four components to it:

• The "Search.xml.js" file itself, which is really just a barebones "searchTemplate" template.
• A link to that file, indicating that the document should be presented using the "searchPresenter" presentation.
• The "searchPresenter" presentation function, which is implemented in the "Presenter.js" file.
• And the search handler itself, which is most likely going to be hosted.

Step 1: Prepare Your searchTemplate.

Create a file to initialize a (mostly empty) searchTemplate. In this example, I've named that file "Search.xml.js," and it looks like this.

 var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>  
  <document>  
   <searchTemplate>  
    <searchField>Search</searchField>  
    <collectionList>  
     <shelf />  
    </collectionList>  
   </searchTemplate>  
  </document>`  
 }  

Notice that the document's collectionList element contains an empty shelf element. There's no need to present an initial shelf when the search function loads.

Step 2. Link to the searchTemplate.

Create a link to the search function, and set the presentation type to "searchPresenter." For example, in this example, I'm linking to the search function from the menubar of my main template. The menu item looks like this.

 <menuItem template="${this.BASEURL}Search.xml.js" presentation="searchPresenter">  
      <title>Search</title>  
 </menuItem>  

Step 3. Add searchPresenter presentation support.

In your "Presenter.js" file, add support for the "searchPresenter" presentation. Here's my implementation.

 searchPresenter: function(xml) {  
      this.defaultPresenter.call(this, xml);  
      var doc = xml;  
      // Set a reference to the search field.  
      var searchField = doc.getElementsByTagName("searchField").item(0);  
      // Set a reference to the searchField's keyboard feature.  
      var keyboard = searchField.getFeature("Keyboard");  
      // Register an onTextChange event for the keyboard.  
      keyboard.onTextChange = function() {  
           // Get the text that has been entered.  
           var searchText = keyboard.text;  
           // Encode the text.  
           searchText = encodeURIComponent(searchText);        
           // If more 4+ characters have been entered...  
           if ( keyboard.text.length >= 4 ) {  
                // Create an XMLHttpRequest.  
                var xhttp;  
                xhttp = new XMLHttpRequest();                 
                // Add a "load" event listener.  
                xhttp.addEventListener("load", function() {   
                     // And the dynamically generated shelf to the collection list.  
                     doc.getElementsByTagName("collectionList").item(0).innerHTML = xhttp.responseText;  
                 }, false);  
                // Sets the request's method, URL, and synchronous flag.  
                xhttp.open("GET", "http://yourserver.com/SearchAction.php?q=" + searchText, true);  
                // Send the request.  
                xhttp.send();       
           } else {                                     
                // Replace any existing shelf with an empty shelf.  
                doc.getElementsByTagName("collectionList").item(0).innerHTML = `&lt;shelf /&gt;`;  
           }                 
      }  
 },  

There are a few things worth noting about the searchPresenter function:

• It only sends a search request to the server if the user has entered at least 4 characters into the search field. You can adjust this if you'd like, and you probably should depending on the nature of your app.
• It takes the text that the user has entered and passes it an encoded version of it as a URL parameter.
• The entire response that is received from the XMLHttpRequest is loaded into the collectionList tag. As you'll see in a moment, the search handler returns a complete and well-formed "shelf" as a response. This makes implementing the searchPresenter function significantly easier. There's no need to do any additional processing here, such as deserializing data in the response. It's just XML, so we can place it directly into the document.
• Also note that if the user initially enters 4 or more characters into the search field, and then uses the backspace button to remove some of the characters, that the app will respond accordingly (by replacing the shelf with an empty one).

Step 4: Develop the server-based search handler.

The final step is to develop server-side code to handle the search requests. How you do that will, of course, depend on your backend and what you're working with. In this example, the data is in an Amazon Aurora database, and the search is handled by a PHP-based web application along with a "Luna" (a PHP-based API framework that I've developed). It really doesn't matter what your backend is.

What does matter is what your search handler returns. With this technique, the searchPresenter function is expecting that the XMLHttpRequests that it sends will return a well-formed "shelf" element - regardless of whether the search was successful or not.

If the search is successful, then the response might look like this.

 <shelf>  
      <header>  
           <title>Results</title>  
      </header>  
      <section>  
           <lockup>  
                <img src="http://yourserver.com/content/sample-ring-2-small.jpg" width="308" height="308" />  
                <title class="whiteText">14kt Two Tone Gold Ring</title>  
           </lockup>                      
           <lockup>  
                <img src="http://yourserver.com/content/sample-ring-4-small.jpg" width="308" height="308" />  
                <title class="whiteText">14kt White Gold Ring</title>  
           </lockup>       
           <lockup>  
                <img src="http://yourserver.com/content/sample-ring-5-small.jpg" width="308" height="308" />  
                <title class="whiteText">14kt White Gold Ring</title>  
           </lockup>       
      </section>  
 </shelf>  

If the search fails, then the response should be an empty shelf. For example:

 <shelf />  

This is a simple, clean approach to implementing search in a TVML / TVJS-based Apple TV app. If you have any questions about it, please feel free to reach out to me.