update countries example

This commit is contained in:
rolux 2012-07-02 15:37:24 +02:00
parent d95d13f13d
commit 6e0b496852

View file

@ -1,12 +1,20 @@
/* /*
In this example, we will build a list of countries that can be displayed in grid In this example, we will build a list of countries that can be displayed both as
or list view. a table and as a grid of icons.
*/ */
'use strict'; '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) { var items = Ox.COUNTRIES.map(function(country) {
return Ox.extend({}, country, { return Ox.extend({}, country, {
flag: Ox.getFlagByCountryCode(country.code, 256), 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 = [ columns = [
{ {
id: 'code', id: 'code',
@ -59,10 +73,10 @@ Ox.load(['Geo', 'UI'], function() {
width: 112 width: 112
}, },
{ {
align: 'right',
format: function(value) { format: function(value) {
return Ox.formatDegrees(value, 'lat'); return Ox.formatDegrees(value, 'lat');
}, },
align: 'right',
id: 'lat', id: 'lat',
operator: '-', operator: '-',
title: 'Latitude', title: 'Latitude',
@ -70,10 +84,10 @@ Ox.load(['Geo', 'UI'], function() {
width: 80 width: 80
}, },
{ {
align: 'right',
format: function(value) { format: function(value) {
return Ox.formatDegrees(value, 'lng'); return Ox.formatDegrees(value, 'lng');
}, },
align: 'right',
id: 'lng', id: 'lng',
operator: '+', operator: '+',
title: 'Longitude', 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, itemSize = 288 + Ox.UI.SCROLLBAR_SIZE,
/*
`ui` holds the state of our application:
*/
ui = { ui = {
/*
the query string,
*/
find: '', find: '',
/*
the types of countries to include,
*/
include: { include: {
dependency: false, dependency: false,
disputed: false, disputed: false,
dissolved: false, dissolved: false,
exception: 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: [], 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: '+'}], sort: [{key: 'name', operator: '+'}],
/*
and the view (either `'grid'` or `'list'`).
*/
view: 'grid' view: 'grid'
}, },
/*
This is the query for our list, which depends on `ui.find` and
`ui.include`.
*/
query = getQuery(), query = getQuery(),
/*
A simple toolbar.
*/
$toolbar = Ox.Bar({size: 24}).addClass('bar'), $toolbar = Ox.Bar({size: 24}).addClass('bar'),
/*
A group of two buttons to switch between `grid` and `list` view.
*/
$view = Ox.ButtonGroup({ $view = Ox.ButtonGroup({
buttons: [ buttons: [
{id: 'grid', title: 'grid', tooltip: 'View as Grid'}, {id: 'grid', title: 'grid', tooltip: 'View as Grid'},
@ -113,6 +161,11 @@ Ox.load(['Geo', 'UI'], function() {
.bindEvent({change: view}) .bindEvent({change: view})
.appendTo($toolbar), .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({ $sort = Ox.Select({
items: columns.filter(function(column) { items: columns.filter(function(column) {
return column.id != 'flag'; return column.id != 'flag';
@ -129,13 +182,20 @@ Ox.load(['Geo', 'UI'], function() {
[ui.view == 'grid' ? 'show' : 'hide']() [ui.view == 'grid' ? 'show' : 'hide']()
.appendTo($toolbar), .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}) .bindEvent({click: order})
[ui.view == 'grid' ? 'show' : 'hide']() [ui.view == 'grid' ? 'show' : 'hide']()
.appendTo($toolbar), .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({ $find = Ox.Input({
changeOnKeypress: true, changeOnKeypress: true,
clear: true, clear: true,
@ -146,6 +206,9 @@ Ox.load(['Geo', 'UI'], function() {
.bindEvent({change: find}) .bindEvent({change: find})
.appendTo($toolbar), .appendTo($toolbar),
/*
And a menu to specify which types of countries to include.
*/
$include = Ox.MenuButton({ $include = Ox.MenuButton({
items: [ items: [
{id: 'dependency', title: 'Include dependencies'}, {id: 'dependency', title: 'Include dependencies'},
@ -161,12 +224,24 @@ Ox.load(['Geo', 'UI'], function() {
.bindEvent({change: include}) .bindEvent({change: include})
.appendTo($toolbar), .appendTo($toolbar),
/*
The list itself.
*/
$list = renderList(), $list = renderList(),
/*
A simple statusbar.
*/
$statusbar = Ox.Bar({size: 16}), $statusbar = Ox.Bar({size: 16}),
/*
An element for the status text.
*/
$status = Ox.Element().addClass('status').appendTo($statusbar), $status = Ox.Element().addClass('status').appendTo($statusbar),
/*
The list panel holds the toolbar, the list and the statusbar.
*/
$listPanel = Ox.SplitPanel({ $listPanel = Ox.SplitPanel({
elements: [ elements: [
{element: $toolbar, size: 24}, {element: $toolbar, size: 24},
@ -176,14 +251,23 @@ Ox.load(['Geo', 'UI'], function() {
orientation: 'vertical' orientation: 'vertical'
}), }),
/*
Now we can move on to the item panel. The first element is another bar.
*/
$titlebar = Ox.Bar({size: 24}).addClass('bar'), $titlebar = Ox.Bar({size: 24}).addClass('bar'),
/*
The label will show the country name.
*/
$title = Ox.Label({ $title = Ox.Label({
width: itemSize - 28 width: itemSize - 28
}) })
.hide() .hide()
.appendTo($titlebar), .appendTo($titlebar),
/*
A button to deselect the currently selected item.
*/
$deselect = Ox.Button({ $deselect = Ox.Button({
title: 'close', title: 'close',
type: 'image' type: 'image'
@ -197,8 +281,15 @@ Ox.load(['Geo', 'UI'], function() {
.hide() .hide()
.appendTo($titlebar), .appendTo($titlebar),
/*
An element to hold the item data.
*/
$item = Ox.Element().addClass('item'), $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({ $itemPanel = Ox.SplitPanel({
elements: [ elements: [
{element: $titlebar, size: 24}, {element: $titlebar, size: 24},
@ -208,6 +299,10 @@ Ox.load(['Geo', 'UI'], function() {
orientation: 'vertical' orientation: 'vertical'
}), }),
/*
And finally the main panel, which combines the list panel and the item
panel.
*/
$mainPanel = Ox.SplitPanel({ $mainPanel = Ox.SplitPanel({
elements: [ elements: [
{element: $listPanel}, {element: $listPanel},
@ -217,6 +312,10 @@ Ox.load(['Geo', 'UI'], function() {
}) })
.appendTo(Ox.$body); .appendTo(Ox.$body);
/*
Whenever the user types something in the search box, we update the list
query.
*/
function find() { function find() {
ui.find = $find.options('value'); ui.find = $find.options('value');
query = getQuery(); query = getQuery();
@ -224,14 +323,26 @@ Ox.load(['Geo', 'UI'], function() {
$list.options({query: query}); $list.options({query: query});
} }
/*
This function returns the options for the sort order button, which depend on
`ui.sort`.
*/
function getOptions() { function getOptions() {
var operator = ui.sort[0].operator; var operator = ui.sort[0].operator;
return { return {
title: operator == '+' ? 'up' : 'down', 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() { function getQuery() {
var query = { var query = {
conditions: [{key: 'name', operator: '=', value: ui.find}], conditions: [{key: 'name', operator: '=', value: ui.find}],
@ -245,11 +356,24 @@ Ox.load(['Geo', 'UI'], function() {
return query; return query;
} }
/*
A handler for the `change` event of the menu button.
*/
function include(data) { function include(data) {
ui.include[data.id] = data.checked; ui.include[data.id] = data.checked;
find(); 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) { function init(data) {
$status.html( $status.html(
(data.items || 'No') + ' countr' (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() { function order() {
ui.sort[0].operator = ui.sort[0].operator == '+' ? '-' : '+'; ui.sort[0].operator = ui.sort[0].operator == '+' ? '-' : '+';
$order.options(getOptions()); $order.options(getOptions());
$list.options({sort: Ox.clone(ui.sort, true)}); $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() { function renderItem() {
var code = ui.selected[0]; var code = ui.selected[0];
$item.empty(); $item.empty();
@ -281,6 +417,9 @@ Ox.load(['Geo', 'UI'], function() {
} }
} }
/*
...
*/
function renderList() { function renderList() {
return ui.view == 'grid' return ui.view == 'grid'
? Ox.IconList({ ? Ox.IconList({
@ -336,6 +475,9 @@ Ox.load(['Geo', 'UI'], function() {
}); });
} }
/*
A handler for the `select` event of the list.
*/
function select(data) { function select(data) {
var id = data.ids[0]; var id = data.ids[0];
if (id) { if (id) {
@ -351,12 +493,17 @@ Ox.load(['Geo', 'UI'], function() {
renderItem(); 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) { function sort(data) {
if (data.value) { if (data.value) {
data = { data = {
key: data.value, key: data.value,
operator: Ox.getObjectById(columns, data.value).operator operator: Ox.getObjectById(columns, data.value).operator
} };
} }
ui.sort = [{key: data.key, operator: data.operator}]; ui.sort = [{key: data.key, operator: data.operator}];
$sort.options({value: data.key}); $sort.options({value: data.key});
@ -364,6 +511,9 @@ Ox.load(['Geo', 'UI'], function() {
$list.options({sort: Ox.clone(ui.sort, true)}); $list.options({sort: Ox.clone(ui.sort, true)});
} }
/*
And a handler for the change event of the `view` switch.
*/
function view(data) { function view(data) {
ui.view = data.value; ui.view = data.value;
$sort[ui.view == 'grid' ? 'show' : 'hide'](); $sort[ui.view == 'grid' ? 'show' : 'hide']();