Acre Recipes

From Freebase

Jump to: navigation, search

This page collects Acre recipes, small code fragments that show you how to do achieve specific things with Acre.

Feel free to contribute yours!

Contents

Make library files runnable

Usually you want to make library scripts and templates executable so you can test them.

Example acre template:

<acre:def="useful_template(args)">
  ...
</acre:def>

<acre:block if="acre.current_script.id === acre.request.script.id">
  ${useful_template("sample data")}
</acre:block>

Example acre script:

function useful_func(food) {
  return 'I like to eat '+food;
}

if (acre.current_script.id === acre.request.script.id) {
  acre.write( useful_func('oats') );
}

Write Acre Templates to generate XML (or XHTML,RSS,etc)

Acre has two template parsing modes, a forgiving HTML one (the default) and a strict XML one (which is triggered the presence of the <?xml version="1.0"?> in the first line of your template).

Here's the boilerplate to write XHTML templates in Acre:

    <?xml version="1.0"?>
    <acre:doc type="application/xhtml+xml">
      ${acre.markup.bless('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">')}
      <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
        <head>
          <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
          <title>XHTML Test</title>
        </head>
        <body>
          <p><a href="http://validator.w3.org/check/referrer">Validate as XHTML 1.0 Transitional</a></p>
        </body>
      </html>
    </acre:doc>
  • Line 1 - the template is XML and should be parsed by the strict XML parser;
  • Line 2 - the mime-type of the resulting content should be "application/xhtml+xml"
  • Line 3 - use acre.markup.bless to output the doctype you want.

NOTE: placing the DOCTYPE below the <?xml version="1.0"?> doesn't work because it will be considered a declaration for the parser and it's not retained when the content is serialized at output. This is the reason for the explicit injection in line 3.

WARNING: if Acre is parsing your template with the XML parser, it will be very unforgiving, including all the <acre:*> namespaced tags. For example,

    <?xml version="1.0"?>
    <acre:script>
      ...
    </acre:script>
    <html>
      ...
    </html>

will cause a parsing failure because an XML document can only have one root element and here it has two "acre:script" and "html". Use the acre:doc element as the root as in the code fragment above to avoid this problem.

Debug Acre Server-side Code

The best way to debug Acre server-side code (that is, code in "Template" files or "Script" files, as indicated by the headings in the file pane on the left panel) is to use console.log

   console.log("hello");
   console.log(0);
   console.log({ "foo" : "bar", "baz" : [ 1, 4, 9, 16, 25 ] });
   console.log(aVariable);

This function operates on the Acre server and logs whatever you pass to it to a server-side log queue, which can then be viewed in your browser when you view your template or script file by clicking on the button "View with Console". If you already clicked the button "View" instead, then you can force the Acre Console open by adding the parameter acre.console=1 to the URL.

Note that if you're logging a complex Javascript object, it will be displayed as an expandable/collapsible tree much as such objects are displayed in Firebug or other client-side in-browser object viewer.


Fetch and Parse XML/HTML Content Retrieved from Another Web Site

To fetch and parse an XML file given a URL, do:

   var doc = acre.xml.parse(acre.urlfetch(url).body);

while to fetch and parse an HTML gile given a URL do:

   var doc = acre.html.parse(acre.urlfetch(url).body);

Then you can use W3C DOM API to traverse the doc. For example, to get news items from an RSS feeds:

   var itemNodes = doc.getElementsByTagName("item");
   for (var i = 0; i < itemNodes.length; i++) {
     var itemNode = itemNodes[i];
     var title = itemNode.getElementsByTagName("title")[0].firstChild.nodeValue;
     // ...
   }

If you're looking for a simpler way to select parts of the DOM tree, there is the Sizzle Acre library that uses Sizzle (the same code that powers JQuery) to enable selection using CSS selectors:

 acre.require("/freebase/libs/sizzle/sizzle");         // this sizzle-powers the Acre HTML parser
 var body = acre.urlfetch(uri).body;                   // get the HTML document as a string
 var document = acre.html.parse(body);                 // parse the HTML document and get a DOM
 var links = document.find("#content a");              // look for all links in the DOM using CSS selectors
 for (var i in links) {                                // iterate over all the links
   acre.write(links[i].getAttribute("href"));          // get the 'href' attribute of the DOM element returned
 }

Pass a Javascript object from the server-side to the client-side

Often, your server-side code queries Freebase for data, and then it needs to pass some or all of that data to the client-side code (Javascript) so that the client-side code can support dynamic behaviors based on that data. You can do this using the ${ } substitution just like you would in a normal mjt template

   <acre:script>
     var my_var = { "foo" : 1 }; // This is evaluated on the server
   </acre:script>
   <script type="application/javascript">
     var x = ${JSON.stringify(my_var)};
     .... use x ....
   </script>

On the client side, the code looks like

   <script type="application/javascript">
     var x = { "foo" : 1 };
     .... use x ....
   </script>


Process an HTTP POST Request

You can process an HTTP POST either through a template file or a script file. Use a template file if you want to return an HTML document (or an XML document); use a script file if you want to return JSON or JSONP.

First, decide whether your file will also support GET. This is important as often, HTTP POSTs are used to prevent cross-domain XmlHttp requests, you might want to explicitly refuse HTTP GET requests. If you're using a script file, you can use this pattern to check

   <acre:block if="acre.request.method != 'POST'">
     <html><body>Only POSTs allowed.</body></html>
   </acre:block>
   <acre:block else="">
     <html> ... normal processing here ... </html>
   </acre:block>

If you're using a script file, use this pattern

   if (acre.request.method != 'POST') {
     acre.response.status = 200;
     acre.write(JSON.stringify({ "code" : "error", "message" : "Only POSTs allowed." }));
   } else {
     // ... normal processing here ...
   }

After making that check, then respond to the request using URL parameters passed in acre.request.params and parameters passed through the POST body in acre.request.body_params. For example,

   var query = acre.request.params["query"];
   var content = acre.request.body_params["content"];

Note that these parameters have already been URL-decoded for you. However, if a parameter is a JSON string, you have to call JSON.parse on it yourself. Or if a parameter is a boolean or a number, then you must parse it yourself (e.g., using Javascript's global functions parseInt or parseFloat).

Note that on the client-side, we recommend that you use jQuery's post method to make the POST request

   <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
   ...
   $.post(
     "script_file_id_here?query=" + query,
     {  "content" : someString,
        "anotherBodyParam" : 0
     },
     function(result) {
       if (result.code == "ok") {
         // ... do something with result
       } else {
         // ... display result.message
       }
     },
     "json"
   );

Note that if you want to pass a parameter that is an array or an object, then you must serialize it yourself.

Debugging POSTs are typically harder than debugging GETs, because it's hard to simulate a POST in a browser by navigating to a URL. One way to see what your server-side code sees is to send back some internal variables in the HTTP response:

     acre.write(JSON.stringify({
       "code" : "error", 
       "message" : "All hell breaks loose.", 
       "body_params" : acre.request.body_params 
     }));

And use something like Firebug to inspect the HTTP POST request and response.


Render a Comma-Separated List of Things in A Template File

Say you have an array of things and you want to render them (into HTML from within a template file) as a comma-separated list:

   1, 2, 3, 4

and you're picky about where the commas go. You don't want any space between a comma and its preceding item, and you don't want a comma after the last item. The trick here is to test the index of each item and not generate a comma for the first item:

   <acre:block for="i,v in [1,2,3,4]" trim="">
     <acre:block if="i > 0">,</acre:block>
     $v
   </acre:block>

The trim attribute makes sure that Acre eliminates undesirable spaces.


Resolve a Text Name to A Freebase Topic

Given a string (e.g., "San Francisco") that is supposed to be the name of a Freebase topic (/en/san_francisco), you can resolve the name to that topic by using the Search API (also known as the "Relevance" API). As it is a Web service with an HTTP entry point, you can call it directly using acre.urlfetch, but we recommend the acre.freebase.search utility function:

   var r = acre.freebase.search(
     "San Francisco",
     { "type" : "/location/citytown", 
       "type_strict" : "any",
       "limit" : 3
     }
   );
   if (r.code == "/api/status/ok") {
     var entry = r.result[0];
     var topicID = entry.id;
     ...
   }

While you can resolve a text name to a Freebase topic this way, it's not accurate, because many topics share the same name. We recommend that you use Freebase Suggest, which allows your user to interactively pick a topic from a list of suggestions. Freebase Suggest renders many more identifying details beyond just the name for each suggestion so that your user can make the right choice. See the next recipe for more details.


How to use Freebase Suggest in an Acre Template

Freebase Suggest is a client-side Javascript library that lets users find Freebase IDs for topics in a natural and effort-saving manner. Here is the simplest way you can embed it in an Acre template:

   <html>
    <head>
      ${acre.require("/freebase/apps/default/templates").suggest()}
      <script>
        $(function() {
          $("#myinput").suggest({type:'/film/director'});
        });
      </script>
    </head>
    <body>
      Select a Film Director: <input type="text" id="myinput"/>
    </body>
   </html>

Return JSON or JSONP

In order to return JSON or JSONP, you need to use a "script" file, not a "template" file. In the script file, you just need

   acre.response.status = 200;
   acre.response.set_header("content-type", "application/json");
   var obj = { ... };
   acre.write(JSON.stringify(obj));

where obj is the Javascript object you want to return.

To support JSONP with the callback URL parameter named callback, you can do this:

   acre.response.status = 200;
   acre.response.set_header("content-type", "application/json");
   var callback = acre.environ.params["callback"];
   if (callback) {
    acre.write(callback + "(");
   }
   var obj = { ... };
   acre.write(JSON.stringify(obj));
   if (callback) {
     acre.write(")");
   }

If you want the JSON string to be readable (formatted on multiple lines and indented), use JSON.stringify(obj, null, 2) instead.


Query for Geographic lat/lng Coordinates

Latitude/longitude coordinates are stored in /location/geocode nodes but typically in your app you're dealing with /location/location topics. So you would have to query for the geocode node(s) associated with a location topic. For example, if you want to find the lat/lng of San Francisco, do:

   var o = acre.freebase.mqlread({
     "id" : "/en/san_francisco",
     "type" : "/location/location",
     "geolocation" : [{
       "latitude" : null,
       "longitude" : null,
       "optional" : true
     }]
   });
   if (o.result["geolocation"].length > 0) {
     var geocode = o.result["geolocation"][0];
     ... do something with geocode.latitude and geocode.longitude ...
   }


Recursive Templates

Acre templates are compiled into Javascript functions, which means that you can them recursively... just like any other function. For example, here's a dict/list formatter, suitable for displaying JSON values:

    <acre:script>
      q = acre.require("/user/jdouglas/templates/example_query").query;
    </acre:script>

    <acre:block def="showjson(o)"
      acre:choose="typeof(o)">
      <span acre:when="object">
        <i acre:if="!o"> null </i>
        <span acre:if="o instanceof Array">
          [
          <div acre:for="v in o"
            style="padding-left:20px;">
            ${showjson(v)}
          </div>
          ]
        </span>
        <span acre:else="">
          {
          <div acre:for="k,v in o"
            style="padding-left:20px;">
            $k: ${showjson(v)}
          </div>
          }
        </span>
      </span>
      <span acre:when="number"> $o </span>
      <i acre:when="boolean"> $o </i>
      <span acre:when="string"> "$o" </span>
    </acre:block>

    <div> ${showjson(q)} </div>


Render a Mult-Column Table in a Template

Here's a way to do multi-column display of a list:

    <table border="1" acre:def="mktable(ncols, items)">
      <tr acre:for="(var rowi = 0; rowi < items.length; rowi += ncols)">
         <td acre:for="(var coli = 0; coli < ncols; coli++)"
             acre:if="rowi+coli < items.length">
             ${items[rowi+coli]}
         </td>
      </tr>
    </table>
    ${mktable(3, 'the quick brown fox jumped over the lazy dog'.split(' '))}


Reuse Template Fragments

Often, you need to render a certain kind of things in the same way on different web pages served by different Acre template files. You can extract out that rendering code into an Acre sub-template. Create another file to store one or more such sub-templates, e.g.,

    <acre:block def="render1(p1, p2)">
      <div>
        $p1 $p2 ...
      </div>
    </acre:block>

    <acre:block def="render2(o1, o2)">
      ...
    </acre:block>

Then call these sub-templates from the original template files, e.g.,

    <html>
      <acre:script>
        var templates = acre.require("id_of_file_containing_sub_templates");
      </acre:script>
      ...
      <body>
        ...
        ${templates.render1(arg1, arg2)}

You could also define a sub-template within a full template file.

Run Multiple Queries Together

To run several MQL queries together, you'd first need to form an envelope that holds all of the queries:

   var env = {  
     "q1" : {
       "query" : {
         "id":   "/en/radiohead",
         "type": []
       }
     },
     "q2" : {
       "query" : {
         "id":   "/en/david_bowie",
         "type": []
       }
     }
   };

Then, perform an HTTP POST to the mqlread service. That involves encoding the POST body (payload) properly as well as setting the right HTTP request headers.

   var body = acre.form.encode({ "queries" : JSON.stringify(env) });
   var response = acre.urlfetch("http://www.freebase.com/api/service/mqlread", "POST", {
     'Content-type' : 'application/x-www-form-urlencoded',
     "Content-Length" : body.length.toString()
   }, body);

Finally, parse the HTTP response body:

   var o = JSON.parse(response.body);


Support the iPhone

There are a few tasks involved in supporting the iPhone. First, if a particular page in your Acre app is supposed to respond differently to a desktop browser and to an iPhone, then you need to detect that the page is serving an iPhone and include the corresponding iPhone-specific template file, called page-iphone-version in this code sample:

    <acre:block if="acre.request.headers['user-agent'].indexOf('iPhone') > 0">
      ${acre.include("page-iphone-version")}
    </acre:block>
    <acre:block else="">
      <html> ... normal page here ... </html>
    </acre:block>

Next, you want to make sure that the iPhone-specific template includes a meta tag that fixes the web page's width to the device's width and sets the scale to 1.0:

    <html>
      <head>
        <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0;" />

This makes sure that everything is 100% scaled, and you can then lay out your page accurately to the pixel.

You might find it convenient to open two "tabs" on your iPhone: one for the page you are developing, and one for /acre/touch so you can quickly force your iPhone's Safari to clear Freebase cookies and get the latest code changes.


Beat the timeout

Acre gives its users a time limit for execution. One can expect to be able to perform ~100-600 mqlread queries, depending on their size, before a timeout.

To workaround, pass a cursor to the client side, and instruct it to begin again where it left off:

<acre:script> 
   var cursor = acre.request.body_params.cursor || false; 
   var query={"id":null,"type":"/people/person","limit":1}
     if(!cursor){  var response = acre.freebase.mqlread(query, {cursor:true});}//first one
     if(cursor){var response = acre.freebase.mqlread(query, {cursor:cursor});} //not first one
     cursor=response.cursor;
     acre.write(response.result.id);  
</acre:script>
<html><body onload="document.theform.submit();">
   <form action="" method="post"name="theform">
     <input type="hidden" name="cursor" value="$cursor" />
   </form>
 </body></html>

try it in acre

Use Google Maps with Acre

acre has a built-in api key- ${acre.keystore.get('maps.google.com')[0]}

      $(document).ready(function () {
       if (google.maps.BrowserIsCompatible()) {
         var map = new google.maps.Map2(document.getElementById("map_location"));
         map.setCenter(new GLatLng(37.4419, -122.1419), 13);
         map.setUIToDefault();          
       }
     });

acre demo google documentation

Personal tools