diff --git a/examples/lists/countries/js/example.js b/examples/lists/countries/js/example.js index a719b7b4..23eab166 100644 --- a/examples/lists/countries/js/example.js +++ b/examples/lists/countries/js/example.js @@ -1,12 +1,20 @@ /* -In this example, we will build a list of countries that can be displayed in grid -or list view. +In this example, we will build a list of countries that can be displayed both as +a table and as a grid of icons. */ - 'use strict'; -Ox.load(['Geo', 'UI'], function() { +/* +We load the `UI` and `Geo` modules. The `Geo` module gives us Ox.COUNTRIES, the +data for our list. +*/ +Ox.load(['UI', 'Geo'], function() { + /* + We extend the country data to include a flag icon (Ox.getFlagByCountryCode + returns an image URL), and patch the `region` property so that when our list + is sorted by region, regions will be grouped by continent. + */ var items = Ox.COUNTRIES.map(function(country) { return Ox.extend({}, country, { flag: Ox.getFlagByCountryCode(country.code, 256), @@ -14,6 +22,12 @@ Ox.load(['Geo', 'UI'], function() { }); }), + /* + These are the table columns in list view. The `operator` property + specifies the default sort order, `format` allows us to modify the value + before it gets displayed, `align` (default: `'left'`) is used to set the + alignment, and the rest should be pretty self-explanatory. + */ columns = [ { id: 'code', @@ -59,10 +73,10 @@ Ox.load(['Geo', 'UI'], function() { width: 112 }, { + align: 'right', format: function(value) { return Ox.formatDegrees(value, 'lat'); }, - align: 'right', id: 'lat', operator: '-', title: 'Latitude', @@ -70,10 +84,10 @@ Ox.load(['Geo', 'UI'], function() { width: 80 }, { + align: 'right', format: function(value) { return Ox.formatDegrees(value, 'lng'); }, - align: 'right', id: 'lng', operator: '+', title: 'Longitude', @@ -82,25 +96,59 @@ Ox.load(['Geo', 'UI'], function() { } ], + /* + This is the width of the UI element where more information about the + selected item gets displayed. + */ itemSize = 288 + Ox.UI.SCROLLBAR_SIZE, + /* + `ui` holds the state of our application: + */ ui = { + /* + the query string, + */ find: '', + /* + the types of countries to include, + */ include: { dependency: false, disputed: false, dissolved: false, exception: false }, + /* + the selected items (note that this is an array, even though we will + limit the maximum number of selected items to 1, later on), + */ selected: [], + /* + the sort order (note that this is an array too, so if our country + names weren't unique, we could add more criteria) + */ sort: [{key: 'name', operator: '+'}], + /* + and the view (either `'grid'` or `'list'`). + */ view: 'grid' }, + /* + This is the query for our list, which depends on `ui.find` and + `ui.include`. + */ query = getQuery(), + /* + A simple toolbar. + */ $toolbar = Ox.Bar({size: 24}).addClass('bar'), + /* + A group of two buttons to switch between `grid` and `list` view. + */ $view = Ox.ButtonGroup({ buttons: [ {id: 'grid', title: 'grid', tooltip: 'View as Grid'}, @@ -113,6 +161,11 @@ Ox.load(['Geo', 'UI'], function() { .bindEvent({change: view}) .appendTo($toolbar), + /* + A select element to set the sort order. In `list` view, the table + provides its own UI for sorting, so we only show this element in `grid` + view. + */ $sort = Ox.Select({ items: columns.filter(function(column) { return column.id != 'flag'; @@ -129,13 +182,20 @@ Ox.load(['Geo', 'UI'], function() { [ui.view == 'grid' ? 'show' : 'hide']() .appendTo($toolbar), - $order = Ox.Button(Ox.extend(getOptions(), { - type: 'image' - })) + /* + A button to switch between 'ascending' and 'descending'. Again, this is + only needed in `grid` view. + */ + $order = Ox.Button(getOptions()) .bindEvent({click: order}) [ui.view == 'grid' ? 'show' : 'hide']() .appendTo($toolbar), + /* + This is our search box. To implement "find-as-you-type", we set its + `changeOnKeypress` option to `true`. Otherwise, its change event would + only fire when the user hits return. + */ $find = Ox.Input({ changeOnKeypress: true, clear: true, @@ -146,6 +206,9 @@ Ox.load(['Geo', 'UI'], function() { .bindEvent({change: find}) .appendTo($toolbar), + /* + And a menu to specify which types of countries to include. + */ $include = Ox.MenuButton({ items: [ {id: 'dependency', title: 'Include dependencies'}, @@ -160,13 +223,25 @@ Ox.load(['Geo', 'UI'], function() { .addClass('right') .bindEvent({change: include}) .appendTo($toolbar), - + + /* + The list itself. + */ $list = renderList(), + /* + A simple statusbar. + */ $statusbar = Ox.Bar({size: 16}), + /* + An element for the status text. + */ $status = Ox.Element().addClass('status').appendTo($statusbar), + /* + The list panel holds the toolbar, the list and the statusbar. + */ $listPanel = Ox.SplitPanel({ elements: [ {element: $toolbar, size: 24}, @@ -176,14 +251,23 @@ Ox.load(['Geo', 'UI'], function() { orientation: 'vertical' }), + /* + Now we can move on to the item panel. The first element is another bar. + */ $titlebar = Ox.Bar({size: 24}).addClass('bar'), + /* + The label will show the country name. + */ $title = Ox.Label({ width: itemSize - 28 }) .hide() .appendTo($titlebar), + /* + A button to deselect the currently selected item. + */ $deselect = Ox.Button({ title: 'close', type: 'image' @@ -197,8 +281,15 @@ Ox.load(['Geo', 'UI'], function() { .hide() .appendTo($titlebar), + /* + An element to hold the item data. + */ $item = Ox.Element().addClass('item'), + /* + The item panel: titlebar, item and one more bar (to match the layout of + the list panel). + */ $itemPanel = Ox.SplitPanel({ elements: [ {element: $titlebar, size: 24}, @@ -208,6 +299,10 @@ Ox.load(['Geo', 'UI'], function() { orientation: 'vertical' }), + /* + And finally the main panel, which combines the list panel and the item + panel. + */ $mainPanel = Ox.SplitPanel({ elements: [ {element: $listPanel}, @@ -217,6 +312,10 @@ Ox.load(['Geo', 'UI'], function() { }) .appendTo(Ox.$body); + /* + Whenever the user types something in the search box, we update the list + query. + */ function find() { ui.find = $find.options('value'); query = getQuery(); @@ -224,14 +323,26 @@ Ox.load(['Geo', 'UI'], function() { $list.options({query: query}); } + /* + This function returns the options for the sort order button, which depend on + `ui.sort`. + */ function getOptions() { var operator = ui.sort[0].operator; return { title: operator == '+' ? 'up' : 'down', - tooltip: operator == '+' ? 'Ascending' : 'Descending' + tooltip: operator == '+' ? 'Ascending' : 'Descending', + type: 'image' }; } + /* + This function returns a query, which is an array of conditions and an + operator. Additionally to `find`, which applies to the country's name + property, we add a condition for each `include` setting that is `false`. For + example, in order to not include dependencies, we have to add a condition + that tests if the country's `dependency` property is `undefined`. + */ function getQuery() { var query = { conditions: [{key: 'name', operator: '=', value: ui.find}], @@ -245,11 +356,24 @@ Ox.load(['Geo', 'UI'], function() { return query; } + /* + A handler for the `change` event of the menu button. + */ function include(data) { ui.include[data.id] = data.checked; find(); } + /* + A handler for the `init` event of the list, which fires once the list knows + the total number of items for the current query. In our case, it'll know + that instantly. But the list's `items` option doesn't have to be a static + array — it can also be a call to any remote API that understands our + `query` syntax. (In fact, when passing an `items` array, Ox.List uses + Ox.api, which is a local implementation of such an API.) The first request + to this API will return the totals, so they can be displayed before + retrieving the actual data. + */ function init(data) { $status.html( (data.items || 'No') + ' countr' @@ -257,12 +381,24 @@ Ox.load(['Geo', 'UI'], function() { ); } + /* + A handler for the `click` event of the sort order button. Note that we have + to pass a deep copy of `ui.sort`. If `ui.sort` and the lists `sort` option + were references to the same array, then changing `ui.sort` and passing it + as `sort` option would no longer register as a change. In other words: to + update the options of a widget, don't hold references and update them, but + use the widget's `options` method. + */ function order() { ui.sort[0].operator = ui.sort[0].operator == '+' ? '-' : '+'; $order.options(getOptions()); $list.options({sort: Ox.clone(ui.sort, true)}); } + /* + This renders the selected item. In grid view, we display all the country's + properties, in list view, we show a flag icon. + */ function renderItem() { var code = ui.selected[0]; $item.empty(); @@ -281,6 +417,9 @@ Ox.load(['Geo', 'UI'], function() { } } + /* + ... + */ function renderList() { return ui.view == 'grid' ? Ox.IconList({ @@ -336,6 +475,9 @@ Ox.load(['Geo', 'UI'], function() { }); } + /* + A handler for the `select` event of the list. + */ function select(data) { var id = data.ids[0]; if (id) { @@ -351,12 +493,17 @@ Ox.load(['Geo', 'UI'], function() { renderItem(); } + /* + A handler for both the `sort` event of the table and the `change` event of + the sort select element. In the latter case, we patch the event's properties + to match the signature of the former. + */ function sort(data) { if (data.value) { data = { key: data.value, operator: Ox.getObjectById(columns, data.value).operator - } + }; } ui.sort = [{key: data.key, operator: data.operator}]; $sort.options({value: data.key}); @@ -364,6 +511,9 @@ Ox.load(['Geo', 'UI'], function() { $list.options({sort: Ox.clone(ui.sort, true)}); } + /* + And a handler for the change event of the `view` switch. + */ function view(data) { ui.view = data.value; $sort[ui.view == 'grid' ? 'show' : 'hide']();