• log out

Pages in Qbix

Since AJAX was invented many years ago, websites have been able to update the document without completely reloading it. This leads to a much more seamless user experience.

A web app in Qbix consists of one or more "pages" on which you place tools. Even a person with little programming experience can use this to construct complex apps out of building blocks.

One of the design goals for "pages" was to promote best practices by remaining consistent with web conventions. Thus, each "page" is actually tied to a specific internal URI, which corresponds to an external URL. Navigating to a new page is as easy as calling Q.handle(url). The browser history is managed automatically by Qbix in all browsers. When a user hits the Refresh button, the currently displayed page is re-requested from the server.

There are numerous advantages to honoring web conventions. Pages can be cached properly and served via CDNs. Search engine crawlers can request each page normally, even when it's dynamically generated. And users who have javascript turned off can still browse your site.

Building pages

When designing a page, you typically will have some Javascript you want to run once it's loaded and activated. You might also want to run some other Javascript before the page is unloaded. Here is how you would do that:

Q.page("First/welcome", function () {

  // prepare the page here
	
  return function () {
    // clean things up here
  };
}, "First");

The above code simply adds handlers for two event factory events Q.Page.onActivate("First/welcome") Q.Page.beforeUnload("Q\tFirst/welcome"). The last parameter is the key under which to save the events -- this would typically begin with your app's name.

If you want to attach a handler to run for all the pages, you pass an empty string, like so:

Q.page("", function () {
  ... 
}, "First.doSomethingOnEveryPage");

Event handlers

Returning a cleanup function above is optional. Cleanup usually involves things like removing event handlers added during the page's lifetime. However, Qbix provides easier ways for you to accomplish this.

Whenever you add an event handler using event.set(handler, key) or event.add(handler, key), pass true in place of the key, and the handler will be automatically removed when the page unloads.

Qbix also overrides jQuery's on, bind and live methods to let you similarly pass true to "set it and forget it":

$('a').click(true, function () {
	
  // this handler will be detached with .off
  // automatically when the page is unloaded

}).on('mouseover', true, function () {
	
  // same thing

});

Navigating to a new page is as easy as calling Q.handle(url). Here is how you could "ajaxify" all the links in your app:

Q.handle.options.loadUsingAjax = true;

Q.page('', function () {
  $('a[data-notlink!="true"]')
  .click(true, function () {
    var url = $(this).attr('href');
    Q.handle(url, {
      quiet: true,
      loadExtras: true
    });
    return false;
  });	
});

Q.info

Q.info is an object that is updated whenever a new page is loaded (whether on the initial document load or dynamically via Javascript). Here's what this page's Q.info contains:

Q.info = {
  "app": "QP",
  "url": "http://qp.loc/guide/pages",
  "uriString": "QP/guide topic=pages",
  "uri": {
    "module":"QP",
    "action":"guide",
    "topic":"pages"
  },
  "proxies": [],
  "baseUrl": "http://qp.loc",
  "proxyBaseUrl": "http://qp.loc",
  "proxyUrl": "http://qp.loc/guide/pages",
  "sessionName": "Q_sessionId",
  "socketUrl": "http://localhost:10500",
  "slotNames": [
    "title","notices","dashboard",
    "column0","column1","column2"
  ],
};

Try viewing the source code of a webpage generated with Qbix. You will see Q.info on the bottom.

Behind the scenes

Qbix does a lot of work for you to simulate moving from one page to another. It actively swaps out stylesheets, for example, that were present in the page being unloaded but not in the one being loaded.

The methods Q.handle and (the more internal) Q.loadUrl request the corresponding URL from the server. The response looks like this:

{
  "slots": { ... },
  "stylesheets": { ... },
  "scripts": { ... },
  "scriptData": { ... },
  "scriptLines": { ... },
  "stylesInline": { ... },
  "templates": { ... }
}

When Qbix processes this response, it actually adds all the stylesheets and scripts to the document, replaces the HTML contents of the slot containers, sets any scriptData and templates that were passed, executes any scriptLines, and finally, activates the page.

Activating the page

The Q.activate() function is pretty integral to the Qbix front end. Even though it's usually called automatically, you should familiarize yourself with what it does.

Q.activate([element]) recursively walks the DOM (via a Depth-First Search) using Q.find(). When it encounters a tool, it executes that tool's constructor.

Activation is not always synchronous. Sometimes, Qbix has to go and fetch external resources, such as Javascript files, before it can run the constructors. Tools may contain other tools, and the function will construct and initialize those, too.

After Q.activate() has completed its work on the page, the Q.Page.onActivate("") event is handled, and followed by Q.Page.onActivate("Module/action") handler runs, and Q.Page.onActivate(Q.info.uriString). You can register handlers for any of these events using Q.page(...)

Generating the page

Pages are typically generated with PHP, and can be cached in content delivery networks, web browsers, or native app bundles. A social app following best practices will expect that the HTML of a page is either static, or changes very rarely. Most of the time apps can assume that Javascript is enabled, and thus all the pages and tools should fetch dynamic data using the Streams API.

When the browser first requests a page, PHP fills some slots and inserts them into a layout. The layout typically has <div> elements with the id $slotName_slot for each slot in the layout. Q.loadUrl fetches JSON data and inserts HTML in the appropriate slot containers. When Q.activate() runs, it runs constructors for all the tools in the newfound HTML, and those typically use the Streams API to fetch dynamic data.

There are times that you want some of the dynamic data — which is normally loaded by Javascript on the front end — to be available to web crawlers or browsers without Javascript enabled. If HTML is inserted into the document via a the page constructor then you can render the same HTML via PHP when implementing the page. Similarly, you can use PHP to render HTML for tools.

Although Qbix promotes using routes as the primary way to implement pages, you may for one reason or another decide to additionally use variables in the location #hash to identify a page. In this case, you are encouraged to respect the Google's specification for the escaped_fragment query field when you implement your pages and tools. Qbix supports "!#" URLs out of the box, e.g. its String.prototype.queryField function knows how to deal with them.