• log out

Various Outputs

A typical request to a web server results in some output being produced. Typing the URL into a webpage produces a GET request, submitting a form produces a POST request, while certain Javascript may cause an asynchronous "AJAX" request.

Qbix has sensible conventions for handling all these requests, and more. The first two return a full HTML page, and the last one returns JSON. You can, of course, override these conventions (if you need to), but it's much more convenient to just use what Qbix provides.

The "Q/response" event is when the output should be actually generated, printed and sent to the client. Developers are discouraged from changing server state when handling this event — the time to do that is during events such as "Q/$verb" for any HTTP verb other than GET. This encourages best practices when it comes to the HTTP specification.

Slots and Layouts

Typically, Qbix apps generate an HTML document in a two-step process:

  1. First, they fill some slots (such as dashboard, content, title)
  2. Then, they render a layout, where the contents of those slots are variables (such as $dashboard, $content, $title)

Internally, slots are filled by a call to Q_Response::fillSlot($slotName). If the slot hasn't already been filled — e.g. with a call to Q_Response::setSlot($slotName, $content) — then fillSlot invokes the slot's handler to fill it. Then, it returns the slot's contents.

This is done as follows: let's say the URI "First/foo" was requested. To fill a slot named "content", you would implement a handler for the event "First/foo/response/content". Its return value is used to fill the slot.

Default Slots

When a URL is typed into a browser's location bar (i.e. not requested through javascript), the default set of slots to fill is:

["title", "notices",
 "dashboard", "content",
 "dialogs"]

You can replace this array by setting the config field Q/response/$module/slotNames (where $module is the module the request has been routed to). If you do, you should probably also echo them in the layout, like this:

<div id="footer_slot">
  <?php echo $footer ?>
</div>

The default set of slots is also passed to the client side, to be used by functions like Q.loadUrl.

Filling Slots

By default, the Q/response/default handler fills slots, by triggering the $module/$action/response/$slotName event, which you can write a handler for, e.g.:

<?php
	
function First_welcome_response_footer()
{
  $app = Q_Config::expect('Q', 'app');
  return Q::view('$app/footer.php');
}

but you can override it by defining your own handler for the slots:

<?php
	
function Q_response_footer()
{
  // Do this for every Module and action:
  $app = Q_Config::expect('Q', 'app');
  return Q::view('$app/footer.php');
}

This is essentially what the MyApp template does. For slots such as content, that handler in turn does this:

<?php

function Q_response_content()
{
  ...
  $event = "$module/$action/response/content";
  if (!Q::canHandle($event)) {
    $event = "$app/notFound/response/content";
  }
  return Q::event($event);
}

Implementing Pages in your App

Given the above, it follows that, in a typical Qbix app, you would implement a page for the "First/welcome" internal URI simply by implementing the following event handler:

<?php
	
function First_welcome_response_content()
{
  // set up some variables
  $foo = 1;
  $bar = 2;

  // now render HTML to fill the slot:
  return Q::view(
    "First/content/welcome.php",
    compact('foo', 'bar')
  );
}

The above also shows the convention for naming view files, so they would be grouped by the slot name.

Implementing Tools

Say you want to implement a tool in your app or plugin, such as "First/chat". By default, PHP simply outputs a div and your job is to write a javascript constructor that runs when the tool is "activated". You can also implement a handler in PHP to render the tool:

<?php

/**
 * First/chat tool, for doing xyz
 * @param {array} $options ...
 */
function First_chat_tool($options)
{
  // export some or all options to the client
  Q_Response::setToolOptions($options);

  // set up some variables
  $foo = 1;
  $bar = 2;

  // now render HTML to insert into the div:
  return Q::view(
    "First/tool/chat.php",
    compact('foo', 'bar')
  );
} 

Usually you'd do this if you want to implement a tool in PHP for web crawlers and browsers that don't have Javascript enabled. Additionally, it provides developers of reusable tools the opportunity to call Q_Response::addScript(), Q_Response::addStylsheet() and other such functions, to preload these resources and avoid FOUC.

The default tool rendering handler simply outputs an empty div. If you override it with your own, and fill the div with some HTML, you will typically want to modify your JS tool constructors to check for HTML already present:

Q.Tool.define("First/chat", function () {
  ...
  
  if (tool.element.innerHTML) {
    return more();
  }
  Q.Template.render("First/chat/tool",
  function (err, html) {
    tool.element.innerHTML = html;
    more();
  });
  
  function more() {
    ...
  }
});

Rendering Tools

To render a "Streams/chat" tool to the client, you simply do

<?php echo Q::tool("Streams/chat", $options); ?>

The $options are exported to the client side as JSON in a data- attribute. When Q.activate() traverses the page, it finds this div and runs the tool's constructor, passing the options to it.

The Q::tool function also supports extra options in a third parameter. These include things like "tag" => 'span' (to change the tag on the container element or omit it altogether), "retain" => true to tell the client side to retain the tool when replacing chunks of HTML, and "replace" => true to tell the client side to replace a tool even if it was retained before.

The most major of the extra options is id — this string is used as a suffix when Qbix generates the tool's id when rendering to the client side. You are actually required to provide the id if you are rendering multiple tools with the same prefix — that is to say, next to each other. Otherwise the tool rendering will generate a PHP warning until you fix it. You can set the id in two ways:

<?php // provide id instead of array
echo Q::tool($name, $options, 'foo'); ?>

// provide it among other options
echo Q::tool($name, $options, array(
  'id' => 'foo'
  'retain' => true
));

Finally, if you want to render multiple tools on a single element, here is how you do it:

<?php echo Q::tool(array(
  'Q/tabs' => $options1,
  'Streams/related' => $options2
), 'foo');

The rendered result would be something like:

<div id="Q_tabs_foo_tool"
     class="Q_tabs_tool Streams_related_tool"
     q-tabs-tool="{...}"
     streams-related-tool="{...}">
$htmlFromQTabs
$htmlFromStreamsRelated
</div>

Views

Qbix supports a Model-View-Controller (MVC) architecture. As a result, you will typically keep separate files (in the APP_DIR/views directory) which are easy for web designers to work with.

Although it is not enforced, there is a common naming naming convention for views in Qbix. If a view is meant to be rendered in a certain slot, it should be placed in a directory named after that slot. Thus, all the views the content slot should be in the APP_DIR/views/$app/content/ directory.

Views can either have the extension .php — in which case they are executed with naked PHP — or .handlebars — in which case they are rendered with the Handlebars templating engine. You can add any other templating engine by adding a "before" hook to the "Q/view" event.

Qbix adds a couple of very useful helpers allowing you to render tools and call functions in your Handlebars templates. They are implemented in both PHP and JS so they work whether your template is for a transactional email being rendered and sent by PHP, a notification being dispatched by Node.js, or HTML to append to an element in the DOM.

Views can also be exported as client-side templates when rendering a page. By calling

Q_Response::addTemplate("First/foo", "handlebars");

the file views/First/foo.php is read and its contents are set in a front end template with the same name.

Layout

The webpage layout itself is actually just a view, and the default "Q/response" handler selects the layout based on Q_Request::isAjax().

For example, if an HTML document is requested by a browser, the APP_DIR/views/$app/layout/desktop.php layout is used by default. For tablets, it's tablet.php and for smartphones it's mobile.php . As usual, you can either override these view files in your app, or change the paths in the "Q/response/layout/..." config fields.

More about Slots

Slots are used for more than just filling out layouts in Qbix. They let you return multiple pieces of data in a single request. You can define additional, custom slots with arbitrary names. This is used very often in AJAX requests from the client to the server. For example, a client may want the server to fill and return some values in two slots, "votes" and "profileHtml". Custom slots can contain strings, arrays, or anything else.

During AJAX requests the client specifies which slots it would like. Whether the request is AJAX or not, Q_Request::slotNames() returns the list of slots to be filled.

To return content in a slot, simply implement a handler for it, as before:

<?php
	
function First_voting_response_votes()
{
  // fills votes slot in First/voting action
  $votes = First::getVotes();
  return $votes; // can be an array
}
?>

or in rare cases you might want to have the same handler for all actions:

<?php
	
function First_response_votes()
{
  $uri = Q_Dispatcher::uri();
  $module = $uri->module;
  $action = $uri->action;
  // do something special
  return $votes; // can be an array
}
?>

Alternatively, you might want to have one handler fill all slots, given a requested Module and action:

<?php
	
function First_voting_response()
{
  // let's set a bunch of slots
  $profileHtml = First::getProfileHtml();
  Q_Response::setSlot('profileHtml', $profileHtml);
  $votes = First::getVotes();
  Q_Response::setSlot('votes', $votes);
}
?>

The results are then inserted into JSON, unless you want to return custom output.

The Q_Html class

When rendering output in PHP, you would do well to make use of Q_Html functions as much as possible. They do a lot of things on behalf of your app, that not only let you interoperate with the rest of Qbix (such as doing automatic un-routing), but also help you write standards-compliant code more easily.

Here is a partial list of useful functions from Q_Html. In the list below, note that $href, $action, etc. don't have to be URLs, but can also be URI strings, such as "First/welcome".

// escape literal text in HTML
Q_Html::text($str);

// outputs a link to URI or URL
Q_Html::a($uri, $attributes = array());

// output a form with action URI or URL
Q_Html::form($action, 
  $method = 'post', 
  $attributes = array()
);

// output standard Qbix fields inside a form
Q_Html::formInfo($onSuccess);

// output an image, src relative to theme url
Q_Html::img($src,
  $alt='image',
  $attributes = array()
);

// output a div
Q_Html::div($id, $class, $attributes = array());

// output an arbitrary HTML tag
Q_Html::tag($tagname, $attributes = array());

// output an inline script inside HTML
Q_Html::script($contents);

Most of the tags above output an opening tag, which you would then follow by some HTML and then a closing tag. For example:

<?php echo Q_Html::a('First/welcome') ?>cool</a>

However, you can usually pass the content of the tag as its final parameter

<?php echo Q_Html::a('First/welcome', null, 'cool') ?>

You can also output a self-closing tag like this:

<?php echo Q_Html::a('script', null, true)

Also, a tip: use json_encode($something) to prepare variables for output in javascript. Doing this will be effective even for escaping strings. You will then have to run the result through Q_Html::text($result) to output it in an HTML document.

Scripts and stylesheets

When filling a slot, you will often want to reference javascripts and stylesheets. You can easily do this by using calls like the following:

Q_Response::addStylesheet('css/foo.js');
Q_Response::addScript('js/foo.js');
Q_Response::addScript(
  'http://yahoo.com/yui/CoolBar.js'
);

Qbix translates strings like "js/foo.js" into absolute urls using Q_Html::getThemedUrl($local).

Although it is discouraged, you can also add dynamically-generated javascript lines and stylesheet rules to be rendered directly inside the document, as follows:

Q_Response::setStyle(
  'p, div.foo', array('background' => 'red')
);
Q_Response::addScriptLine(
  "document.location = $new_location_json;"
);

All these functions also take a $slotName as an optional second parameter. If you don't specify it, an intelligent default is used. You can also pass "" to mean that these resources will not "belong" to any slot.

When the Qbix front end simulates switching pages, it will swap out any scripts, stylesheets, and inline styles belonging to a slot that is being reloaded. That means a script that is attached to a particular slot being reloaded might be run again, whereas a script attached to "" (no slot) will run only once.

Passing data to the client

One way to safely pass data to the client is as follows:

 Q_Response::setScriptData(
  "First.foo.bar", $value
);

This sets some global variables in the document. When you are rendering tools on a page, options passed to these tools are another way to pass data, which avoids the need to set global variables.

Setting other things

The Q_Response class contains more methods for customizing output in the client.

Q_Response::setMeta($name, $content);
Q_Response::setNotice($key, $notice, $transient);

You should use these to set any meta tags or add notices to be displayed on the page.

When you are validating input on a request, you can report errors to the client in two ways. One is by throwing an exception. However, during validation you can also report multiple errors to the client like this:

Q_Response::addError($exception);

This is useful, for example, when you are validating a form. In fact, the Q/form tool works with this to report the errors next to the appropriate fields when Javascript is enabled, and otherwise the errors are rendered in the notices slot.

Redirecting

To redirect to another URL or URI, call Q_Response::redirect($uri). For example, you can do

Q_Response::redirect('First/welcome')

You should typically do this before the "Q/response" event -- which is expected to start printing output -- since headers can't be set after that.

A typical time to redirect is when a POST request succeeded. (That way, refreshing the page will not cause the browser to attempt the POST request again.) However, Qbix does this automatically through a convention: the Q_Request::special('onSuccess', ...) field is automatically checked by the default "Q/response" handler -- and if it not empty, Qbix redirects there (if the Q_Response::getErrors() array was empty). This is explained further in the "Forms and Tools" article.

Custom output

Normally, Qbix takes care of the response by filling slots in a layout, or perhaps assembling them in a JSON response. However, if you want to generate all the output yourself, you can do it very simply:

<?php
	
function First_voting_response()
{
  // when it's time to generate a response
  // for a request to First/voting endpoint
  echo json_encode(array(
    'errors' => array('blablabla!')
  ));
  return false; // we already did it ourselves
}
?>

You can also play around with the buffering settings. By default, Qbix buffers your output, until the Q/response event concludes. You can set a buffer handler to gzip the output by calling Q_Response::isBuffered('gzip'). Or you can turn output buffering off altogether calling Q_Response::isBuffered(false). This might be useful for results that are designed to load slowly over time. For example, "Comet" is a technique that uses long-polling for pushing information over HTTP, and can benefit from this.

Themes

Qbix has support for dynamic theming. For example, your app can give each user the ability to customize the appearance of the site. The way this is achieved is through a "theme cascade".

To add a theme directory to the cascade at runtime, you call Q_Html::pushThemeUrl($theme_url). From then on, the function Q_Html::themedUrl($src) checks this directory before trying previous directories. If the specified $src exists relative to a particular theme directory, the resulting URL is used for the src attribute.

Let's illustrate with some examples:

Q_Html::pushThemeUrl('');
Q_Html::pushThemeUrl('themes/red'); // red theme

// if themes/red/img/foo.jpg exists, we use that
// otherwise, use img/foo.jpg
Q_Html::img('img/foo.jpg');

// if themes/red/js/foo.js exists, use that
// otherwise, use js/foo/js
Q_Response::addScript('js/foo.js');

Since adding a theme causes Qbix to check whether files exist at request-time, if you're going to use themes, we strongly recommend using APC with the stat option set to off.

Among other things, themes can be used for versioning files, which we will talk about in the Cordova section.