PageRenderTime 14ms CodeModel.GetById 3ms app.highlight 62ms RepoModel.GetById 2ms app.codeStats 0ms

/documentation/spice2.md

https://github.com/a-West/duckduckgo
Markdown | 1298 lines | 1004 code | 294 blank | 0 comment | 0 complexity | 649cd33da580e9d07a382e0911f54210 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1#Spice Frontend
  2
  3--------
  4
  5#Index
  6- [Overview](#overview)
  7    - [Tech](#tech)
  8- [Example #1: NPM (Basic Instant Answer)](#example-1---npm-basic-instant-answer)
  9- [Example #2: Alternative.To (Basic Carousel Instant Answer)](#example-2---alternativeto-basic-carousel-instant-answer)
 10- [Example #3: Movie (Advanced Instant Answer)](#example-3---movie-advanced-instant-answer)
 11- [Example #4: Quixey (Advanced Carousel Instant Answer)](#example-4---quixey-advanced-carousel-instant-answer)
 12- [Example #5: Dictionary (More Advanced Instant Answer)](#example-5---dictionary-more-advanced-instant-answer)
 13- [Advanced Techniques](#advanced-techniques)
 14    - [Slurping Multiple Trigger Words](#slurping-multiple-trigger-words)
 15    - [Using API Keys](#using-api-keys)
 16    - [Using the GEO Location API](#using-the-geo-location-api)
 17    - [Common Code for Spice Endpoints (.pm's)](#common-code-for-spice-endpoints-pms)
 18    - [Common JavaScript and Handlebars Templates](#common-javascript-and-handlebars-templates)
 19    - [Using Custom CSS](#using-custom-css)
 20    - [Using images](#using-images)
 21- [Common Pitfalls](#common-pitfalls)
 22    - [Defining Perl Variables and Functions](#defining-perl-variables-and-functions)
 23- [StyleGuide](#styleguide)
 24    - [Formatting](#formatting)
 25    - [Naming Conventions](#naming-conventions)
 26    - [Do's & Don'ts](#dos--donts)
 27- [FAQ](#faq)
 28- [DDG Methods (JavaScript)](#ddg-methods-javascript)
 29- [Spice Helpers (Handlebars)](#spice-helpers-handlebars)
 30- [Spice Attributes (Perl)](#spice-attributes-perl)
 31- [Spice Helper Functions (Perl)](#spice-helper-functions-perl)
 32
 33
 34-------
 35
 36
 37##Overview
 38The Spice frontend is the code that is triggered by the Perl backend (which we learned about in the previous tutorial) for your spice instant answer. It mainly consists of a function (the Spice "callback" function) that takes a JSON formatted, API response as its input, specifies which template format you'd like your result to have and uses the data to render a Spice result at the top of the DuckDuckGo search results page.
 39
 40The Perl part of the instant answers go in lib directory: `lib/DDG/Spice/instantAnswerName.pm`, while all of the frontend files discussed below should go in the share directory: `share/spice/instant_answer_name/`.
 41
 42**\*\*Note** : The file and folder names must adhere to our [naming conventions](#naming-conventions) in order for everything to function properly.
 43
 44###Tech
 45The Spice frontend uses [Handlebars](http://handlebarsjs.com) for templates and includes [jQuery](https://jquery.org) (although it's use is not required). It also allows the use of custom CSS when required.
 46
 47If you're not already familiar with Handlebars, *please* read the [Handlebars documentation](http://handlebarsjs.com) before continuing on. Don't worry if you don't fully understand how to use Handlebars; the examples will explain it to you. But you should, at the very least, familiarize yourself with Handlebars concepts and terminology before moving on. (Don't worry, it should only take a few minutes to read!)
 48
 49Below, we will walk you through several examples ranging from simple to complicated, which will explain how to use the template system and make your instant answers look awesome.
 50
 51
 52------------------------
 53
 54
 55##Example #1 - NPM (Basic Instant Answer)
 56
 57The NPM instant answer [[link](https://duckduckgo.com/?q=npm+uglify-js)] [[code](https://github.com/duckduckgo/zeroclickinfo-spice/tree/master/share/spice/npm)] is a great example of a basic Spice implementation. Let's walk through it line-by-line:
 58
 59#####npm.js
 60```javascript
 61function ddg_spice_npm (api_result) {
 62    if (api_result.error) return
 63
 64    Spice.render({
 65         data              : api_result,
 66         force_big_header  : true,
 67         header1           : api_result.name + ' (' + api_result.version + ')',
 68         source_name       : "npmjs.org", // More at ...
 69         source_url        : 'http://npmjs.org/package/' + api_result.name,
 70         template_normal   : 'npm',
 71         template_small    : 'npm'
 72    });
 73}
 74```
 75
 76As mentioned, every instant answer requires a Spice callback function, for the *NPM* instant answer, the callback is the `ddg_spice_npm()` function that we defined here in *npm.js*. The *NPM* Perl module we wrote specifies this as the callback by using the name of the package `DDG::Spice::NPM` and gives this *ddg_spice_npm* name to the API call so that this funtion will be executed when the API responds using the data returned from the upstream (API) provider as the function's input.
 77
 78#####npm.js (continued)
 79```javascript 
 80if (api_result.error) return
 81```
 82Pretty self-explanatory - If the error object in the API result is defined, then break out of the function and don't show any results. In the case of this API, when the error object is defined, it means no results are given, so we have no data to use for a Spice result. 
 83
 84#####npm.js (continued)
 85```javascript
 86Spice.render({
 87     data              : api_result,
 88     force_big_header  : true,
 89     header1           : api_result.name + ' (' + api_result.version + ')',
 90     source_name       : "npmjs.org",
 91     source_url        : 'http://npmjs.org/package/' + api_result.name,
 92     template_normal   : 'npm',
 93     template_small    : 'npm'
 94});
 95```
 96
 97Alright, so here is the bulk of the instant answer, but it's very simple:
 98
 99- `Spice.render()` is a function that the instant answer system has already defined. You pass an object to it that specifies a bunch of important parameters. 
100
101- `data` is perhaps the most important parameter. The object given here will be the object that is passed along to the Handlebars template. In this case, the context of the NPM template will be the **api_result** object. This is very important to understand because **only the data passed along to the template is accessible to the template**. In most cases the `data` parameter should be set to 
102`api_result` so all the data returned from the API is accessible to the template. 
103
104- `force_big_header` is related to the display formatting -- it forces the system to display the large grey header that you see when you click the example link above. 
105
106- `header1` is the text of this header, i.e. the text displayed inside the large grey bar. 
107
108- `source_name` is the name of the source for the "More at <source>" link that's displayed below the text of the instant answer for attribution purposes. 
109
110- `source_url` is the target of the "More at" link. It's the page that the user will click through to. 
111
112- `template_normal` is the name of the Handlebars template that contains the structure information for your instant answer.
113
114- `template_small` is the name of the Handlebars template to be used when you instant answer is displayed in a stacked state. This isn't required, but if your instant answer can provide a succint, one or two line answer this template should be used in the event that the instant answer appears in the stacked state. If no template is given the stacked result will simply show the header of the spice result 
115
116----
117
118Now, let's look at the NPM instant answer's Handlebars template:
119
120######npm.handlebars
121```html
122<div>
123    <div>{{{description}}}</div>
124    <pre> $ npm install {{{name}}}</pre>
125</div>
126```
127
128As you can see, this is a special type of HTML template. Within the template, you can refer directly to objects that are returned by the API. `description` and `name` are both from the `api_result` object that we discussed earlier -- the data that's returned by the API. All of `api_result`'s sub-objects (e.g. `name`, `description`) are in the template's scope. You can access them by name using double or triple curly braces (triple braces will not escape the contents). Here, we just create a basic HTML skeleton and fill it in with the proper information.
129
130###Conclusion
131We've created two files in the Spice share directory (`share/spice/npm/`) :
132
1331. `npm.js` - which delegates the API's response and calls `Spice.render()`
1342. `npm.handlebars` - which specifies the instant answer's HTML structure and determines which attributes of the API response are placed in the HTML result
135
136You may notice other instant answers also include a css file. For **NPM** the use of CSS wasn't necessary and this is also true for many other instant answers. If however CSS is needed it can be added. Please refer to the [Spice FAQ](#faq) for more inforamtion about custom css.
137
138##Example #2 - Alternative.To (Basic Carousel Instant Answer)
139The Alternative.To instant answer is very similar to NPM in that it is also relatively basic, however, it uses the **Carousel** Spice Template. Let's take a look at the code and see how this is done:
140
141###### alternative\_to.js
142```javascript
143function ddg_spice_alternative_to(api_result) {
144    if(!api_result || !api_result.Items || api_result.Items.length === 0) {
145        return;
146    }
147
148    Spice.render({
149        data                     : api_result,
150        source_name              : 'AlternativeTo',
151        source_url               : api_result.Url,
152        spice_name               : 'alternative_to',
153        more_icon_offset         : '-2px',
154        template_frame           : "carousel",
155        template_options         : {
156            items                : api_result.Items,
157            template_item        : "alternative_to",
158            template_detail      : "alternative_to_details",
159            li_height            : 65,
160            single_item_handler  : function(obj) {            // gets called in the event of a single result
161                obj.header1 = obj.data.Items[0].Name;         // set the header
162                obj.image_url = obj.data.Items[0].IconUrl;    // set the image
163            }
164        },
165    });
166}
167```
168
169Just like the NPM instant answer, Alternative.To uses `Spice.render()` with most of the same properties, however, it also uses a few new properties as well:
170
171- `template_frame` is used to tell the Render function that the base template for this instant answer will be the **Carousel** template.  
172**\*\*Note**: This is a template which we have already created and you don't have to worry about creating or modifying.*
173
174- `template_options` is a property which is used to specify more properties that are specifc to the current `template_frame`. In this case, we use `template_options` to define more properties for the `carosuel` template.
175
176- `items` is **required** when using the carousel template. It passes along an array or object to be iterated over by the carousel template. Each of these items becomes the context for the `alternative_to.handlebars` template which defines the content of each `<li>` in the carousel.
177
178- `template_item` tell the carousel template which sub-template should be applied to each of the objects in the `items` array. This template defines what the contents of each `<li>` in the carousel will contain. Generally for a `carousel` Instant Answer, each `<li>` should contain an image and title for the current item.
179
180- `template_detail` similarly to `template_frame` this is again another sub-template applied to each of the objects in the `items` array, however this template defines the look/layout of the **detail area**, which opens up below the carousel with more information about the selected item.
181
182- `li_height` is an optional property which defines the height of each of the carousel's `<li>`'s
183
184- `single_item_handler` is a property which defines a function that lets you modify the other properties of `Spice.render()` if and only if a single item is returned from the upstream API. In this case, we use it to  set values for the `header1` and the `image_url`related to the single item returned. If these had not been set, the carousel will still automatically (by default) apply the `template_detail` to the single item returned and display that, rather than a carousel with a single item in it. This can be overridden by setting the `use_alternate_template` property to false, inside the `template_options` property.
185
186- `carousel_template_detail` is an **optional** parameter which specifies the Handlebars template to be used for the Carousel ***detail*** area - the space below the template which appears after clicking a carousel item. For Alternative.To, when a user clicks a carousel item (icon), the detail area appears and provides more information about that particular item. This is similarly used for the [Quixey instant answer](https://duckduckgo.com/?q=ios+flight+tracking+app).
187
188----------------------------
189
190Now, let's take a look at the Alternative.To Handlebars templates:
191
192###### alternative\_to.handlebars
193```html
194<img src="/iu/?u={{IconUrl}}">
195<span>{{{condense Name maxlen="25"}}}</span>
196```
197This simple template is used to define each of the carousel items. More specifically, it defines what the contents of each `<li>` in the carousel will be. In this case we specify an image - the result's icon - and a span tag, which contains the name of the result.
198
199You might notice that we prepend the `<img>`'s `src` url with the string `"/iu/?u="`. This is **required** for any images in your handlebars template. What this line does is proxy the image through our own servers, which ensure the user's privacy (because it forces the request to come from DuckDuckGo instead of the user).
200
201The carousel uses this template by iterating over each item in the object given to `carousel_items` and uses that item as the context of the template.
202
203Another important point is that we use `{{{condense Name maxlen="25"}}}` which demonstrates the usage of a Handlebars helper function. In this case, we are using the `condense` function (defined elsewhere, internally) which takes two parameters: `Name` (from `api_result`), which is the string to be shortened and `maxlen="25"` which specifies the length the string will be shortened to. 
204
205Seeing as this is a carousel instant answer, which uses the optional carousel details area, it has another Handlebars template which defines the content for that.  Let's have a look at the Alternative.To details template:
206
207###### alternative\_to\_details.handlebars
208```html
209{{#rt}} <a href="{{Url}}">{{Name}}</a> <span class="likes">({{Votes}} likes)</span>{{/rt}}
210{{#rd "Description"}} {{{ShortDescription}}}{{/rd}}
211{{#rd "Platforms"}} {{#concat Platforms sep=", " conj=" and "}}{{this}}{{/concat}}{{/rd}}
212```
213    
214This template is also relatively simple and it utilizes more of our Handlebars helpers to create a few elements and populates them with relevant information related to the carousel item that was clicked.
215
216In this case we're using the `{{#rt}}` and `{{#rd}}` Handlebars block helpers, which are perfect for this kind of data, which represent a "record" where each piece of data has a name/title. The `{{#rt}}` helper is used to define the record title. It creates a `div` tag with special css class of `rd_title`. Inside the div, a `<span>` is created with a class of `rt_val`. This `<span>` contains the content defined in the `{{#rt}}` block, which in this case is an `<a>` tag and another `<span>`.
217
218Likewise, the `{{#rd}}` helper creates a `<div>` that has a css class of `rd_normal`. Inside the `<div>` two `<span>` tags are created: the first one has a css of `rd_key` and it contains the string that was given to the `{{#rd}}` helper as input. The other `<span>` has a class of `rd_val` and it contains the content defined in the `{{#rd}}` block.
219
220In the second `{{#rd}}` you'll notice the use of another Handlebars helper function, `{{#concat}}`. This function takes an array as its first parameter and iterates over each element in the array. For each iteration, `{{#concat}}` sets the context of the block equal to the current array element and then concatenates the content of its block, joining each by the separator string (`sep=`) with the final element separated by the `conj=` string. In this case, if `Platforms` is a list of operating systems: `["windows", "linux", "mac"]`, then `concat` would return: **"widows, linux and mac"**.
221
222##Example #3 - Movie (Advanced Instant Answer)
223The movie instant answer is a more advanced than **NPM** and **Alternative.To**, but most of the logic is used to obtain the most relevant movie from list given to us in `api_result`. Other than that, its relatively easy to understand, so lets start by looking at the Movie instant answer's javascript:
224
225###### movie.js
226```javascript
227function ddg_spice_movie (api_result) {
228    if (api_result.total === 0) {
229        return;
230    }
231
232    var ignore = ["movie", "film", "rotten", "rating", "rt", "tomatoes", "release date"];
233    var result, max_score = 0;
234
235    // Assign a ranking value for the movie. This isn't a complete sorting value though
236    // also we are blindling assuming these values exist
237    var score = function(m) {
238        var s = m.ratings.critics_score * m.ratings.audience_score;
239        if (s > max_score) max_score = s;
240        // If any above are undefined, s is undefined.
241        return s;
242    };
243
244    // returns the more relevant of the two movies
245    var better = function(currentbest, next) {
246        // If score() returns undefined, this is false, so we're still OK.
247        return (score(next) > score(currentbest) &&
248                (next.year > currentbest.year) &&
249                DDG.isRelevant(next.title, ignore)) ? next : currentbest;
250    };
251
252    result = DDG_bestResult(api_result.movies, better);
253
254    // Favor the first result if the max score is within 1% of the score for the first result.
255    if (result !== api_result.movies[0] && Math.abs(score(api_result.movies[0]) - max_score) / max_score < 0.1) {
256        result = api_result.movies[0];
257    }
258
259    // Check if the movie that we have is relevant enough.
260    if (!DDG.isRelevant(result.title, ignore)) {
261        return;
262    }
263
264    var checkYear = function(year) {
265        if (year) {
266            return " (" + year + ")";
267        }
268        return "";
269    };
270
271    if ((result.synopsis && result.synopsis.length) ||
272        (result.critics_consensus && result.critics_consensus.length)) {
273        result.hasContent = true;
274    }
275
276```
277    
278We start by making sure that the `api_result` actually returned 1 or more results, if not we exit out, which will display nothing because no call has been made to `spice.render()`.
279
280We then go on to define some functions used to determine which movie is the most relevant by taking into consideration the rating, release date and relevancy of the title, compared to the search terms. We won't go into the details of how the `score()` and `better()` functions are defined, but you'll notice that inside `better()` we use the function `DDG.isRelevant()`. This function takes as input a string and has an optional second input containing an array of strings. `DDG.isRelevant` can be used to compare the given string to the search terms and returns a `boolean` which lets us know if the input is considered "relevant" to the search terms. The optional 2nd input, the array of strings is called the **skip array** and it contains words which should be ignored when considering the relevancy of the search term and the input to `DDG.isRelevant`. In this case, we are using `DDG.isRelevant` to compare the title of the movies returned from the Rotten Tomatoes API to the user's search term. The skip array contains arbitrary words which are likely to be found in the query, that we assume aren't relevant to the title of the movie.
281
282You'll also notice the use of `DDG_bestResult()`. This function takes as input a list of objects, and a comparator function. It then applies the comparator function, which takes two parameters, `currentbest` and `next` to each consecutive item in the input list. It assumes that the first item is the `currentbest`.
283
284By this point we have either determined that there is a relevant movie to display, or we have found nothing to be relevant and have exited out. If we have a relevant movie, we then call `Spice.render()` as we would in any other Spice instant answer:
285
286###### movie.js (continued)
287```javascript
288    Spice.render({
289        data: result,
290        source_name: 'Rotten Tomatoes',
291        template_normal: "movie",
292        template_small: "movie_small",
293        force_no_fold: 1,
294        source_url: result.links.alternate,
295        header1: result.title + checkYear(result.year),
296        image_url: result.posters.thumbnail.indexOf("poster_default.gif") === -1 ? result.posters.thumbnail : ""
297    });
298}
299```
300
301This is a fairly simple call to `Spice.render()`, but it slightly differs from other instant answer because it not only defines `template_normal`, the default template to be used, but it also defines `template_small` which is the template to be used when this instant answer is shown in a stacked state i.e., it is shown below another zero click result, but the content is minimal, preferably a single line of text.
302
303Before looking at the implementation of the Handlebars helper functions lets first take a look at the Movie Spice's Handlebars template to see how the helper functions are used:
304
305###### movie.handlebars
306```html
307<div id="movie_data_box" {{#if hasContent}}class="half-width"{{/if}}>
308    <div>
309        {{#if ratings.critics_rating}}
310            <span class="movie_data_item">{{ratings.critics_rating}}</span>
311            <span class="movie_star_rating">{{{star_rating ratings.critics_score}}}</span>
312            <div class="movie_data_description">
313            ({{ratings.critics_score}}% critics,
314             {{ratings.audience_score}}% audience approved)
315            </div>
316        {{else}}
317            <span>No score yet...</span>
318            <div class="movie_data_description">
319                ({{ratings.audience_score}}% audience approved)
320            </div>
321        {{/if}}
322    </div>
323
324    <div><span class="movie_data_item">MPAA rating:</span>{{mpaa_rating}}</div>
325    {{#if runtime}}
326        <div><span class="movie_data_item">Running time:</span>{{runtime}} minutes</div>
327    {{/if}}
328
329    {{#if abridged_cast}}
330        <div><span class="movie_data_item">Starring:</span>
331            {{#concat abridged_cast sep=", " conj=" and "}}<a href="http://www.rottentomatoes.com/celebrity/{{id}}/">{{name}}</a>{{/concat}}.
332        </div>
333    {{/if}}
334</div>
335
336{{#if hasContent}}
337    <span>
338        {{#if synopsis}}
339            {{condense synopsis maxlen="300"}}
340        {{else}}
341            {{condense critics_consensus maxlen="300"}}
342        {{/if}}
343    </span>
344{{/if}}
345
346```
347
348In the template, we create a few `div`'s and reference properties of the context just like we did in **NPM** and **Alternative.To**. We also use a few more Handlebars helper functions, `{{#star_rating}}` and `{{#rating_adjective}}` which are defined in **movie.js** as well as `{{#concat}}` and `{{#condense}}`, which we've already discussed, and another block helper, `{{#if}}` (a default Handlebars helper) which should be self-explanatory. 
349
350We use the `{{#if}}` helper to check if a variable exists in the current context and then adds the contents of its own block to the template when the input variable does exist. As well, the `{{if}}` block allows the use of the optional `{{else}}` block which lets you add alternate content to the template when the input variable does not exist.
351
352Moving on, let's take a look at the implementation of `{{#star_rating}}`:
353    
354###### movie.js (continued) - star_rating helper
355
356```javascript
357/* star rating */
358Handlebars.registerHelper("star_rating", function(score) {
359    var r = (score / 20) - 1;
360    var s = "";
361
362    if (r > 0) {
363        for (var i = 0; i < r; i++) {
364            s += "&#9733;";
365        }
366    }
367
368    if (s.length == 0) {
369        s = "0 Stars";
370    }
371
372    return s;
373});
374```
375
376As you can see this is a pretty simple function, it takes a number as input, and use that to calculate a star rating. Then creates a string of ASCII stars and returns it to the template which will then be rendered by the browser to show a star rating of the movie.
377
378Now let's take a look at the implementation of `{{#rating_adjective}}`:
379
380###### movie.js (continued) - rating_adjective helper
381```javascript
382/*
383 * rating_adjective
384 *
385 * help make the description of the movie gramatically correct
386 * used in reference to the rating of the movie, as in
387 *   'an' R rated movie, or
388 *   'a'  PG rated movie
389 */
390Handlebars.registerHelper("rating_adjective", function() {
391    return (this.mpaa_rating === "R"
392         || this.mpaa_rating === "NC-17"
393         || this.mpaa_rating === "Unrated") ?  "an" :"a";
394});
395```
396
397Again, this is a fairly simply function which simply returns either "a" or "an" based on the rating of the movie.
398
399Now that you've seen a more advanced instant answer and understand how to use Handlebars helpers, lets look at another advanced instant answer example.
400
401##Example #4 - Quixey (Advanced Carousel Instant Answer)
402The Quixey instant answer is one of our more advanced carousel instant answers which uses a considerable amount of Handlebars helpers and similarly to the **Movie** instant answer has a relevancy checking component. Let's begin by taking a look at the Quixey instant answer's JavaScript:
403
404###### quixey.js
405```javascript
406// spice callback function
407function ddg_spice_quixey (api_result) {
408
409    if (api_result.result_count == 0) return;
410
411    var q = api_result.q.replace(/\s/g, '+');
412    var relevants = getRelevants(api_result.results);
413
414    if (!relevants) return;
415
416    Spice.render({
417        data: api_result,
418        source_name: 'Quixey',
419        source_url: 'https://www.quixey.com/search?q=' + q,
420        header1: api_result.q + ' (App Search)',
421        force_big_header: true,
422        more_logo: "quixey_logo.png",
423        spice_name: 'quixey',
424        template_frame: "carousel",
425        template_options: {
426            template_item: "quixey",
427            template_detail: "quixey_detail",
428            items: relevants
429        }
430    });
431
432```
433
434Similarly to **Alternative.To**, the Quixey instant answer uses the carousel, and sets values for all the required carousel-specific properties. However, this instant answer also uses the `force_big_header` property to create a ZeroClick header and subsquently sets the value of the header text, `header1`. Also, the `more_logo` property is set, which allows a custom image to be used instead of the `source_name` text for the "More at" link.  
435
436Similarly to the **Movie** instant answer, in the **Quixey** instant answer, we use the `getRelevants()` function (defined below in **Quixey.js**), which is used to check for relevant results before calling `Spice.render()`. We are required to get relevant results in this manner so that only the results we want included in the carousel are passed on to the **quixey.handlebars** template.
437
438Moving on, let's take a look at the implementation of the `getRelevants()` helper:
439
440###### quixey.js (continued) - getRelevants function
441```javascript
442// Check for relevant app results
443function getRelevants (results) {
444        
445    var res,
446        apps = [],
447        backupApps = [],
448        categories = /action|adventure|arcade|board|business|casino|design|developer tools|dice|education|educational|entertainment|family|finance|graphics|graphics and design|health and fitness|kids|lifestyle|medical|music|networking|news|photography|productivity|puzzle|racing|role playing|simulation|social networking|social|sports|strategy|travel|trivia|utilities|video|weather/i,
449        skip_words = ["app", "apps", "application", "applications", "android", "droid", "google play store", "google play", "windows phone", "windows phone 8", "windows mobile", "blackberry", "apple app store", "apple app", "ipod touch", "ipod", "iphone", "ipad", "ios", "free", "search", "release", release date"];
450        
451    for (var i = 0; i < results.length; i++) {
452
453        app = results[i];
454
455        // check if this app result is relevant
456        if (DDG.isRelevant(app.name.toLowerCase(), skip_words)) {
457            apps.push(app);
458        } else if (app.hasOwnProperty("short_desc") &&
459                   DDG.isRelevant(app.short_desc.toLowerCase(), skip_words)) {
460                        backupApps.push(app);
461        } else if (app.custom.hasOwnProperty("category") &&
462                   DDG.isRelevant(app.custom.category.toLowerCase(), skip_words)) {
463                        backupApps.push(app);
464        } else{
465            continue;
466        }
467    }
468
469    // Return highly relevant results
470    if (apps.length > 0) {
471        res = apps;
472    }
473
474    // Return mostly relevant results
475    else if (backupApps.length > 0) {
476        res = backupApps;
477    }
478
479    else {
480        // No relevant results,
481        // check if it was a categorical search
482        // Eg."social apps for android"
483        var q = DDG.get_query();
484        res = q.match(categories) ? results : null;
485    }
486    return res;
487});
488```
489
490We begin by defining the function and its input, `results` which is an array of apps. Then we define some variables, notable we define `skip_words`, which we will use later for a call to the `isRelevant()` function we discussed earlier. Then, we move onto a `for` loop which does the bulk of the work by iterating over ever app in the `results` array and applies a series of `isRelevant()` checks to see if either the app name, short description or category are relevant to the search query. If the name is considered to be relevant we add it to the `apps` array which contains all the relevant app results. If the name isn't relevant but the description or category is, we add it to the `backupApps` array, because we might need them later. If none of those properties are considered relevant we simply exclude that app from the set of apps that will be displayed to the user.
491
492After we've checked every app we check to see if there were any relevant apps and if so, we show them to the user. Otherwise, we check our `backupApps` array to see if there were any apps who might be relevant and show those to the user. Failing that, we check if the search was for an app category and if so, we return all the results because the Quixey API is assumed to have relevant results. 
493
494Before looking at the implementation of the remaining Quixey Handlebars helpers, lets look at the template to see how the helpers are used:
495
496###### quixey.handlebars
497```html
498<p><img src="{{{icon_url}}}" /></p>
499<span>{{{condense name maxlen="40"}}}</span>
500```
501
502This template is very simple, it creates an `<img>` tag, for the resulting app icon and a `<span>` tag for the app name. You may also notice that unlilke **Alternative.To**, we placed the `<img>` tag inside `<p>` tags. We do this to automatically center and align the images, through the use of carousel specific CSS that we wrote, because the images aren't all the same size and would otherwise be missalligned. So, if the images for your instant answer aren't the same size, simply wrap them in `<p>` tags and the carousel will take care of the rest. If not, simply ignore the use of the `<p>` tags.
503
504Now let's take a look at the Quixey `carousel_template_detail` template. This template is more advanced, but most of the content is basic HTML which is populated by various `api_result` properties and Handlebars helpers:
505
506###### quixey\_detail.handlebars (continued)
507```html
508<div id="quixey_preview" style="width: 100%; height: 100%;" app="{{id}}">
509    <div class="app_info">
510        <a href="{{{url}}}" class="app_icon_anchor">
511            <img src="{{{icon_url}}}" class="app_icon">
512        </a>
513        <div class="name_wrap">
514            <a href="{{url}}" class="name" title="{{name}}">{{name}}</a>
515```
516
517Here we create the outer div that wraps the content in the detail area. Note the use of HTML ids and classes - this is to make the css more straightforward, modular and understandable.
518
519###### quixey\_detail.handlebars (continued)
520```html
521            {{#if rating}}
522                <div title="{{rating}}" class="rating">
523                    {{#loop rating}}
524                        <img src="{{quixey_star}}" class="star"></span>
525                    {{/loop}}
526                </div>
527            {{/if}}
528```
529
530Here we use the `{{#if}}` block helper and nested inside that, we use our own `{{#loop}}` block helper (defined internally), which simply counts from 0 to the value of its input, each time applying the content of its own block. In this example, we use it to create a one or more star images to represent the app's rating.
531 
532###### quixey\_detail.handlebars (continued) 
533```html
534            <div class="price">{{pricerange}}</div>
535            <div class="app_description">{{{short_desc}}}</div>
536            <div id="details_{{id}}" class="app_details">
537                <div class="app_editions">
538                    {{#each editions}}
539                        <div class="app_edition" title="{{name}} - Rating: {{rating}}">
540                            <a href="{{{url}}}" class="app_platform">
541                                {{#with this.platforms.[0]}}
542                                <img src="{{platform_icon icon_url}}" class="platform_icon">
543                                {{/with}}
544                                {{platform_name}}
545                                {{#if ../hasPricerange}}
546                                     - {{price cents}}
547                                {{/if}}
548                            </a>
549                        </div>
550                    {{/each}}
551                </div>
552            </div>
553        </div>
554    </div>
555    <div class="clear"></div>
556</div>
557```
558
559Here, we create a few more `<div>`'s and then we use another block helper, `{{#each}}`, which takes an array as input, and iterates over each of the array's elements, using them as the context for the `{{#each}}` block. Nested within the `{{#each}]` helper, we also use the `#{{with}}` block helper, which takes a single object as input, and applies that object as the context for its block. One more interesting thing to note is the input we give to the `{{#if}}` block nested in our `{{#each}}` block. We use the `../` to reference the parent template's context.  
560
561Now that we've seen the template and the helpers we're using, let's take a look at how they're all implemented:
562
563###### quixey.js (continued) -  qprice function
564```javascript
565// format a price
566// p is expected to be a number
567function qprice(p) {
568    if (p == 0) {    // == type coercion is ok here
569        return "FREE";
570    }
571    
572    return "$" + (p/100).toFixed(2).toString();
573}
574```
575
576This is a simple function that formats a price. We don't register it as a helper because we don't need to use this function directly in our templates, however our helper functions do use this function `qprice()` function.
577
578###### quixey.js (continued) -  price helper
579```javascript
580// template helper for price formatting
581// {{price x}}
582Handlebars.registerHelper("price", function(obj) {
583    return qprice(obj);
584});
585```
586
587This helper function is relatively simple, it takes a number as input, calls the `qprice()` function we just saw, and returns it's output to the template. It essentially abstracts our `qprice()` function into a Handlebars helper. We do this because the next function we'll see also uses `qprice()` and its simply easier to call it as a locally defined function, rather than register it as a helper and then use the `Handlebars.helpers` object to call the `qprice()` function.
588
589###### quixey.js (continued) -  pricerange helper
590```javascript
591// template helper to format a price range
592Handlebars.registerHelper("pricerange", function(obj) {
593   
594    if (!this.editions)
595        return "";
596
597    var low  = this.editions[0].cents;
598    var high = this.editions[0].cents;
599    var tmp, range, lowp, highp;
600
601    for (var i in this.editions) {
602        tmp = this.editions[i].cents;
603        if (tmp < low) low = tmp;
604        if (tmp > high) high = tmp;
605    }
606
607    lowp = qprice(low);
608
609    if (high > low) {
610       highp = qprice(high);
611       range = lowp + " - " + highp;
612       this.hasPricerange = true;
613    } else {
614        range = lowp;
615    }
616   
617    return range;
618});
619```
620
621This function is a little more complex, it takes an object as input, iterates over the objects keys, and records the highest and lowest prices for the app. Then, it verifies that the range has different high and low values. If not, it simply returns the low price, formatted using our `qprice()` function. Otherwise, it creates a string indicating the range and formats the values with `qprice()`.
622
623###### quixey.js (continued) -  platform\_icons helper
624```javascript
625// template helper to replace iphone and ipod icons with
626// smaller 'Apple' icons
627Handlebars.registerHelper("platform_icon", function(icon_url) {
628    if (this.id === 2004 || this.id === 2015) {
629        return "https://icons.duckduckgo.com/i/itunes.apple.com.ico";
630    }
631
632    return "/iu/?u=" + icon_url + "&f=1";
633});
634```
635
636Another very simple helper function, the `platform_icon()` function simply checks if its input is equal to `2005` or `2015` and if so returns a special url for the platform icon. If not, it returns the originial icon url but adds our proxy redirect, `/iu/?u=` as previously discussed.
637
638###### quixey.js (continued) -  platform\_name helper
639```javascript
640// template helper that returns and unifies platform names
641Handlebars.registerHelper("platform_name", function() {
642    var name;
643    var platforms = this.platforms;
644
645    name = platforms[0].name;
646
647    if (platforms.length > 1) {
648        switch (platforms[0].name) {
649            case "iPhone" :
650            case "iPad" :
651                name = "iOS";
652                break;
653
654            case "Blackberry":
655            case "Blackberry 10":
656                name = "Blackberry";
657                break;
658        }
659    }
660
661    return name;
662});
663```
664
665This helper is also quite simple, it is used to return a platform name and someties also unifies the platform name when multiple platforms exist for an app. If the app is available for both 'iPhone' and 'iPad', the `switch()` will catch this and indicate the app is availabe for "iOS".
666
667###### quixey.js (continued) -  quixey\_star helper
668```javascript
669// template helper to give url for star icon
670Handlebars.registerHelper("quixey_star", function() {
671    return DDG.get_asset_path("quixey", "star.png").replace("//", "/");
672});
673```
674
675This helper is also very simple, but it is important because it uses the `DDG.get_asset_path()` function which returns the URI for an asset stored in a instant answer's share folder. This is necessary because Spice instant answers and their content are versioned internally. So the URI returned by this function will contain the proper version number, which is required to access any assets.
676
677##Example #5 - Dictionary (More Advanced Instant Answer)
678The dictionary instant answer is a more advanced instant answer than the previous examples, because it requires multiple endpoints (which means it has multiple perl modules -`.pm` files) in order to function properly. You will notice the `definition` endpoint is a subdirectory of the `dictionary` directory: `zeroclickinfo-spice/share/spice/dictionary/definition/`. In the case of the **Dictionary** instant answer, its Perl modules work together as one instant answer, however if the other endpoints worked seperately from the `definition` endpoint, such as they do in the **[Last.FM](https://github.com/duckduckgo/zeroclickinfo-spice/tree/spice2/share/spice/lastfm)** instant answer, they too would each have their own subdirectories and would also each have their own respective JavaScript, Handlebars and CSS files. 
679
680To begin, lets look at the first callback function definition in the Dictionary javascript:
681
682######dictionary_definition.js
683```javascript
684// Description:
685// Shows the definition of a word.
686//
687// Dependencies:
688// Requires SoundManager2.
689//
690// Commands:
691// define dictionary - gives the definition of the word "dictionary."
692//
693// Notes:
694// ddg_spice_dictionary_definition - gets the definitions of a given word (e.g. noun. A sound or a combination of sounds).
695// ddg_spice_dictionary_pronunciation - gets the pronunciation of a word (e.g. wûrd).
696// ddg_spice_dictionary_audio - gets the audio file.
697// ddg_spice_dictionary_reference - handles plural words. (Improve on this in the future.)
698```
699
700The comments at the beginning of the file explain what the various callbacks are for. Each of these callback functions is connected to a different endpoint, meaning they each belong to a different Perl module. As you can see, the name of each callback corellates to the name of the perlmodule. So `dictionary_definition()` is the callback for `DDG::Spice::Dictionary::Definition`, likewise `dictionary_audio` is for `DDG::Spice::Dictionary::Audio`, etc.
701
702Each of these endpoints are used to make different API calls (either to a different endpoint or possibly even a different API altogether), which can only be done by creating a different Perl module for each endpoint. We can make these endpoints work together for a given instant answer by using the jQuery `getScript()` function which makes an ajax call to a given endpoint, which results in a call to that endpoint's callback function. This function needs to be defined before it is called, so the Dictionary instant answer defines all **four** callback functions in **dictionary_definition.js**
703
704Moving on, let's take a look at the implementation of the `Spice.render()` call and the `dictionary_definition()`  callback:
705
706######dictionary_definition.js (continued) - dictionary_definition callback
707```javascript
708// Dictionary::Definition will call this function.
709// This function gets the definition of a word.
710function ddg_spice_dictionary_definition (api_result) {
711    "use strict";
712    var path = "/js/spice/dictionary";
713
714    // We moved Spice.render to a function because we're choosing between two contexts.
715    var render = function(context, word, otherWord) {
716        Spice.render({
717            data              : context,
718            header1           : "Definition (Wordnik)",
719            force_big_header  : true,
720            source_name       : "Wordnik",
721            source_url        : "http://www.wordnik.com/words/" + word,
722            template_normal   : "dictionary_definition"
723        });
724
725        // Do not add hyphenation when we're asking for two words.
726        // If we don't have this, we'd have results such as "black• hole".
727        if(!word.match(/\s/)) {
728            $.getScript(path + "/hyphenation/" + word);
729        }
730
731        // Call the Wordnik API to display the pronunciation text and the audio.
732        $.getScript(path + "/pronunciation/" + otherWord);
733        $.getScript(path + "/audio/" + otherWord);
734    };
735```
736
737We begin by wrapping the `Spice.render()` call in a function which also does a little extra work. Specifically after rendering the result it calls the Wordnik API, this time using two different API endpoints. The first gets the pronounciation text, the second gets the audio file for the pronounciation of the word. As mentioned these endpoints are used to work together as one instant answer so using the returns from the seperate API calls we construct one dictionary instant answer result which contains the word definition, the pronounciation text and the audio recording of the pronounciation.
738
739The reason for wrapping the `Spice.render()` call in a function is because we need to be able to call our `render()` function from both the `dictionary_defintion()` callback as well as the `dictionary_reference()` callback, as you will see below:
740
741######dictionary_definition.js (continued) - dictionary_definition callback
742```javascript
743    // Expose the render function.
744    ddg_spice_dictionary_definition.render = render;
745
746    // Prevent jQuery from appending "_={timestamp}" in our url when we use $.getScript.
747    // If cache was set to false, it would be calling /js/spice/dictionary/definition/hello?_=12345
748    // and that's something that we don't want.
749    $.ajaxSetup({
750        cache: true
751    });
752
753    // Check if we have results we need.
754    if (api_result && api_result.length > 0) {
755
756        // Wait, before we display the instant answer, let's check if it's a plural
757        // such as the word "cacti."
758        var singular = api_result[0].text.match(/^(?:A )?plural (?:form )?of <xref>([^<]+)<\/xref>/i);
759
760        // If the word is plural, then we should load the definition of the word
761        // in singular form. The definition of the singular word is usually more helpful.
762        if(api_result.length === 1 && singular) {
763            ddg_spice_dictionary_definition.pluralOf = api_result[0].word;
764            $.getScript(path + "/reference/" + singular[1]);
765        } else {
766            // Render the instant answer if everything is fine.
767            render(api_result, api_result[0].word, api_result[0].word);
768        }
769    }
770};
771```
772
773After defining the `render()` function we give the function a `render` property, `ddg_spice_dictionary_definition.render = render;` (so we can access the `render()` function from other callbacks) and then move on to check if we actually have any definition results returned from the API. If so, we then check if the queried word is a plural word and if so, make another API call for the singular version of the queried word. This call, `$.getScript(path + "/reference/" + singular[1]);` will result in calling the `dictionary_reference()` callback which eventually calls our `render()` function to show our Spice result on the page. If the word is not a plural, we instead immediately call the `render()` function and display our result.
774
775**\*\*Note:** More info on the jQuery `$.getScript()` method is available [here](http://api.jquery.com/jQuery.getScript/).
776
777######dictionary_definition.js (continued) - dictionary_reference callback
778```javascript
779// Dictionary::Reference will call this function.
780// This is the part where we load the definition of the
781// singular form of the word.
782function ddg_spice_dictionary_reference (api_result) {
783    "use strict";
784
785    var render = ddg_spice_dictionary_definition.render;
786
787    if(api_result && api_result.length > 0) {
788        var word = api_result[0].word;
789
790        // We're doing this because we want to say:
791        // "Cacti is the plural form of cactus."
792        api_result[0].pluralOf = word;
793        api_result[0].word = ddg_spice_dictionary_definition.pluralOf;
794
795        // Render the instant answer.
796        render(api_result, api_result[0].word, word);
797    }
798};
799```
800
801In this relatively simple callback, we begin by using the previously defined render property of the `dictionary_definiton()` function to give this callback access to the `render()` function we defined at the beginning of `quixey.js`. Then we confirm that this callback's `api_result` actually recieved the singular form of the originially searched query. If so, we add the singular and plural form of the word to our `api_result` object so we can check for and use them later in our Handlebars template.
802
803######dictionary_definition.js (continued) - dictionary_hyphenation callback
804```javascript
805// Dictionary::Hyphenation will call this function.
806// We want to add hyphenation to the word, e.g., hello -> hel•lo.
807function ddg_spice_dictionary_hyphenation (api_result) {
808    "use strict";
809
810    var result = [];
811    if(api_result && api_result.length > 0) {
812        for(var i = 0; i < api_result.length; i += 1) {
813            result.push(api_result[i].text);
814        }
815        // Replace the, rather lame, non-hyphenated version of the word.
816        $("#hyphenation").html(result.join("•"));
817    }
818};
819```
820
821This callback is also fairly simple. If the API returns a result for the hyphenated version of the word, we loop over the response to get the various parts of the word, then join them with the dot character "•", and inject the text into the HTML of the **#hyphenation** `<div>` using jQuery.
822
823######dictionary_definition.js (continued) - dictionary_pronunciation callback
824```javascript
825// Dictionary::Pronunciation will call this function.
826// It displays the text that tells you how to pronounce a word.
827function ddg_spice_dictionary_pronunciation (api_result) {
828    "use strict";
829
830    if(api_result && api_result.length > 0 && api_result[0].rawType === "ahd-legacy") {
831        $("#pronunciation").html(api_result[0].raw);
832    }
833};
834```
835
836Similarly to the `dictionary_hyphenation()` callback, this callback receives a phonetic spelling of the queried word and injects it into the Spice result by using jQuery as well to modify the HTML of the **#pronounciation** `<div>`.
837 
838######dictionary_definition.js (continued) - dictionary_audio callback
839```javascript
840// Dictionary::Audio will call this function.
841// It gets the link to an audio file.
842function ddg_spice_dictionary_audio (api_result) {
843    "use strict";
844
845    var isFailed = false;
846    var url = "";
847    var icon = $("#play-button");
848
849    // Sets the icon to play.
850    var resetIcon = function() {
851        icon.removeClass("widget-button-press");
852    };
853
854    // Sets the icon to stop.
855    var pressIcon = function() {
856        icon.addClass("widget-button-press");
857    };
858```
859
860This callback begins by defining a few simple functions and some variables to used below. Again, jQuery is used to modify the DOM as needed in this callback.  
861
862```javascript
863    // Check if we got anything from Wordnik.
864    if(api_result && api_result.length > 0) {
865        icon.html("▶");
866        icon.removeClass("widget-disappear");
867
868        // Load the icon immediately if we know that the url exists.
869        resetIcon();
870
871        // Find the audio url that was created by Macmillan (it usually sounds better).
872        for(var i = 0; i < api_result.length; i += 1) {
873            if(api_result[i].createdBy === "macmillan" && url === "") {
874                url = api_result[i].fileUrl;
875            }
876        }
877
878        // If we don't find Macmillan, we use the first one.
879        if(url === "") {
880            url = api_result[0].fileUrl;
881        }
882    } else {
883        return;
884    }
885```
886
887The callback then verifies the API returned a pronunciation of the queried word and if so, injects a play icon, "▶" into the **#play-button** `<button>` and grabs the url for the…

Large files files are truncated, but you can click here to view the full file