Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 0c92a6b590
Fetching contributors…

Cannot retrieve contributors at this time

319 lines (249 sloc) 14.778 kB
{% extends "api.html" %}
{% block api_content %}
<p style="font-size: 14px;"><em>Jump to:</em>
&nbsp; <a href="#routes">Routes</a>
&nbsp; <a href="#auth">Authorization</a>
&nbsp; <a href="#return_values">Return values</a>
&nbsp; <a href="#lang">Content-Language</a>
&nbsp; <a href="#filter">Post-processing</a>
&nbsp; <a href="#simplebridge">SimpleBridge request object</a></p>
<p>Chicago Boss associates each URL with a function of a controller.
The URL <nobr>/foo/bar</nobr> will call the function <code>foo_controller:bar</code>.
Each controller module should go into your project's src/controller/ directory and the file name should start with the application name and end with "_controller.erl", e.g. "appname_my_controller.erl".
Helper functions should go into your project's src/lib/ directory.
Controllers can take one parameter or two parameters: the <a href="#simplebridge">SimpleBridge request object</a>, and an optional session ID (if <a href="api-session.html">sessions</a> are enabled). Declare it like:</p>
<div class="code">
<span class="attr">-module</span>(appname_my_controller, [Req]).
</div>
<p>Or:</p>
<div class="code">
<span class="attr">-module</span>(appname_my_controller, [Req, SessionID]).
</div>
<p>Each exported controller function takes two or three arguments:</p>
<ul>
<li>First argument: the HTTP request method as an atom, e.g. <code>'GET'</code> or <code>'POST'</code></li>
<li>Second argument: the list of slash-separated tokens after the action name in the URL.</li>
<li>Third argument (optional): the result of a function named <code>before_</code> in the controller</li>
</ul>
<p>Example function clauses:</p>
<div class="code">
<span class="comment">% GET /blog/view</span><br />
view(<span class="atom">'GET'</span>, []) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
<span class="comment">% GET /blog/view/1234</span><br />
view(<span class="atom">'GET'</span>, [Id]) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
<span class="comment">% GET /blog/view/tag/funny</span><br />
view(<span class="atom">'GET'</span>, [<span class="string">"tag"</span>, Tag]) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
<span class="comment">% GET /blog/view/tag/funny/author/saint-paul</span><br />
view(<span class="atom">'GET'</span>, [<span class="string">"tag"</span>, Tag, <span class="string">"author"</span>, AuthorName]) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
<span class="comment">% GET /blog/view/2009/08</span><br />
view(<span class="atom">'GET'</span>, [Year, Month]) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;...<br />
</div>
<p>These function clauses act as templates for constructing URLs in the view; for each CamelCase variable, simply use the lower-cased underscored equivalent as the parameter name. To continue the example above, you can construct URLs to match the above controllers with the following view tags:</p>
<div class="code">
{{ '{% url action="view" %}' }}<br />
{{ '{% url action="view" id="1234" %}' }}<br />
{{ '{% url action="view" tag="funny" %}' }}<br />
{{ '{% url action="view" tag="funny" author_name="saint-paul" %}' }}<br />
{{ '{% url action="view" year="2009" month="08" %}' }}<br />
</div>
<p>Template variables can of course be used in place of string literals.</p>
<a name="routes"></a>
<h3>Routes</h3>
<p>Most routing takes place in the controller pattern-matching code. You can define additional routes in <code>priv/my_application.routes</code>. The file contains a list of erlang terms, one per line finished with a dot. Each term is a tuple with a URL or an HTTP status code as the first term, and a <code>{Controller::string(), Action::string()}</code> or <code>{Controller::string(), Action::string(), Parameters::proplist()}</code> tuple as the second term.</p>
<p>A few examples:</p>
<div class="code">
{"/", [{controller, "main"}, {action, "welcome"}]}.<br />
{"/signup", [{application, login_app}, {controller, "account"}, {"create"}]}.<br />
{404, [{controller, "main"}, {action, "not_found"}]}.
</div>
<p>Most routes directly render the specified action; however, routing across applications (as in the second example) results in a 302 redirect.</p>
<p>To define a default action for a controller, simply add a <code>default_action</code> attribute to the controller like so:</p>
<div class="code">
-default_action(index).
</div>
<a name="auth"></a>
<h3>Authorization</h3>
<p>If an action takes three arguments, then the function <code>before_/1</code> in your controller will be passed the action name as a string and should return one of:</P>
<div class="code spec">
{ok, ExtraInfo}
</div>
<p><code>ExtraInfo</code> will be passed as the third argument to the action, and as a variable called "before_" to the templates.</p>
<div class="code spec">
{redirect, Location}
</div>
<p><code>Location = string() | [{Key<span class="typevar">::atom()</span>, Value<span class="typevar">::atom()</span>}]</code></p>
<p>Do not execute the action. Instead, perform a 302 redirect to <code>Location</code>, which can be a string or a proplist that will be converted to a URL using the routes system.</p>
<p>Probably most common before_ looks like:</p>
<div class="code spec">
before_(_) -&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="function">my_user_lib</span>:<span class="function">require_login</span>(Req).
</div>
<p>Which might return a tuple of user credential or else redirect to a login page. This way, if you want to require a login to a set of actions, just give those actions a <code>User</code> argument, and the actions will be login protected and have access to the <code>User</code> variable.</p>
<br />
<a name="return_values"></a>
<h3>Return values</h3>
<p>Whether or not it takes a third argument, a controller action should return with one of the following:</p>
<div class="code spec">
ok
</div>
<p>The template will be rendered without any variables.</p>
<div class="code spec">
{ok, Variables<span class="typevar">::proplist()</span>}
</div>
<p><code>Variables</code> will be passed into the associated <a href="api-view.html#nav">Django template</a>.</p>
<div class="code spec">
{ok, Variables<span class="typevar">::proplist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p><code>Variables</code> will be passed into the associated Django template, and <code>Headers</code> are HTTP headers you want to set (e.g., <code>Content-Type</code>).</p>
<div class="code spec">
{redirect, Location}
</div>
<p><code>Location = string() | [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
<p>Perform a 302 HTTP redirect to <code>Location</code>, which may be a URL string or a proplist of parameters that will be converted to a URL using the routes system.</p>
<div class="code spec">
{redirect, Location, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Perform a 302 HTTP redirect to <code>Location</code> and set additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{action_other, OtherLocation}
</div>
<p><code>OtherLocation = [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
<p>Execute the controller action specified by <code>OtherLocation</code>, but without performing an HTTP redirect.</p>
<div class="code spec">
{render_other, OtherLocation}
</div>
<p><code>OtherLocation = [{action, Value<span class="typevar">::string()</span>}, ...]</code></p>
<p>Render the view from <code>OtherLocation</code>, but don't actually execute the associated controller action.</p>
<div class="code spec">
{render_other, OtherLocation, Variables}
</div>
<p>Render the view from <code>OtherLocation</code> using <code>Variables</code>, but don't actually execute the associated controller action.</p>
<div class="code spec">
{output, Output<span class="typevar">::iolist()</span>}
</div>
<p>Skip views altogether and return <code>Output</code> to the client.</p>
<div class="code spec">
{output, Output<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Skip views altogether and return <code>Output</code> to the client while setting additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{json, Data<span class="typevar">::proplist()</span>}
</div>
<p>Return <code>Data</code> as a JSON object to the client. Performs appropriate serialization if the values in Data contain a BossRecord or a list of BossRecords.</p>
<div class="code spec">
{json, Data<span class="typevar">::proplist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Return <code>Data</code> to the client as a JSON object while setting additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{jsonp, Callback<span class="typevar">::string()</span>, Data<span class="typevar">::proplist()</span>}
</div>
<p>Returns <code>Data</code> as a JSONP method call to the client. Performs appropriate serialization if the values in Data contain a BossRecord or a list of BossRecords.</p>
<div class="code spec">
{jsonp, Callback<span class="typevar">::string()</span>, Data<span class="typevar">::proplist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Return <code>Data</code> to the client as a JSONP method call (as above) while setting additional HTTP <code>Headers</code>.</p>
<div class="code spec">
not_found
</div>
<p>Invoke the 404 File Not Found handler.</p>
<br />
<a name="lang"></a>
<h3>Content-Language</h3>
<p>CB application views can be multi-lingual. By default, the language served to the client is chosen by comparing the incoming Accept-Language header to the available translations in a given view (see <a href="https://github.com/evanmiller/ChicagoBoss/wiki/How-Chicago-Boss-Chooses-Which-Language-To-Serve">"How Chicago Boss Chooses Which Language To Serve"</a>. This can be overridden in two ways:</p>
<ol>
<li>Returning [{"Content-Language", Lang}] from each action in your controller
<li>Defining a <code>lang_</code> function in your controller which returns the chosen language
</ol>
<p>The <code>lang_</code> function will be passed the name of the current action, and optionally the result of the <code>before_</code> filter. This function should return one of:</p>
<ul>
<li><code>auto</code> - automatically choose a language based on the Accept-Language header
<li>A string indicating the language choice ("en", "fr", etc.)
</ul>
<br />
<a name="filter"></a>
<h3>Post-processing</h3>
<p>If it exists, a function called <code>after_</code> in your controller will be passed the result that is about to be returned to the client. The 'after_' function takes two or three arguments:</p>
<ol>
<li>The action name, as a string</li>
<li>The HTTP result tuple</li>
<li>The result of the before_ function, provided one exists</li>
</ol>
<p>The <code>after_</code> function should return a (possibly) modified HTTP result tuple. Result tuples may be one of:</p>
<div class="code spec">
{redirect, Location<span class="typevar">::string()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Performs a 302 HTTP redirect to <code>Location</code> and sets additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{ok, Payload<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Returns a 200 OK response to the client with <code>Payload</code> as the HTTP body, and sets additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{unauthorized, Payload<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Returns a 401 Unauthorized response to the client with <code>Payload</code> as the HTTP body, and sets additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{not_found, Payload<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Returns a 404 Not Found response to the client with <code>Payload</code> as the HTTP body, and sets additional HTTP <code>Headers</code>.</p>
<div class="code spec">
{error, Payload<span class="typevar">::iolist()</span>, Headers<span class="typevar">::proplist()</span>}
</div>
<p>Returns a 500 Internal Error response to the client with <code>Payload</code> as the HTTP body, and sets additional HTTP <code>Headers</code>.</p>
<br />
<a name="simplebridge"></a>
<h3>SimpleBridge</h3>
<p>Controller functions are passed a SimpleBridge request object (slightly modified for Boss's purposes). Useful functions in the request object include:</p>
<div class="code spec">
request_method() -&gt; atom()
</div>
<p>Get the request method, e.g. GET, POST, etc.</p>
<div class="code spec">
query_param( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
<p>Get the value of a given query string parameter (e.g. "?id=1234")</p>
<div class="code spec">
post_param( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
<p>Get the value of a given POST parameter</p>
<div class="code spec">
deep_post_param( [ Path<span class="typevar">::string()</span> ] ) -&gt; DeepParam | undefined
</div>
<p>Get the value of a given "deep" POST parameter.
This function parses parameters that have numerical or labeled indices, such as "widget[4][name]", and returns either a value or a set of nested lists (for numerical indices) and proplists (for string indices).</p>
<div class="code spec">
header( Header<span class="typevar">::string()</span> | atom() ) -&gt; string() | undefined
</div>
<p>Get the value of a given HTTP request header. Valid values are strings or one of these atoms:</p>
<ul>
<li><code>accept</code></li>
<li><code>accept_language</code></li>
<li><code>accept_ranges</code></li>
<li><code>authorization</code></li>
<li><code>connection</code></li>
<li><code>content_encoding</code></li>
<li><code>content_length</code></li>
<li><code>content_type</code></li>
<li><code>cookie</code></li>
<li><code>host</code></li>
<li><code>if_match</code></li>
<li><code>if_modified_since</code></li>
<li><code>if_none_match</code></li>
<li><code>if_unmodified_since</code></li>
<li><code>keep_alive</code></li>
<li><code>location</code></li>
<li><code>range</code></li>
<li><code>referer</code></li>
<li><code>transfer_encoding</code></li>
<li><code>user_agent</code></li>
<li><code>x_forwarded_for</code></li>
</ul>
<div class="code spec">
cookie( Key<span class="typevar">::string()</span> ) -&gt; string() | undefined
</div>
<p>Get the value of a given cookie.</p>
{% endblock %}
Jump to Line
Something went wrong with that request. Please try again.