oxjs/examples/lists/countries/js/example.js

527 lines
16 KiB
JavaScript
Raw Normal View History

2012-06-29 12:19:54 +00:00
/*
2012-07-02 13:37:24 +00:00
In this example, we will build a list of countries that can be displayed both as
a table and as a grid of icons.
2012-06-29 12:19:54 +00:00
*/
2012-06-19 12:18:45 +00:00
'use strict';
2012-07-02 13:37:24 +00:00
/*
We load the `UI` and `Geo` modules. The `Geo` module gives us Ox.COUNTRIES, the
data for our list.
*/
Ox.load(['UI', 'Geo'], function() {
2012-06-19 12:18:45 +00:00
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
var items = Ox.COUNTRIES.map(function(country) {
return Ox.extend({}, country, {
flag: Ox.getFlagByCountryCode(country.code, 256),
region: country.continent + ', ' + country.region
});
}),
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
columns = [
{
id: 'code',
operator: '+',
title: 'Code',
visible: true,
width: 64
},
{
id: 'name',
operator: '+',
removable: false,
title: 'Name',
visible: true,
width: 256
},
{
id: 'continent',
operator: '+',
title: 'Continent',
visible: true,
width: 96
},
{
format: function(value) {
return value.split(', ')[1];
},
id: 'region',
operator: '+',
title: 'Region',
visible: true,
width: 160
},
{
align: 'right',
format: function(value) {
return Ox.formatArea(value);
},
id: 'area',
operator: '-',
title: 'Area',
visible: true,
width: 112
},
{
2012-07-02 13:37:24 +00:00
align: 'right',
2012-06-19 12:18:45 +00:00
format: function(value) {
return Ox.formatDegrees(value, 'lat');
},
id: 'lat',
operator: '-',
title: 'Latitude',
visible: true,
width: 80
},
{
2012-07-02 13:37:24 +00:00
align: 'right',
2012-06-19 12:18:45 +00:00
format: function(value) {
return Ox.formatDegrees(value, 'lng');
},
id: 'lng',
operator: '+',
title: 'Longitude',
visible: true,
width: 80
2012-06-19 12:59:33 +00:00
}
2012-06-19 12:18:45 +00:00
],
2012-07-02 13:37:24 +00:00
/*
This is the width of the UI element where more information about the
selected item gets displayed.
*/
2012-06-19 12:18:45 +00:00
itemSize = 288 + Ox.UI.SCROLLBAR_SIZE,
2012-07-02 13:37:24 +00:00
/*
`ui` holds the state of our application:
*/
2012-06-19 12:59:33 +00:00
ui = {
2012-07-02 13:37:24 +00:00
/*
the query string,
*/
2012-06-19 12:18:45 +00:00
find: '',
2012-07-02 13:37:24 +00:00
/*
the types of countries to include,
*/
2012-06-19 12:18:45 +00:00
include: {
dependency: false,
disputed: false,
dissolved: false,
exception: false
},
2012-07-02 13:37:24 +00:00
/*
the selected items (note that this is an array, even though we will
limit the maximum number of selected items to 1, later on),
*/
2012-06-19 12:18:45 +00:00
selected: [],
2012-07-02 13:37:24 +00:00
/*
the sort order (note that this is an array too, so if our country
names weren't unique, we could add more criteria)
*/
2012-06-19 12:18:45 +00:00
sort: [{key: 'name', operator: '+'}],
2012-07-02 13:37:24 +00:00
/*
and the view (either `'grid'` or `'list'`).
*/
2012-06-19 12:18:45 +00:00
view: 'grid'
},
2012-07-02 13:37:24 +00:00
/*
This is the query for our list, which depends on `ui.find` and
`ui.include`.
*/
2012-06-19 12:18:45 +00:00
query = getQuery(),
2012-07-02 13:37:24 +00:00
/*
A simple toolbar.
*/
2012-06-19 12:18:45 +00:00
$toolbar = Ox.Bar({size: 24}).addClass('bar'),
2012-07-02 13:37:24 +00:00
/*
A group of two buttons to switch between `grid` and `list` view.
*/
2012-06-19 12:18:45 +00:00
$view = Ox.ButtonGroup({
buttons: [
2012-06-19 12:59:33 +00:00
{id: 'grid', title: 'grid', tooltip: 'View as Grid'},
{id: 'list', title: 'list', tooltip: 'View as List'}
2012-06-19 12:18:45 +00:00
],
selectable: true,
type: 'image',
2012-06-19 12:59:33 +00:00
value: ui.view
2012-06-19 12:18:45 +00:00
})
.bindEvent({change: view})
.appendTo($toolbar),
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
$sort = Ox.Select({
2012-06-19 12:59:33 +00:00
items: columns.filter(function(column) {
return column.id != 'flag';
}).map(function(column) {
return {
id: column.id,
title: 'Sort by ' + column.title
};
}),
value: ui.sort[0].key,
2012-06-19 12:18:45 +00:00
width: 128
})
.bindEvent({change: sort})
2012-06-19 12:59:33 +00:00
[ui.view == 'grid' ? 'show' : 'hide']()
2012-06-19 12:18:45 +00:00
.appendTo($toolbar),
2012-07-02 13:37:24 +00:00
/*
A button to switch between 'ascending' and 'descending'. Again, this is
only needed in `grid` view.
*/
$order = Ox.Button(getOptions())
2012-06-19 12:18:45 +00:00
.bindEvent({click: order})
2012-06-19 12:59:33 +00:00
[ui.view == 'grid' ? 'show' : 'hide']()
2012-06-19 12:18:45 +00:00
.appendTo($toolbar),
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
$find = Ox.Input({
changeOnKeypress: true,
clear: true,
placeholder: 'Find',
width: 192
})
.addClass('right')
.bindEvent({change: find})
.appendTo($toolbar),
2012-07-02 13:37:24 +00:00
/*
And a menu to specify which types of countries to include.
*/
2012-06-19 12:18:45 +00:00
$include = Ox.MenuButton({
items: [
{id: 'dependency', title: 'Include dependencies'},
{id: 'disputed', title: 'Include disputed countries'},
{id: 'dissolved', title: 'Include dissolved countries'},
{id: 'exception', title: 'Include other entities'}
].map(function(item) {
return Ox.extend(item, {checked: false});
}),
type: 'image'
})
.addClass('right')
.bindEvent({change: include})
.appendTo($toolbar),
2012-07-02 13:37:24 +00:00
/*
The list itself.
*/
2012-06-19 12:59:33 +00:00
$list = renderList(),
2012-06-19 12:18:45 +00:00
2012-07-02 13:37:24 +00:00
/*
A simple statusbar.
*/
2012-06-19 12:18:45 +00:00
$statusbar = Ox.Bar({size: 16}),
2012-07-02 13:37:24 +00:00
/*
An element for the status text.
*/
2012-06-19 12:18:45 +00:00
$status = Ox.Element().addClass('status').appendTo($statusbar),
2012-07-02 13:37:24 +00:00
/*
The list panel holds the toolbar, the list and the statusbar.
*/
2012-06-19 12:18:45 +00:00
$listPanel = Ox.SplitPanel({
elements: [
{element: $toolbar, size: 24},
2012-06-19 12:59:33 +00:00
{element: $list},
2012-06-19 12:18:45 +00:00
{element: $statusbar, size: 16}
],
orientation: 'vertical'
}),
2012-07-02 13:37:24 +00:00
/*
Now we can move on to the item panel. The first element is another bar.
*/
2012-06-19 12:18:45 +00:00
$titlebar = Ox.Bar({size: 24}).addClass('bar'),
2012-07-02 13:37:24 +00:00
/*
The label will show the country name.
*/
2012-06-19 12:18:45 +00:00
$title = Ox.Label({
width: itemSize - 28
})
.hide()
.appendTo($titlebar),
2012-07-02 13:37:24 +00:00
/*
A button to deselect the currently selected item.
*/
2012-06-19 12:18:45 +00:00
$deselect = Ox.Button({
title: 'close',
type: 'image'
})
.bindEvent({
click: function() {
2012-06-19 12:59:33 +00:00
$list.options({selected: []});
2012-06-19 12:18:45 +00:00
select({ids: []});
}
})
.hide()
.appendTo($titlebar),
2012-07-02 13:37:24 +00:00
/*
An element to hold the item data.
*/
2012-06-19 12:18:45 +00:00
$item = Ox.Element().addClass('item'),
2012-07-02 13:37:24 +00:00
/*
The item panel: titlebar, item and one more bar (to match the layout of
the list panel).
*/
2012-06-19 12:18:45 +00:00
$itemPanel = Ox.SplitPanel({
elements: [
{element: $titlebar, size: 24},
{element: $item},
{element: Ox.Bar({size: 16}), size: 16}
],
orientation: 'vertical'
}),
2012-07-02 13:37:24 +00:00
/*
And finally the main panel, which combines the list panel and the item
panel.
*/
2012-06-19 12:18:45 +00:00
$mainPanel = Ox.SplitPanel({
elements: [
{element: $listPanel},
{element: $itemPanel, size: itemSize}
],
orientation: 'horizontal'
})
.appendTo(Ox.$body);
2012-07-02 13:37:24 +00:00
/*
Whenever the user types something in the search box, we update the list
query.
*/
2012-06-19 12:18:45 +00:00
function find() {
2012-06-19 12:59:33 +00:00
ui.find = $find.options('value');
2012-06-19 12:18:45 +00:00
query = getQuery();
$status.html('Loading...');
2012-06-19 12:59:33 +00:00
$list.options({query: query});
}
2012-07-02 13:37:24 +00:00
/*
This function returns the options for the sort order button, which depend on
`ui.sort`.
*/
2012-06-19 12:59:33 +00:00
function getOptions() {
var operator = ui.sort[0].operator;
return {
title: operator == '+' ? 'up' : 'down',
2012-07-02 13:37:24 +00:00
tooltip: operator == '+' ? 'Ascending' : 'Descending',
type: 'image'
2012-06-19 12:59:33 +00:00
};
2012-06-19 12:18:45 +00:00
}
2012-07-02 13:37:24 +00:00
/*
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`.
*/
2012-06-19 12:18:45 +00:00
function getQuery() {
var query = {
2012-06-19 12:59:33 +00:00
conditions: [{key: 'name', operator: '=', value: ui.find}],
2012-06-19 12:18:45 +00:00
operator: '&'
};
2012-06-19 12:59:33 +00:00
Ox.forEach(ui.include, function(value, key) {
2012-06-19 12:18:45 +00:00
!value && query.conditions.push(
{key: key, operator: '=', value: void 0}
);
});
return query;
}
2012-07-02 13:37:24 +00:00
/*
A handler for the `change` event of the menu button.
*/
2012-06-19 12:18:45 +00:00
function include(data) {
2012-06-19 12:59:33 +00:00
ui.include[data.id] = data.checked;
2012-06-19 12:18:45 +00:00
find();
}
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
function init(data) {
$status.html(
(data.items || 'No') + ' countr'
+ (data.items == 1 ? 'y' : 'ies')
);
}
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:59:33 +00:00
function order() {
ui.sort[0].operator = ui.sort[0].operator == '+' ? '-' : '+';
$order.options(getOptions());
$list.options({sort: Ox.clone(ui.sort, true)});
2012-06-19 12:18:45 +00:00
}
2012-07-02 13:37:24 +00:00
/*
This renders the selected item. In grid view, we display all the country's
properties, in list view, we show a flag icon.
*/
2012-06-19 12:59:33 +00:00
function renderItem() {
var code = ui.selected[0];
2012-06-19 12:18:45 +00:00
$item.empty();
2012-06-19 13:06:57 +00:00
if (code) {
$item.append(
ui.view == 'grid'
? Ox.TreeList({
data: Ox.getCountryByCode(code),
scrollbarVisible: true,
width: itemSize
})
: Ox.Element('<img>')
.addClass('flag')
.attr({src: Ox.getFlagByCountryCode(code, 256)})
);
2012-06-19 12:18:45 +00:00
}
}
2012-07-02 13:37:24 +00:00
/*
...
*/
2012-06-19 12:59:33 +00:00
function renderList() {
return ui.view == 'grid'
? Ox.IconList({
borderRadius: 16,
fixedRatio: 1,
item: function(data, sort, size) {
var key = sort[0].key == 'name' ? 'code' : sort[0].key,
column = Ox.getObjectById(columns, key),
info = (column.format || Ox.identity)(data[key]);
return {
height: size,
id: data.id,
info: info,
title: data.name,
url: data.flag,
width: size
};
},
items: items,
keys: ['flag', 'name'],
max: 1,
pageLength: 1000,
query: query,
2012-06-29 12:19:54 +00:00
selectAsYouType: 'name',
2012-06-19 12:59:33 +00:00
selected: ui.selected,
size: 128,
sort: Ox.clone(ui.sort, true),
unique: 'code'
})
.bindEvent({
init: init,
select: select
})
2012-06-27 07:41:10 +00:00
: Ox.TableList({
2012-06-19 12:59:33 +00:00
columns: columns,
columnsMovable: true,
columnsRemovable: true,
columnsVisible: true,
items: items,
max: 1,
pageLength: 1000,
query: query,
2012-06-29 12:19:54 +00:00
selectAsYouType: 'name',
2012-06-19 12:59:33 +00:00
scrollbarVisible: true,
selected: ui.selected,
2012-06-29 12:19:54 +00:00
sort: Ox.clone(ui.sort, true),
unique: 'code'
2012-06-19 12:59:33 +00:00
})
.bindEvent({
init: init,
select: select,
sort: sort
});
}
2012-07-02 13:37:24 +00:00
/*
A handler for the `select` event of the list.
*/
2012-06-19 12:59:33 +00:00
function select(data) {
var id = data.ids[0];
if (id) {
ui.selected = [id];
data = $list.value(id);
$title.options({title: data.name}).show();
$deselect.show();
} else {
ui.selected = [];
$title.hide();
$deselect.hide();
}
2012-06-19 13:06:57 +00:00
renderItem();
2012-06-19 12:59:33 +00:00
}
2012-07-02 13:37:24 +00:00
/*
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.
*/
2012-06-19 12:18:45 +00:00
function sort(data) {
2012-06-19 12:59:33 +00:00
if (data.value) {
data = {
key: data.value,
operator: Ox.getObjectById(columns, data.value).operator
2012-07-02 13:37:24 +00:00
};
2012-06-19 12:59:33 +00:00
}
ui.sort = [{key: data.key, operator: data.operator}];
$sort.options({value: data.key});
$order.options(getOptions());
$list.options({sort: Ox.clone(ui.sort, true)});
2012-06-19 12:18:45 +00:00
}
2012-07-02 13:37:24 +00:00
/*
And a handler for the change event of the `view` switch.
*/
2012-06-19 12:18:45 +00:00
function view(data) {
2012-06-19 12:59:33 +00:00
ui.view = data.value;
$sort[ui.view == 'grid' ? 'show' : 'hide']();
$order[ui.view == 'grid' ? 'show' : 'hide']();
$list = renderList();
$listPanel.replaceElement(1, $list);
$list.gainFocus();
2012-06-19 13:06:57 +00:00
renderItem();
2012-06-19 12:18:45 +00:00
}
});