Commits

Kirill Simonov committed 64aaf50

Added sphinx-based documentation.

Comments (0)

Files changed (16)

-.PHONY: demo dist
-
-# The connection URI for regression databases.
-SQLITE_REGRESS_DB?=sqlite:///build/regress/regress-sqlite/htsql_regress.sqlite
-PGSQL_ADDRESS?=${PGHOST}$(if ${PGPORT},:${PGPORT})
-PGSQL_REGRESS_DB?=pgsql://htsql_regress:secret@${PGSQL_ADDRESS}/htsql_regress
-
-# The HTTP server address.
-HTSQL_HOST?=localhost
-HTSQL_PORT?=8080
+.PHONY: build dist doc clean
 
 JQUERY_VER=1.5.1
 JQUERYUI_VER=1.8.13
 
 JSMIN=python -c 'import sys, jsmin; sys.stdout.write(jsmin.jsmin(sys.stdin.read()))'
 
-demo:
-	python demo/serve.py ${PGSQL_REGRESS_DB} ${HTSQL_HOST} ${HTSQL_PORT}
-
-
 build/json-js:
 	mkdir -p build/json-js
 	git clone https://github.com/douglascrockford/JSON-js build/json-js
 	tar -xz -C build/jqplot -f build/jquery.jqplot.${JQPLOT_VER}.tar.gz
 	rm build/jquery.jqplot.${JQPLOT_VER}.tar.gz
 
-build/htraf: build/json-js build/jquery build/jquery-ui build/blockui build/jqplot
+build/htraf: build/json-js build/jquery build/jquery-ui build/blockui build/jqplot doc
 	mkdir -p build/htraf
-	cp htraf/htraf.js build/htraf
-	cp htraf/jquery.htraf.js build/htraf
-	cp htraf/htraf.plugins.js build/htraf
-	cp htraf/htraf.css build/htraf
-	cp htraf/htraf-load.gif build/htraf
+	cp src/htraf.js build/htraf
+	cp src/jquery.htraf.js build/htraf
+	cp src/htraf.plugins.js build/htraf
+	cp src/htraf.css build/htraf
+	cp src/htraf-load.gif build/htraf
 	mkdir -p build/htraf/lib
 	cat build/json-js/json2.js | ${JSMIN} > build/htraf/lib/json2.min.js
 	cp build/jquery/jquery.min.js build/htraf/lib/jquery.min.js
 	cp build/jqplot/dist/plugins/*.min.js build/htraf/lib
 	cp build/jqplot/dist/jquery.jqplot.min.css build/htraf/lib
 
-dist:
-	rm -rf build
-	${MAKE} build/htraf
+doc:
+	mkdir -p build
+	sphinx-build -b html doc build/htraf/doc
+
+build:
+	${MAKE} clean build/htraf
+
+dist: build
 	cd build; zip -rq htraf-bundle.zip htraf
 
+clean:
+	rm -rf build
+
+# -*- coding: utf-8 -*-
+#
+# HTRAF documentation build configuration file, created by
+# sphinx-quickstart on Mon Jun 13 16:25:54 2011.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.join(os.path.abspath('.'), 'extensions'))
+
+# -- General configuration -----------------------------------------------------
+
+# Set when building documentation for htsql.org.
+build_website = False
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.ifconfig',
+              'sphinxext_htsqldoc', 'sphinxext_htrafdemo']
+
+# The default URL of an HTSQL service.
+htsql_server = 'http://demo.htsql.org'
+
+# Add any paths that contain templates here, relative to this directory.
+#templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'HTRAF'
+copyright = (u'2006-2011 Prometheus Research, LLC;'
+              ' written by Oleksiy Golovko and Owen McGettrick')
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '2.0'
+# The full version, including alpha/beta/rc tags.
+release = '2.0.0b1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = 'obj'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'HTRAFdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'HTRAF.tex', u'HTRAF Documentation',
+   u'2006-2011 Prometheus Reserch', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'htraf', u'HTRAF Documentation',
+     [u'2006-2011 Prometheus Research'], 1)
+]

doc/extensions/sphinxext_htrafdemo/__init__.py

+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+from sphinx.util.osutil import copyfile
+
+from urllib import urlopen
+import os.path
+
+
+JQUERY_URL = "http://code.jquery.com/jquery-1.5.2.min.js"
+
+
+class DemoDirective(Directive):
+
+    has_content = True
+    option_spec = {
+            'source': directives.flag,
+            'hide': directives.flag,
+    }
+
+    def run(self):
+        wrapper = nodes.compound(classes=['demo-block'])
+        data = "\n".join(self.content)
+        demo = nodes.raw(data, data,
+                         format='html',
+                         classes=['demo-area'])
+        source = nodes.literal_block(data, data,
+                                     language='html',
+                                     classes=['demo-source'])
+        switch = nodes.literal(classes=['demo-switch'])
+        if 'source' not in self.options:
+            if 'hide' not in self.options:
+                switch += nodes.Text("[- view source]")
+            else:
+                switch += nodes.Text("[+ view source]")
+                source['classes'].append('demo-hide')
+            wrapper += demo
+            wrapper += switch
+            wrapper += source
+        else:
+            if 'hide' not in self.options:
+                switch += nodes.Text("[- view demo]")
+            else:
+                switch += nodes.Text("[+ view demo]")
+                demo['classes'].append('demo-hide')
+            wrapper += source
+            wrapper += switch
+            wrapper += demo
+        return [wrapper]
+
+
+def copy_static(app, exception):
+    if app.builder.name != 'html' or exception:
+        return
+    src_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static')
+    dst_dir = os.path.join(app.builder.outdir, '_static')
+    for filename in os.listdir(src_dir):
+        src = os.path.join(src_dir, filename)
+        if not os.path.isfile(src):
+            continue
+        dst = os.path.join(dst_dir, filename)
+        copyfile(src, dst)
+    data = urlopen(JQUERY_URL).read()
+    open(os.path.join(dst_dir, 'jquery.js'), 'w').write(data)
+
+
+def setup(app):
+    app.add_directive('demo', DemoDirective)
+    app.connect('build-finished', copy_static)
+    app.add_stylesheet('htrafdemo.css')
+    app.add_javascript('htrafdemo.js')
+
+

doc/extensions/sphinxext_htrafdemo/static/htrafdemo.css

+
+div.demo-block {
+  margin: 1em 0 0;
+}
+
+div.demo-block tt.demo-switch {
+  cursor: pointer;
+  font-size: 95%;
+  background-color: inherit;
+}
+
+div.demo-block div.demo-area {
+/*  background-color: yellow; */
+}
+
+/* div.demo-block div.demo-source {
+  display: none;
+}*/
+
+div.demo-block div.demo-hide {
+  display: none;
+}
+
+.demo-area h1,
+.demo-area h2,
+.demo-area h3 {
+  color: #900;
+}
+
+.demo-area em.htraf,
+.demo-area strong.htraf {
+  color: #369;
+}
+
+.demo-area table.htraf {
+	border-collapse: collapse;
+	border-spacing: 0;
+  border-left: 2px solid #ddd;
+  border-right: 2px solid #ddd;
+  border-bottom: 2px solid #ddd;
+}
+
+.demo-area table.htraf th,
+.demo-area table td {
+  padding: 4px 6px;
+  font-size: 90%;
+  vertical-align: top;
+}
+
+.demo-area table.htraf th {
+  background: #ddd;
+  font-weight: bold;
+  color: #555;
+}
+
+.demo-area table.htraf td {
+  border-right: 1px solid #ddd;
+  border-bottom: 1px solid #ddd;
+}
+
+.demo-area ul.htraf,
+.demo-area ol.htraf {
+  background: #ddd;
+}
+
+.demo-area ul.htraf li,
+.demo-area ol.htraf li {
+  margin: 0;
+}
+
+.demo-area .htraf-hover {
+  background: #f6ddaf;
+}
+
+.demo-area .htraf-selected {
+  background: #ffa43f;
+}
+

doc/extensions/sphinxext_htrafdemo/static/htrafdemo.js

+
+
+
+$(function () {
+    $('.demo-switch').click(function () {
+        var text = $(this).text()
+        if (text == '[+ view source]') {
+            $(this).next('.demo-source').slideDown('fast');
+            $(this).text('[- view source]');
+        }
+        else if (text == '[- view source]') {
+            $(this).next('.demo-source').slideUp('fast');
+            $(this).text('[+ view source]');
+        }
+        else if (text == '[+ view demo]') {
+            $(this).next('.demo-area').slideDown('fast');
+            $(this).text('[- view demo]');
+        }
+        else if (text == '[- view demo]') {
+            $(this).next('.demo-area').slideUp('fast');
+            $(this).text('[+ view demo]');
+        }
+    });
+});
+
+
+*******************
+  HTRAF Reference
+*******************
+
+HTRAF is a toolkit for embedding data into HTML pages.  HTRAF lets an
+HTML designer to associate certain HTML elements with data sources; then,
+when the page is opened in a browser, HTRAF automatically fetches the
+data from the database and populates the selected elements.
+
+HTRAF is based upon JQuery_ Javascript framework and relies on an HTSQL_
+service to retrieve data from the database.
+
+.. _HTSQL: http://htsql.org/
+.. _JQuery: http://jquery.org/
+
+HTRAF is written by Oleksiy Golovko and Owen McGettrick and released
+under the same licenses as JQuery_.
+
+
+Overview
+========
+
+Extracting and presenting data from a relational database is one of
+the most common tasks in web development.  The usual approach splits
+this task into several *tiers*:
+
+* a database tier that stores the data;
+* a middleware tier is a server side application that retrieves data
+  from the database and renders it into HTML;
+* a presentation tier is a web browser that displays the rendered
+  page to the users.
+
+While powerful and generic, this approach is quite heavyweight.  HTSQL
+and HTRAF radically simplify it by eliminating the middleware tier;
+instead you embed the data from the database directly to an HTML page.
+
+Take the following use case: allow a user to select a *school* from
+a drop-down list, then, for the selected school, display associated
+*departments* together with the *number of courses* offered by each
+department.
+
+(In all examples below, we use a sample database of a student enrollment
+system in a fictional university.  The database contains schools,
+programs administered by a school, departments associated with a school,
+and courses offered by a department.)
+
+Here is how we implement this use case with HTRAF:
+
+.. demo::
+   :source:
+   :hide:
+
+    <select id="school_1"
+        data-htsql="/school{code, name}?exists(department)">
+    </select>
+
+    <table
+        data-htsql="/department{name, count(course)}?school.code=$school_1"
+        data-ref="school_1">
+    </table>
+
+This HTML fragment contains two elements: ``<select>`` and ``<table>``
+which display a drop-down list of schools and a list of associated
+departments respectively.  The elements (we call them *widgets*) are
+empty, but have some extra attributes.
+
+The ``data-htsql`` attribute contains an HTSQL query; it instructs
+HTRAF to execute the query and use the result to populate the content
+of the widget.  Take a look at the output of the query:
+
+.. htsql:: /school{code, name}?exists(department)
+   :cut: 4
+
+HTRAF renders this output into the following HTML code:
+
+.. sourcecode:: html
+
+    <select id="school_1">
+        <option value="art">School of Art and Design</option>
+        <option value="bus">School of Business</option>
+        <option value="edu">College of Education</option>
+        <option value="eng">School of Engineering</option>
+        ...
+    </select>
+
+The ``<table>`` widget is more interesting.  To indicate that the
+``<table>`` widget depends on the ``<select>`` list and should be
+updated when some row in the list is selected, we assign anchor
+``id="school_1"`` to the ``<select>`` element and add attribute
+``data-ref="school_1"`` to ``<table>``.  The selected value is available
+in HTSQL under the name ``$school_1``.
+
+For example, if the user selects *School of Engineering* in the
+drop-down list, then to update the linked table, HTRAF will execute the
+query:
+
+.. htsql::
+   :cut: 4
+
+    /department{name, count(course)}?school.code=$school_1
+     :where $school_1 := 'eng'
+
+For more information on HTSQL syntax and semantics, see `HTSQL Tutorial
+<http://htsql.org/doc/tutorial.html>`_; this document describes how to use
+HTRAF toolkit to embed results of HTSQL queries into HTML pages.
+
+Prerequisites
+=============
+
+HTRAF uses an HTSQL service to retrieve data from the database; therefore, in
+order to use HTRAF, you need to install HTSQL and deploy it as a web service
+against your database.  See `HTSQL Installation Guide
+<http://htsql.org/doc/install.html>`_ for more details.
+
+It is strongly recommended to configure the HTTP server to serve both
+HTSQL service and HTML pages from the same domain; otherwise browser
+security settings would prevent HTRAF from accessing HTSQL service.
+That could be circumvented by using CORS on the HTSQL service, but note
+that not all browsers support CORS.  For more details on CORS, see `CORS
+W3C specification`_.
+
+.. _CORS W3C specification: http://www.w3.org/TR/cors/
+
+Then download and install HTRAF.  HTRAF is a pure Javascript toolkit, so
+simply unpack the archive to where you keep other static data for your
+website.
+
+To start using HTRAF, include the script ``htraf.js`` to your HTML
+pages.
+
+.. ifconfig:: build_website
+
+    .. demo::
+       :source:
+       :hide:
+
+        <script type="text/javascript"
+            src="/htraf/htraf.js"
+            data-htsql-version="2"
+            data-htsql-prefix="/@demo">
+        </script>
+
+.. ifconfig:: not build_website
+
+    .. demo::
+       :source:
+       :hide:
+
+        <script type="text/javascript"
+            src="../htraf.js"
+            data-htsql-version="2"
+            data-htsql-prefix="http://demo.htsql.org">
+        </script>
+
+In addition to regular ``<script>`` attributes, we added two extra
+attributes:
+
+`data-htsql-prefix` (absolute or relative URL)
+    This specifies the root of the HTSQL service; in the example above,
+    HTSQL service is located at http://htsql.org/@demo.
+
+    Note that the URL should not include a trailing slash.
+
+`data-htsql-version` (``1`` or ``2``)
+    The major version of HTSQL; currently the only meaningful value
+    is ``2``.  (HTSQL 1 is used for in-house projects of `Prometheus
+    Research`_.)
+
+.. _Prometheus Research: http://prometheusresearch.com/
+
+
+Widgets
+=======
+
+HTML elements controlled by HTRAF are called *widgets*.  HTRAF supports
+a number of different widgets: drop-down and regular lists, tables,
+charts, and also provides an API to define new widget types.
+
+Widget elements should not contain any subelements since the content of
+the widget is populated and maintained by HTRAF.
+
+Linking
+-------
+
+Common Attributes
+-----------------
+
+A widget may possess the following attributes: 
+
+`id` (a unique identifier)
+    Indicates the name of the widget, must be unique across the whole
+    HTML page.  The name is used when declaring widget dependencies,
+    see description of `data-ref` attribute for more details.
+
+`data-widget` (widget type: ``select``, ``table``, ``chart``, etc)
+    HTRAF determines the type of the widget from the value of this
+    attribute.  In the absense of `data-widget`, HTRAF assumes the type
+    coincides with the tag name.  Conveniently, commonly used widget
+    types are called ``select``, ``table``, ``ul``, etc., so often there
+    is no need to specify the widget type explicitly.
+
+`data-htsql` (an HTSQL query)
+    HTSQL query executed to populate the widget.  This is a mandatory
+    attribute since it used by HTRAF to find widgets on the page.
+
+`data-ref` (list of widget identifiers)
+    If present, indicates that the content of the widget depends on
+    other widgets. 
+
+Common Classes
+--------------
+
+`htraf-selected`
+
+`htraf-hover`
+
+
+Now suppose you conjured an HTSQL query that generates the output
+you want and now you want to embed the output to the page.
+
+Let's take the query:
+
+.. htsql:: /department{name, count(course)}?school.code='eng'
+
+That produces the number of course per department in the school
+of *Engineering*.  The following code utilizes HTRAF to embed
+the output of the query as a table:
+
+.. demo::
+   :source:
+   :hide:
+
+    <table data-htsql="/department{name, count(course)}?school.code='eng'">
+    </table>
+
+In this HTML fragment, attribute ``data-htsql`` instructs HTRAF
+to execute the given query and populate the table with the query
+output.
+
+HTRAF is not limited to tables, it could also produce lists,
+charts, etc.  You can also extend HTRAF with your own widgets.
+
+For instance, the following code uses HTRAF to populate a select
+list.
+
+.. demo::
+   :source:
+   :hide:
+
+    <select data-htsql="/school{code, name}?exists(department)">
+    </select>
+
+In this case, the query
+
+.. htsql:: /school{code, name}?exists(department)
+   :cut: 3
+
+lists the code and the name of schools that have at least one
+associated department.  The first column ``code`` is used to populate
+the ``value`` of the select list, the second column ``name`` populates
+the content of the select list.
+
+HTRAF allows you to bind elements together so that selecting
+a value in one element updates another element.  For example,
+we could bind the two elements above so that a choice done
+in a select list updates the table content.
+
+Note the extra attribute `data-ref` on the table widget.  It indicates
+that the widget uses the data from another widget called ``school``
+and should be updated whenever the ``school`` widget is updated.
+
+
+Widgets
+=======
+
+HTML elements that display data are called *HTRAF widgets*.  HTRAF
+recognizes widgets by presence of attribute `data-htsql`.
+
+HTRAF supports a numerous types of widgets and allows one to extend it
+with custom widgets.  The type of a widget is determined as follows:
+
+* The value of attribute `data-widget` if it is set.
+* Otherwise, the name of the tag; if it is a valid widget name.
+* Otherwise, use *singleValue* --- the default widget.
+
+In the following example, the type of the widget is determined
+from value of ``data-widget``:
+
+.. demo::
+   :source:
+   :hide:
+
+    <div width="500" height="400"
+        data-widget="chart"
+        data-htsql="/department{name, count(course)}?school.code='eng'">
+    </div>
+
+
+HTRAF takes over the widget content, preserving the tag itself,
+but replacing the content with generated data.
+
+Some widgets (such as table or select list) allows one to select
+a row in the output.
+
+
+
+Table Widget
+============
+
+Probably the most common widget in HTRAF, *table* renders data using
+HTML ``<table>`` element.
+
+The *table* widget is selectable, when selected, it emits the value
+from the first output column.
+
+Example
+-------
+
+.. demo::
+   :source:
+
+    <table data-htsql="/school">
+    </table>
+
+Attributes
+----------
+
+`data-hide-column-0` (``true``)
+    Do not display the first output column.
+
+    Useful when the first column contains a value of the key to pass to
+    dependent widgets, but the the value itself is not needed in the
+    output.
+
+    Example:
+
+    .. demo::
+       :source:
+       :hide:
+
+        <h3>Select a School</h3>
+        <table id="school_2"
+            data-htsql="/school{code, name}?exists(department)"
+            data-hide-column-0="true">
+        </table>
+
+        <h3>Associated Departments</h3>
+        <table
+            data-htsql="/department{name}?school.code=$school_2"
+            data-ref="school_2">
+        </table>
+
+    Even though the query produces two columns, the first column is
+    hidden from the table.  It is still used to pass the school code to
+    the dependent widget.
+
+CSS Classes
+-----------
+
+`rN` (*N* is integer starting from *0*)
+    Set on *N*-th row (``<tr>``) element of the widget.
+
+`cN` (*N* is integer starting from *0*)
+    Set on each *N*-th column (``<td>``) element of the widget.
+
+`even`, `odd`
+    Set on each even/odd row element.
+
+
+Select Widget
+=============
+
+The *select* widget presents data in the form of a drop-down menu using
+a ``<select>`` element.
+
+The associate HTSQL query should produce two or one columns.  When the
+output has two columns, they are used to populate the value and the label
+of each option; if only one column is available, it is used to populate
+both the value and the label.
+
+Example
+-------
+
+.. demo::
+   :source:
+   :hide:
+
+    <select data-htsql="/school{code}">
+    </select>
+
+    <select data-htsql="/school{code, name}">
+    </select>
+
+Attributes
+----------
+
+`multiple`
+    The select widget admits multiple selections.  When more then one
+    choice is selected, the values are passed to the dependent widgets
+    as a selector.
+
+    Example:
+
+    .. demo::
+       :source:
+       :hide:
+
+        <select id="school_3" size="4" multiple
+            data-htsql="/school{code, name}?exists(department)">
+        </select>
+
+        <table
+            data-htsql="/department{name}?school.code=$school_3"
+            data-ref="school_3">
+        </table>
+
+UL and OL Widgets
+=================
+
+The *ul* and *ol* widgets present data in a form of an unordered and
+ordered lists using ``<ul>`` and ``<ol>`` HTML elements.
+
+Just like select widgets, the ul and ol widgets accept input data with
+one or two columns; when two columns are given, the second column is
+used to populate the content of each entry.
+
+Example
+-------
+
+.. demo::
+   :source:
+
+    <ul data-htsql="/school{code}?exists(department)">
+    </ul>
+
+    <ol data-htsql="/school{code, name}?exists(department)">
+    </ol>
+
+
+IFrame Widget
+=============
+
+The *iframe* widget embeds the response from the HTSQL server into a
+frame.  This is the only widget that does not render JSON data, but
+relies on HTSQL server to send formatted output in HTML.
+
+Example
+-------
+
+.. demo::
+   :source:
+
+    <iframe width="600" height="300"
+        data-htsql="/school{code, name}?exists(department)">
+    </iframe>
+
+
+Chart Widget
+============
+
+The *chart* widget presents data in a graphical form.  The first column
+of the output specifies the chart labels, the remaining columns specify
+the respective values.
+
+Since HTML does not have a ``<chart>`` element, you need to specify the
+widget type using ``data-widget`` attribute.
+
+Example
+-------
+
+.. demo::
+   :source:
+
+    <div style="width: 700px; height: 350px"
+        data-widget="chart"
+        data-yint="true"
+        data-title="Number of Departments by School"
+        data-htsql="/school{code,
+                            num_dept := count(department)}
+                           ?num_dept>0">
+    </div>
+
+Chart Types
+-----------
+
+The widget provides several types of charts: bar chart, pie chart and
+line chart.  The chart type is specified using `data-type` attribute.
+
+Bar Chart
+~~~~~~~~~
+
+To display a bar chart, the input data should contain two or more
+columns: the first column contains the labels, the subsequent
+columns are numeric values.  Each value is represented by the
+height of a rectangular bar.
+
+.. demo::
+   :source:
+   :hide:
+
+    <div style="width: 345px; height: 325px"
+        data-widget="chart"
+        data-type="bar"
+        data-yint="true"
+        data-title="Bar Chart"
+        data-htsql="/school{code,
+                            num_dept := count(department),
+                            num_prog := count(program)}
+                           ?num_dept>2&num_prog>2">
+    </div>
+
+Stacked Bar Chart
+~~~~~~~~~~~~~~~~~
+
+Same as the regular bar chart except that when the data contain two
+or more value columns, the respective bars are stacked in a single
+line.
+
+.. demo::
+   :source:
+   :hide:
+
+    <div style="width: 345px; height: 325px"
+        data-widget="chart"
+        data-type="stack"
+        data-yint="true"
+        data-title="Stacked Bar Chart"
+        data-htsql="/school{code,
+                            num_dept := count(department),
+                            num_prog := count(program)}
+                           ?num_dept>2&num_prog>2">
+    </div>
+
+Pie Chart
+~~~~~~~~~
+
+For a pie chart, the input data should contain two columns: the
+labels and respective numeric values.
+
+.. demo::
+   :source:
+   :hide:
+
+    <div style="width: 345px; height: 325px"
+        data-widget="chart"
+        data-type="pie"
+        data-yint="true"
+        data-title="Pie Chart"
+        data-htsql="/school{code,
+                            num_dept := count(department)}
+                           ?num_dept>2">
+    </div>
+
+Line Chart
+~~~~~~~~~~
+
+To generate a line chart, the input data should contain two or more
+columns: the first column contains (numeric or date) values for the
+*X* axis, the rest contain numeric values for the *Y* axis.
+
+.. demo::
+   :source:
+   :hide:
+
+    <div style="width: 345px; height: 325px"
+        data-widget="chart"
+        data-type="line"
+        data-yint="true"
+        data-title="Line Chart"
+        data-htsql="/(school^{num_dept := count(department)})
+                            {num_dept,
+                             num_school := count(school),
+                             num_prog := count(school.program)}">
+    </div>
+
+Attributes
+----------
+
+`style`
+
+`data-type` (``bar`` (default), ``stack``, ``pie``, ``line``)
+
+`data-legend` (``ne`` (default), ``se``, ``nw``, ``sw``, ``no``)
+
+`data-title` (a string)
+
+`data-show-title` (``true`` (default) or ``false``)
+
+`data-yint` (``true`` or ``false`` (default))
+
+`data-x-vertical` (``true`` or ``false`` (default))
+
+
+
+
+singleValue
+===========
+
+This is a fall-back widget, it is used when the actual widget type is not found.
+The widget replaces the content of the tag with the value from the first row and the
+first column of the output.
+
+Example
+-------
+
+.. demo::
+   :source:
+
+    <p>The database contains
+    <strong data-htsql="/count(school)"></strong>
+    records in the <em>school</em> table and
+    <strong data-htsql="/count(department)"></strong>
+    records in the <em>department</em> table.</p>
+
+
+Common Attributes
+=================
+
+`data-htsql`
+
+`data-widget`
+
+`data-onchange`
+
+`data-onerror`
+
+`data-empty`
+
+`data-onbeforeload`
+
+`data-onafterload`
+
+

htraf/htraf-load.gif

Removed
Old image

htraf/htraf.css

-.htraf-selected {
-    background: #888888;
-}
-
-.htraf-hover {
-    background: #DDDDDD;
-}

htraf/htraf.js

-(function() {
-
-var attrs = null, scripts = document.getElementsByTagName('script');
-function getAttr(attr, collection) {
-    var c = collection || attrs;
-    if(!c)
-        return null;
-    var attr = c.getNamedItem(attr);
-    return attr ? attr.nodeValue:null;
-}
-
-for(var i = 0, l = scripts.length; i < l; i++) {
-    var src = getAttr('src', scripts[i].attributes);
-    if(src && /(^|\/)htraf.js$/.test(src)) {
-        attrs = scripts[i].attributes;
-        break;
-    } 
-}
-
-var prefix = getAttr('src').replace(/(^|\/)htraf\.js$/, ''),
-    _jqueryVersion = (window.$ || function() {return {}}).call().jquery 
-                     || '0.0.0';
-if(_jqueryVersion < '1.5.1')
-    document.write('<script type="text/javascript" src="' 
-        + prefix + '/lib/jquery.min.js"></script>');
-
-if(!(window.JSON && window.JSON.parse && window.JSON.stringify))
-    document.write('<script type="text/javascript" src="' 
-        + prefix + '/lib/json2.min.js"></script>');
-
-document.write('<script type="text/javascript" src="' 
-    + prefix + '/lib/jquery.blockUI.min.js"></script>');
-
-if(!window.$ || !window.$.Widget || !window.ui || !window.ui.position)
-    document.write('<script type="text/javascript" src="' + prefix 
-        + '/lib/jquery-ui.custom.min.js"></script>');
-
-// HTRAF files
-document.write('<script type="text/javascript" src="' 
-    + prefix + '/jquery.htraf.js"></script>');
-document.write('<link rel="stylesheet" type="text/css" href="'
-    + prefix + '/htraf.css"/>');
-
-})();

htraf/htraf.plugins.js

-(function($, undefined) {
-
-$.htraf.plugin.table = {
-    preBeforeLoad: function() {
-    },
-
-    postBeforeLoad: function() {
-    
-    },
-
-    preAfterLoad: function() {
-    },
-
-    postAfterLoad: function() {
-        var $el = this.element,
-            data = this.data;
-
-        /* 
-         * data structure is:
-         * {
-         *   headers: [{title: '', domain: (number, string, date etc)}, ... {}]
-         *   data: [[value1, value2, ..., valueN], ..., []]
-         * }
-         *
-         */
-
-        // Highlighting
-        function evalExpr(expr, row) {
-            var vars = [];
-            $.each(data.headers, function(i, header) {
-                if(/^[a-z_]\w+$/i.test(header.title))
-                    vars.push(header.title +  '=' + JSON.stringify(row[i])); 
-            });
-            var code = 'var ' + vars.join(',') + ';'
-                + 'try { return (' + expr + '); }' 
-                + 'catch(_) { return false; };';
-            return (new Function(code)).call();
-        }
-
-        if($el.attr('data-rowhighlight-condition')) {
-            var expr = $el.attr('data-rowhighlight-condition'),
-                cls = $el.attr('data-rowhighlight-class') 
-                      || 'htraf-row-highlight';
-            $el.find('tr').each(function(i) {
-                if(evalExpr(expr, data.data[i]))
-                    $(this).addClass(cls);
-                else
-                    $(this).removeClass(cls);
-            });
-        }
-
-        // Convert table columns of named like_to_ into links when
-        // data values are of form link_name|link_href
-        $el.find("th").each(function(i) {
-                      
-            if ( $(this).text().indexOf('link_to_') != -1 )
-            {
-                $(this).text($(this).text().substring($(this).text().indexOf('link_to_')+8));
-                selector = "td:nth-child("+(i+1)+")";
-                $el.find(selector).each(function(e) {
-                    if ( $(this).text().indexOf('|') != -1 )
-                    {
-                        $(this).html('<a href="'+$(this).text().substring($(this).text().indexOf('|')+1)+'">'+$(this).text().substring(0,$(this).text().indexOf('|'))+'</a>');
-                    }
-                });
-            }
-        });
-
-        
-    }
-};
-
-
-})(jQuery);
-
-
-
-
-// Misc functions
-
-
-// hide column in tables
-function hideCol(id,col)
-{ 
-    selector = "#" + id + ' td:nth-child('+col+')';
-    $(selector).hide();
-    selector = "#" + id + ' th:nth-child('+col+')';
-    $(selector).hide();
-}
-    
-// right align column in tables
-function alignCol(id,col)
-{ 
-    selector = "#" + id + ' td:nth-child('+col+')';
-    $(selector).css("text-align","right");
-    selector = "#" + id + ' th:nth-child('+col+')';
-    $(selector).css("text-align","right");
-}
-
-
-// Add column totals to tables
-function colTotals(id,cols)
-{
-
-    selector = "#" + id + " tbody tr";
-    for (i in cols)
-    {
-        tot = 0;
-        dollars = false;
-        $(selector).children("td:nth-child(" + cols[i] + ")")
-        .each(function() {
-            if ($(this).html().indexOf('$')>=0)
-                dollars = true;
-            fl = isFloat( $(this).html().replace('$','').replace(/,/g,''));
-            if ( fl ) {
-                tot += parseFloat($(this).html().replace('$','').replace(/,/g,''));
-            } else {
-                tot += parseInt($(this).html().replace('$','').replace(/,/g,''));
-            }
-        });
-        
-        tot = (isFloat(tot)) ? tot.toFixed(2) : tot;
-        tot = addCommas(tot);
-        if (dollars)
-            tot = '$'+tot;
-
-        selector2 = "#" + id + " tfoot th:nth-child(" + cols[i] + ")";
-        alert(tot);
-        $(selector2).html(tot);
-
-    }
-
-}
-
-// used by colTotals
-function isFloat(x)
-{
-    if (x == parseInt(x) && x == parseFloat(x)) 
-    {
-        return false;
-    } 
-    else if (x == parseFloat(x)) 
-    {
-        return true;
-    } 
-    else 
-        return false;
-}
-
-// used by colTotals
-function addCommas(nStr)
-{
-	nStr += '';
-	x = nStr.split('.');
-	x1 = x[0];
-	x2 = x.length > 1 ? '.' + x[1] : '';
-	var rgx = /(\d+)(\d{3})/;
-	while (rgx.test(x1)) {
-		x1 = x1.replace(rgx, '$1' + ',' + '$2');
-	}
-	return x1 + x2;
-}
-
-// End add column totals to tables
-
-
-

htraf/jquery.htraf.js

-(function($, undefined) {
-
-// {{{ Setting up the base htraf parameters
-var HTRAF = window.HTRAF = {},
-    selector = $.map(['htraf.js', 'jquery.htraf.js'], function(f) {
-        return 'script[src="' + f + '"],script[src$="/' + f + '"]';
-    }).join(','),
-    $script = $(selector);
-
-HTRAF.prefix = $script.attr('src').replace(/(^|\/)htraf\.js$/, '');
-HTRAF.htsqlPrefix = $script.attr('data-htsql-prefix') || '';
-
-var htsqlVersion = ($script.attr('data-htsql-version') || '1').substr(0,1);
-HTRAF.htsqlVersion = /1|2/.test(htsqlVersion) ? htsqlVersion:'1';
-
-HTRAF.addClass = $script.attr('data-htraf-class') || 'htraf';
-
-HTRAF.convert = {};
-HTRAF.convert.htsql = HTRAF.htsqlVersion == '1' ?
-    function(data) {
-        var ret = {headers: [], data: []}; 
-        $.each(data.meta[0].segment[0].element, function(i, element) {
-            ret.headers.push({
-                title: element.title,
-                domain: element.domain
-            });
-        });
-
-        $.each(data.data.branches[0], function(i, row) {
-            ret.data.push(row.fields);     
-        });
-        return ret;
-    }
-    :
-    function(data) {
-        return {
-            headers: data.meta,
-            data: data.data
-        };
-    };
-
-HTRAF.htsqlFormatter = HTRAF.htsqlVersion == 1 ? 'jsonex':':json';
-
-var onerror = $script.attr('data-onerror'); 
-HTRAF.onerror = onerror ? new Function(onerror) :
-                          function (e, info) {
-                              alert('Error loading element\n\n'
-                                    + info.reason + '\n'
-                                    + info.detail + '\n\n'
-                                    + 'Element:\n' 
-                                    + $.htraf.util.getHtml(info.element) 
-                                    + '\n');
-                          };
-
-var qs = location.search;
-HTRAF.param = {};
-if(qs) {
-    qs = qs.substr(1, qs.length).split('&');
-    for(var i = 0, l = qs.length; i < l; i++) {
-        var param = qs[i].split('=');
-        if(param.length == 2)
-            HTRAF.param[param[0]] = decodeURIComponent(param[1]);
-    }
-}
-
-HTRAF.$ = $;
-// }}}
-
-
-$.htraf = $.htraf || {};
-$.htraf.plugin = $.htraf.plugin || {};
-
-// {{{ Error handling
-$.htraf.AssertionError = function(message, element) {
-    if(element)
-        message += '\nElement: ' + getHtml(element);
-    alert('[Assertion Error] ' + message);  
-};
-
-function assert(condition, message, element) {
-    // TODO: provide more information on element which triggered the error    
-    if(!condition)
-        throw $.htraf.AssertionError(message, element);
-}
-
-// }}}
-
-// {{{ Common Utils
-
-var _id = 0;
-function generateId(prefix) {
-    var prefix = prefix || 'htraf';
-    return prefix + (_id++);
-}
-
-function str(value) {
-    return value === null || value === undefined ? '':value + '';
-}
-
-function getHtml(node) {
-    var clone = $(node).clone().get(0);
-    hiddenNode().children().remove();
-    return hiddenNode().append(clone).html(); 
-}
-
-function isTrue(data) {
-    if(!data)
-        return false;
-    if(typeof data=='string') {
-        data = data.toLowerCase();
-        return data == 'yes' || data == 'true';
-    }
-    else
-        return true;
-}
-
-function isHtsqlResource(url) {
-    return url.match(/\.htsql$/) ? true:false;
-}
-
-var $hidden = null;
-function hiddenNode() {
-    if(!$hidden)
-        $hidden = $('<div style="display: none;"/>').appendTo('body');
-    return $hidden;
-}
-
-function node(html) {
-    var hidden = hiddenNode();
-    hidden.get(0).innerHTML = html;
-    return hidden.children().get(0);
-};
-
-function debug() {
-    if(window.console)
-        window.console.debug.apply(window.console, arguments);
-}
-
-function varsToQS(vars) {
-    var ret = [];
-    $.each(vars, function(key, value) {
-        if(value === null)
-            return;
-        ret.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
-    });
-    return ret.join('&');
-}
-
-function escape(obj, noQuotes) {
-    var quote = noQuotes ? '':"'";
-    function _escape(obj) {
-        if(obj === null || obj === undefined)
-            return 'null()';
-        else if(obj instanceof Array) {
-            return '[' + obj.map(function(item) {
-                return _escape(item);
-            }).join(',') + ']'
-        }
-        else if (obj instanceof Object) {
-            var ret = []; 
-            for(var key in obj) {
-                if(HTRAF.htsqlVersion == '1' && (obj[key] === null 
-                || obj[key] === undefined))
-                    continue;
-                ret.push(_escape(key) + ':' + _escape(obj[key]));
-            }
-            return '{' + ret.join(',') + '}';
-        }
-        else {
-            obj = $.trim(obj + '');
-            return quote + obj.replace(/'/g, "''")
-                          .replace(/%/g, "%25")
-                          .replace(/\n/g, "%0A") 
-                          .replace(/\r/g, "%0D") 
-                          .replace(/\t/g, "%09") 
-                       + quote;   
-        }
-    }
-    return _escape(obj);
-}
-
-function expand(obj) {
-    var vars = [];
-    $.each(obj, function(key, value) {
-        vars.push(key + '=' + JSON.stringify(value));
-    });
-    return vars.length ? 'var ' + vars.join(',') + ';':'';
-}
-
-function expandConstants() {
-    return expand($.htraf.util.constant);
-}
-
-var included = {};
-function require(files, onSuccess, onTimeout) {
-    var started = new Date(), timeout = 60;
-    onTimeout = onTimeout || function(files) {
-        assert(false, "Inclusion timeout occured.\nFiles:\n\n" 
-                      + files.join('\n'));
-    };
-
-    var head = document.getElementsByTagName("head")[0];
-    function _createScriptTag(url, hasCheck) {
-        var scriptNode = document.createElement('script');
-        scriptNode.src = url;
-        scriptNode.type = 'text/javascript';
-        scriptNode.async = false;
-        head.appendChild(scriptNode);
-        included[url] = !hasCheck;
-    }
-
-    function _createCSSTag(url) {
-        var cssNode = document.createElement('link');
-        cssNode.type = 'text/css';
-        cssNode.rel = 'stylesheet';
-        cssNode.href = url;
-        cssNode.media = 'screen';
-        cssNode.title = 'dynamicLoadedSheet';
-        head.appendChild(cssNode);
-        included[url] = true;
-    }
-
-    var f = [], check = [];
-    $.each(files, function(i, file) {
-        if(file.css && included[file.css] === undefined)
-            _createCSSTag(file.css); 
-
-        if(file.js)
-            if(file.check) {
-                f.push(file.js);
-                check.push(file.check);
-            }
-            else {
-                if(!included[file.js])
-                    _createScriptTag(file.js, false);
-            }
-    });
-
-
-    function _require() {
-        if(!f.length)
-            return onSuccess.call();
-        function next() {
-            included[f[0]] = true;
-            f.shift();
-            check.shift();
-            setTimeout(_require, 200);
-        }
-        if(included[f[0]] || check[0].call())
-            return next();
-        if(included[f[0]] === undefined)
-            _createScriptTag(f[0], true);
-        
-        if((new Date).getTime() - started < timeout * 1000)
-            setTimeout(_require, 200);
-        else
-            onTimeout.call(null, f);
-    }
-    _require();
-}
-
-function loading(el) {
-    var rect = el.getBoundingClientRect();
-    var position = $('body').css('position');
-    if(position != 'relative' && position != 'absolute')
-        $('body').css('position', 'relative');
-    return $('<img style="position: absolute;" src="' + HTRAF.prefix + 
-      '/htraf-load.gif">').appendTo('body').position({
-        of: $(el),
-        my: 'center center',
-        at: 'center center'
-    });
-}
-
-$.htraf.util = {
-    constant: {
-        HTSQL: 'data-htsql',
-        LOCAL: 'data-bind',
-        WIDGET: 'data-widget',
-        REF: 'data-ref',
-        SERVER: 'data-server',
-        HTSQL_PREFIX: 'data-htsql-prefix',
-        CHANGE: 'change',
-        USERSELECT: 'userselect',
-        BEFORELOAD: 'beforeload',
-        AFTERLOAD: 'afterload',
-        EMPTY: 'empty',
-        ERROR: 'error',
-
-        CSS_SELECTED: 'htraf-selected',
-        CSS_HOVER: 'htraf-hover'
-    }, 
-    expandConstants: expandConstants,
-    assert: assert,
-    node: node,
-    isTrue: isTrue,
-    getHtml: getHtml,
-    isHtsqlResource: isHtsqlResource,
-    escape: escape,
-    require: require
-};
-$.htraf.util.constant.SOURCES = [
-    $.htraf.util.constant.HTSQL,
-    $.htraf.util.constant.LOCAL
-];
-eval(expandConstants());
-// }}}
-
-// {{{ Widgets
-
-// {{{ Widget Utils
-function selectable(obj, property, attribute) {
-    // TODO: consider using $.delegate() here
-
-    obj._selectable = function() {
-        var set = this[property], self = this;
-        set.mouseover(function() {
-            if(!$(this).hasClass(CSS_SELECTED))
-                $(this).addClass(CSS_HOVER); 
-        })
-        .mouseout(function() {
-            $(this).removeClass(CSS_HOVER); 
-        });
-
-        set.click(function() {
-            $(this).removeClass(CSS_HOVER);    
-            self._select($(this).attr(attribute), this);
-        });
-
-        if(set.size() == 0) {
-            this._trigger('empty');
-            this._trigger('change');
-        }
-        else
-            //set.eq(0).click();
-            self._select(set.eq(0).attr(attribute));
-    }; 
-    obj._select = function(id, item) {
-        this._index = id;
-
-        var set = this[property];
-        if(id === '' || id === null) {
-            set.removeClass(CSS_SELECTED);
-            return;
-        }
-
-        var selected = item ? $(item) : set.filter(function() {
-            return $(this).attr(attribute) == id; 
-        });
-        if(selected.size() == 0)
-            return;
-
-        set.removeClass(CSS_SELECTED);
-        selected.eq(0).addClass(CSS_SELECTED);
-
-        this._trigger('change');
-
-    };
-
-    obj.setValue = function(value) {
-        var slot = this._getSlot(), index = null;
-        $.each(this.data.data, function(i, row) {
-            if(row[slot] == value && index == null)
-                index = i;
-        });
-        this._select(index);
-    };
-
-    obj.getValue = function() {
-        var row = this._getRow();
-        if(row == null)
-            return null;
-        return row[this._getSlot()];
-    };
-
-    obj._getRow = function() {
-        if(this._index == null)
-            return null;
-        return this.data.data[this._index] || null;
-    };
-
-    obj.removeUserSelect = function() {
-        if(this[property])
-            this[property].unbind('click.userselect');                  
-    };
-
-    obj.addUserSelect = function() {
-        var self = this;
-        this[property].bind('click.userselect', function() {
-            self._trigger(USERSELECT); 
-        });  
-    };
-}
-$.fn.htrafProc = function() {
-    var args = $.makeArray(arguments);
-    return this.each(function() {
-        if(!$(this).data('htraf'))
-            return;
-        var method = $(this)[$(this).data('htraf')];
-        if(!$.isFunction(method))
-            return;
-        method.apply($(this), args);
-    });
-};
-
-$.fn.htrafFunc = function() {
-    var args = $.makeArray(arguments);
-    if(!this.size())
-        return;
-
-    return (function() {
-        if(!$(this).data('htraf'))
-            return;
-        var method = $(this)[$(this).data('htraf')];
-        if(!$.isFunction(method))
-            return;
-        return method.apply($(this), args);
-    }).call(this[0]);
-};
-
-$.fn._attrOrig = $.fn.attr;
-$.fn.attr = function(attr, value) {
-    if(value !== undefined) {
-        var ret = $.fn._attrOrig.apply(this, arguments);
-        if(attr.substr(0, 5) == 'data-')
-            this.htrafProc('updateAttr', attr);
-        return ret;
-    }
-    else
-        return $.fn._attrOrig.apply(this, arguments);
-    
-};
-
-$.fn._valOrig = $.fn.val;
-$.fn.val = function(value) {
-    var origSelector = 'input,textarea';
-    if(value !== undefined)
-        return this.each(function() {
-            if($(this).is(origSelector) || !$(this).data('htraf'))
-                $.fn._valOrig.call($(this), value);
-            else
-                $(this).htrafProc('setValue', value); 
-        });
-    else
-        return ($(this).is(origSelector) || !$(this).data('htraf')) ?
-               $.fn._valOrig.call(this):this.htrafFunc('getValue');
-    
-};
-// }}}
-
-// {{{ Widgets: htraf.Base
-$.widget('htraf.Base', {
-    _updateAttr: {},
-
-    _setupSource: function() {
-        var server = this.element.attr(SERVER);
-        this._htsqlPrefix = (server ? 
-            $('script[' + SERVER + '="' + server + '"]').attr(HTSQL_PREFIX)
-            : HTRAF.htsqlPrefix) || HTRAF.htsqlPrefix;
-
-        if(this._ref)
-            this._removeRef();
-        else
-            this._ref = $();
-        this._param  = {};
-
-        var self = this;
-        $.each(SOURCES, function(i, attr) {
-            if(self._source)
-                return;
-            var value = self.element.attr(attr);
-            if(value) {
-                self._source = value;
-                self._sourceType = attr;
-            }
-        }); 
-
-        switch(this._sourceType) {
-            case HTSQL: 
-                var refs = [],
-                    ids = this.element.attr(REF) || '';
-                $.each(ids.split(' '), function(i, id) {
-                    if(!id)
-                        return;
-                    if(id.substr(0, 1) == '_') {
-                        var value = HTRAF.param[id.substr(1, id.length)];
-                        self._param[id] = value || null;
-                    }
-                    else
-                        refs.push('#' + id); 
-                });
-                this._addRef(refs.join(',')); 
-                break;
-            case LOCAL: this._addRef('#' + this._source); break;
-        }
-    },
-
-    _getSlot: function() {
-        var _slot = this.element.attr('data-slot') || '0',
-            slot = 0;        
-        if(_slot.match(/^\d+$/)) {
-            _slot = _slot - 0;
-            if(_slot < this.data.headers.length)
-                slot = _slot;
-        }
-        else {
-            $.each(this.data.headers, function(i, element) {
-                if(element.title == _slot)
-                    slot = i;
-            }); 
-        }
-        return slot;
-    },
-
-    _getRow: function() {
-        return this.data.data[0] || null;
-    },
-
-    getData: function() {
-        if(this.element.is('script'))
-            return this.data;
-        var row = this._getRow();
-        return {
-            headers: this.data.headers,
-            data: row ? [row]:[]
-        }; 
-    },
-
-    _create: function() {
-        var self = this;
-        
-        var plugin = $.htraf.plugin[this.widgetName] || {}; 
-        function bindPlugin(prefix) {
-            self.element.bind([BEFORELOAD, AFTERLOAD].join(' '), function(e) {
-                var fName = e.type.substr(0, 5) == 'after' ? 
-                            prefix + 'AfterLoad' : prefix + 'BeforeLoad';
-                var f = plugin[fName] || function() {};
-                f.call(self); 
-            });
-        }
-
-        this.uniqueID = Math.ceil(100000 * Math.random()) 
-                        + '_' + (new Date).getTime();
-        this.widgetEventPrefix = '';
-        this.element.data('htraf', this.widgetName);
-        this._setupSource();
-
-        bindPlugin('pre');
-        this.element.bind(
-            [CHANGE, BEFORELOAD, AFTERLOAD, EMPTY, USERSELECT].join(' '),
-            function(e) {
-                var code = $(this).attr('data-on' + e.type);
-                if(!code)
-                    return;
-                return (new Function(code)).call(this); 
-            });
-        bindPlugin('post');
-
-        var onerrorCode = this.element.attr('data-on' + ERROR);
-        var onerror = !onerrorCode ? null:function(e, info) {
-            return (new Function(onerrorCode)).call(this, e, info); 
-        };
-        this.element.bind(ERROR, onerror || HTRAF.onerror);
-
-        if(HTRAF.addClass) {
-            $.each(HTRAF.addClass.split(' '), function(i, cls) {
-                cls = $.trim(cls);        
-                if(self.element.hasClass(cls))
-                    return;
-                self.element.attr('class', cls + ' '
-                                           + self.element.attr('class') || '');
-            });
-        }
-    },
-
-    updateAttr: function(attr) {
-        var f = this._updateAttr[attr] || null;
-        if($.isFunction(f))
-            f.call(this);
-    },
-
-    ref: function() {
-        return this._ref;
-    },
-
-    isLocal: function() {
-        return this._sourceType == LOCAL;       
-    },
-
-    _addRef: function(selector) {
-        if($(selector).size() == 0)
-            return;
-
-        var self = this;
-        this._ref = this._ref.add(selector);
-        this._ref
-            .unbind('change.' + this.uniqueID)
-            .bind('change.' + this.uniqueID, function() {
-                if(self.isLocal())
-                    self.loadLocal();
-                else
-                    self.load(); 
-            });
-    },
-
-    _removeRef: function() {
-        this._ref.unbind('change.' + this.uniqueID)
-        this._ref = $();
-    },
-
-    getVars: function() {
-        var vars = $.extend({}, this._param || {});
-        this.ref().each(function() {
-            if(!$(this).attr('id'))
-                return;
-            var widget = $(this).data('htraf');
-            vars[$(this).attr('id')] = $(this).val();
-        });
-        return vars;
-    },
-
-    _buildURL: function(formatter) {
-        var url = this._source;
-        formatter = formatter || '';
-        this.assert(url, "Cannot load: URL is undefined");
-
-        var vars = this.getVars(), h;
-        if(isHtsqlResource(url))
-            url = url + '?' + (formatter ? 'format=' + formatter + '&':'') 
-                   + varsToQS(vars);
-        else {
-            if(url.substr(url.length - 1, 1) != '/' && formatter
-            && formatter.substr(0, 1) != '/') {
-                formatter = '/' + formatter;
-            }
-            if(HTRAF.htsqlVersion == '1' && formatter 
-            && formatter.substr(formatter.length - 1, 1) != ')') {
-                formatter += '()';
-            }
-            
-            if(HTRAF.htsqlVersion == '1') {
-                url = '/htsql:let(' + url + formatter + ',' 
-                      + escape(vars) + ')';
-            }
-            else {
-                var s = [];
-                $.each(vars, function(key, value) {
-                    var e = escape(value);
-                    if(e.substr(0, 1) == '[') // sort of hack
-                        e = '{' + e.substr(1, e.length - 2) + '}';
-                    s.push('$' + key + ':=' + e); 
-                });
-                s = s.join(',');
-                url = url + (s ? ' :where(' + s + ')':'') + formatter;
-            }
-        }
-        return this._htsqlPrefix + url;
-    },
-
-    removeUserSelect: function() {
-    
-    },
-
-    addUserSelect: function() {
-                   
-    },
-
-    _loaded: function(data) {
-        this.removeUserSelect();
-        this.data = data;
-        this.clear();
-        this.render();
-        this.setup();
-        this.addUserSelect();
-    },
-
-    loadLocal: function() {
-        var $source = $('#' + this._source);
-
-        var error = "Misconfigured '" + LOCAL + "' attribute.";
-        this.assert($source.size() > 0, "Source is not found. " + error);
-        this.assert($source.size() == 1, "Multiple sources. " + error);
-        this.assert($source.data('htraf'), "Source is not HTRAF widget");
-        var data = $source.htrafFunc('getData');
-        this._loaded(data);
-    },
-
-    load: function() {
-        var dataType = this._sourceType.substr('data-'.length, 100),
-            convert = HTRAF.convert[dataType] || function(data) {
-                return data;
-            },
-            self = this;
-        
-        this._trigger(BEFORELOAD);
-        var url = this._buildURL(HTRAF.htsqlFormatter),
-            $spinner = loading(this.element[0]);
-        $.ajax({
-            url: url,
-            dataType: 'json',
-            success: function(data) {
-                $spinner.remove();
-                self._loaded(convert(data));
-                self._trigger(AFTERLOAD);
-            },
-            error: function(request, status) {
-                $spinner.remove();
-                try {
-                    var obj = eval('(' + request.responseText + ')');
-                }
-                catch(e) {
-                    var obj = {
-                        reason: request.responseText,
-                        detail: ''
-                    };
-                }
-                obj.element = self.element[0];
-                self._trigger(ERROR, {}, [obj]);
-            }
-        });
-    },
-
-    clear: function() {
-        $(this.element).children().remove(); 
-    },
-    
-    render: function() {},
-
-    setup: function() {},
-
-    getValue: function() {
-        var row = this._getRow();
-        return row ? row[this._getSlot()]:null;
-    },
-
-    setValue: function(value) {
-        this.assert(false, 'setting value is not possible');
-    },
-
-    assert: function(condition, message) {
-        assert(condition, message, this.element.get(0));       
-    }
-});
-
-$.htraf.Base.prototype._updateAttr[HTSQL] = 
-$.htraf.Base.prototype._updateAttr[LOCAL] = 
-    function() {
-        this._setupSource();
-        this.load();
-    };
-// }}}
- 
-// {{{ Widgets: htraf.select
-$.widget("htraf.select", $.htraf.Base, {
-    _create: function() {
-        $.htraf.Base.prototype._create.call(this);
-        this.keepOptions = this.element[0].options.length;
-    },
-
-    clear: function() {
-        if(this.keepOptions)
-            this.element[0].selectedIndex = 0;
-        this.element[0].options.length = this.keepOptions || 0;
-    },
-
-    render: function() {
-        var select = this.element[0], 
-            data = this.data.data,
-            keep = this.keepOptions;
-        for(var i = data.length - 1; i >= 0; i--) {
-            var row = data[i];
-            select.options[i + keep] = new Option(
-                row[1] === undefined  ? row[0]:row[1], 
-                row[0] === null ? row[0]:'',
-                i + keep == 0, i + keep == 0); 
-        }
-        for(var i = keep - 1; i >= 0; i--) {
-            var option = select.options[i],
-                value = option.value == '' ? null:option.value;
-            this.data.data.unshift([value, option.text]);
-        }
-        if(!data.length)
-            this._trigger('empty');
-        this._trigger('change');
-    },
-
-    _getRow: function() {
-        return this.data.data[this.element[0].selectedIndex] || null; 
-    },
-
-    setValue: function(value) {
-        value = value === null ? '':value + '';          
-        this.element._valOrig(value);
-    },
-
-    removeUserSelect: function() {
-        this.element.unbind('change.userselect');                  
-    },
-
-    addUserSelect: function() {
-        var self = this;
-        this.element.bind('change.userselect', function() {
-            self._trigger(USERSELECT); 
-        });               
-    }
-});
-// }}}
-
-// {{{ Widgets: htraf.table
-$.widget("htraf.table", $.htraf.Base, {
-    _create: function() {
-        $.htraf.Base.prototype._create.call(this);
-        selectable(this, '_tr', 'data-index');
-    },
-
-    render: function() {
-        var data = this.data,
-            $element = this.element,
-            $thead = $('<thead/>').appendTo($element),
-            $tbody = $('<tbody/>').appendTo($element),
-            $tfoot = $('<tfoot/>').appendTo($element),
-            hidden = {};
-
-        for(var i = 0; i < data.headers.length; i++)
-            if(isTrue($element.attr('data-hide-column-' + i)))
-                hidden[i] = true;
-
-        $.each(data.headers, function(i, el) {
-            var $thHead = $('<th/>').text(el.title).addClass('c' + i)
-                                    .appendTo($thead),
-                $thFoot = $('<th/>').addClass('c' + i).appendTo($tfoot);
-
-            if(hidden[i]) {
-                $thHead.css('display', 'none');
-                $thFoot.css('display', 'none');
-            }
-        });
-
-        $.each(data.data, function(i, row) {
-            var $tr = $('<tr/>').appendTo($tbody)
-                                .addClass('r' + i)
-                                .addClass(i % 2 ? 'even':'odd')
-                                .attr('data-index', i + '');
-            $.each(row, function(j, value) {
-                var $td = $('<td/>').addClass('c' + j)
-                                    .text(str(value)).appendTo($tr); 
-                if(hidden[j]) 
-                    $td.css('display', 'none');
-            });
-        });
-    },
-
-    setup: function() {
-        this._tr = this.element.find('tr');
-        var selectable = this.element.attr('data-selectable') || 'yes';
-        if(isTrue(selectable))
-            this._selectable();
-    }
-});
-$.htraf.table.prototype._updateAttr['data-column'] = function() {
-    this._setupId();
-    this._trigger('change');
-};
-
-// }}}
-
-// {{{ Widgets: htraf.singleValue
-$.widget("htraf.singleValue", $.htraf.Base, {
-    render: function() {
-        var value = this.getValue();
-        value = value === null || value === undefined ? '':value + '';
-        this.element.text(value);
-        this._trigger('change');
-    }
-});
-// }}}
-
-// {{{ Widgets: htraf.iframe
-$.widget("htraf.iframe", $.htraf.Base, {
-    _create: function() {
-        assert(this.element.is('iframe'), '"iframe" widget is incompatible',
-               this.element.get(0));
-        $.htraf.Base.prototype._create.call(this);
-    },
-
-    clear: function() {},
-
-    load: function() {
-        var self = this;
-        this._trigger(BEFORELOAD);
-        this.element.attr('src', 'about:blank'); 
-        this.element.one('load', function() {
-            self._trigger(AFTERLOAD);      
-        });
-        this.element.attr('src', this._buildURL());
-    }
-});
-// }}}
-
-// {{{ Widgets: htraf.ul, htraf.ol
-var list = {