diff --git a/examples/countries/css/example.css b/examples/countries/css/example.css
new file mode 100644
index 00000000..a66d0161
--- /dev/null
+++ b/examples/countries/css/example.css
@@ -0,0 +1,24 @@
+.bar > .OxElement {
+ float: left;
+ margin: 4px 2px;
+}
+.bar > .OxElement.right {
+ float: right;
+}
+.bar > .OxElement:first-child {
+ margin-left: 4px;
+}
+.bar > .OxElement:last-child {
+ margin-right: 4px;
+}
+.flag {
+ border-radius: 32px;
+}
+.item {
+ padding: 16px;
+}
+.status {
+ margin-top: 3px;
+ font-size: 9px;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/examples/countries/index.html b/examples/countries/index.html
new file mode 100644
index 00000000..bc87b115
--- /dev/null
+++ b/examples/countries/index.html
@@ -0,0 +1,13 @@
+
+
+
+ Countries
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/countries/js/example.js b/examples/countries/js/example.js
new file mode 100644
index 00000000..13569f32
--- /dev/null
+++ b/examples/countries/js/example.js
@@ -0,0 +1,419 @@
+'use strict';
+
+Ox.load(['Geo', 'UI'], function() {
+
+ var items = Ox.COUNTRIES.map(function(country) {
+ return Ox.extend({}, country, {
+ flag: Ox.getFlagByCountryCode(country.code, 256),
+ icon: Ox.getFlagByCountryCode(country.code, 16),
+ region: country.continent + ', ' + country.region
+ });
+ }),
+
+ columns = [
+ {
+ format: function(value) {
+ return $('').attr({src: value}).css({
+ width: '14px',
+ height: '14px',
+ borderRadius: '2px',
+ margin: '0 0 0 -3px'
+ })
+ },
+ id: 'icon',
+ operator: '+',
+ title: 'Flag',
+ titleImage: 'flag',
+ visible: true,
+ width: 16
+ },
+ {
+ id: 'code',
+ operator: '+',
+ title: 'Code',
+ unique: true,
+ visible: true,
+ width: 64
+ },
+ {
+ id: 'name',
+ operator: '+',
+ removable: false,
+ title: 'Name',
+ visible: true,
+ width: 256
+ },
+ {
+ format: function(value) {
+ return value.split(', ')[0];
+ },
+ 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
+ },
+ {
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lat');
+ },
+ align: 'right',
+ id: 'lat',
+ operator: '-',
+ title: 'Latitude',
+ visible: true,
+ width: 80
+ },
+ {
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lng');
+ },
+ align: 'right',
+ id: 'lng',
+ operator: '+',
+ title: 'Longitude',
+ visible: true,
+ width: 80
+ },
+ {
+ align: 'right',
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lat');
+ },
+ id: 'south',
+ operator: '-',
+ title: 'South',
+ width: 80
+ },
+ {
+ align: 'right',
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lat');
+ },
+ id: 'north',
+ operator: '-',
+ title: 'North',
+ width: 80
+ },
+ {
+ align: 'right',
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lng');
+ },
+ id: 'west',
+ operator: '-',
+ title: 'West',
+ width: 80
+ },
+ {
+ align: 'right',
+ format: function(value) {
+ return Ox.formatDegrees(value, 'lng');
+ },
+ id: 'east',
+ operator: '-',
+ title: 'East',
+ width: 80
+ },
+ ],
+
+ itemSize = 288 + Ox.UI.SCROLLBAR_SIZE,
+
+ state = {
+ find: '',
+ include: {
+ dependency: false,
+ disputed: false,
+ dissolved: false,
+ exception: false
+ },
+ selected: [],
+ sort: [{key: 'name', operator: '+'}],
+ view: 'grid'
+ },
+
+ query = getQuery(),
+
+ $toolbar = Ox.Bar({size: 24}).addClass('bar'),
+
+ $view = Ox.ButtonGroup({
+ buttons: [
+ {id: 'grid', title: 'grid'},
+ {id: 'list', title: 'list'}
+ ],
+ selectable: true,
+ type: 'image',
+ value: state.view
+ })
+ .bindEvent({change: view})
+ .appendTo($toolbar),
+
+ $sort = Ox.Select({
+ items: [
+ {id: 'name', title: 'Sort by Name'},
+ {id: 'code', title: 'Sort by Code'},
+ {id: 'continent', title: 'Sort by Continent'},
+ {id: 'region', title: 'Sort by Region'},
+ {id: 'area', title: 'Sort by Area'},
+ {id: 'lat', title: 'Sort by Latitude'},
+ {id: 'lng', title: 'Sort by Longitude'}
+ ],
+ width: 128
+ })
+ .bindEvent({change: sort})
+ [state.view == 'grid' ? 'show' : 'hide']()
+ .appendTo($toolbar),
+
+ $order = Ox.Button({
+ title: state.sort[0].operator == '+' ? 'up' : 'down',
+ type: 'image'
+ })
+ .bindEvent({click: order})
+ [state.view == 'grid' ? 'show' : 'hide']()
+ .appendTo($toolbar),
+
+ $find = Ox.Input({
+ changeOnKeypress: true,
+ clear: true,
+ placeholder: 'Find',
+ width: 192
+ })
+ .addClass('right')
+ .bindEvent({change: find})
+ .appendTo($toolbar),
+
+ $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),
+
+ $list = {
+
+ 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,
+ selected: state.selected,
+ size: 128,
+ sort: Ox.clone(state.sort),
+ unique: 'code'
+ })
+ .bindEvent({
+ init: init,
+ select: select
+ }),
+
+ list: Ox.TextList({
+ columns: columns,
+ columnsMovable: true,
+ columnsRemovable: true,
+ columnsVisible: true,
+ items: items,
+ max: 1,
+ pageLength: 1000,
+ query: query,
+ scrollbarVisible: true,
+ sort: Ox.clone(state.sort)
+ })
+ .bindEvent({
+ init: init,
+ select: select
+ })
+
+ },
+
+ $statusbar = Ox.Bar({size: 16}),
+
+ $status = Ox.Element().addClass('status').appendTo($statusbar),
+
+ $listPanel = Ox.SplitPanel({
+ elements: [
+ {element: $toolbar, size: 24},
+ {element: $list[state.view]},
+ {element: $statusbar, size: 16}
+ ],
+ orientation: 'vertical'
+ }),
+
+ $titlebar = Ox.Bar({size: 24}).addClass('bar'),
+
+ $title = Ox.Label({
+ width: itemSize - 28
+ })
+ .hide()
+ .appendTo($titlebar),
+
+ $deselect = Ox.Button({
+ title: 'close',
+ type: 'image'
+ })
+ .bindEvent({
+ click: function() {
+ $list[state.view].options({selected: []});
+ select({ids: []});
+ }
+ })
+ .hide()
+ .appendTo($titlebar),
+
+ $item = Ox.Element().addClass('item'),
+
+ $itemPanel = Ox.SplitPanel({
+ elements: [
+ {element: $titlebar, size: 24},
+ {element: $item},
+ {element: Ox.Bar({size: 16}), size: 16}
+ ],
+ orientation: 'vertical'
+ }),
+
+ $mainPanel = Ox.SplitPanel({
+ elements: [
+ {element: $listPanel},
+ {element: $itemPanel, size: itemSize}
+ ],
+ orientation: 'horizontal'
+ })
+ .appendTo(Ox.$body);
+
+ function find() {
+ state.find = $find.options('value');
+ query = getQuery();
+ $status.html('Loading...');
+ $list[state.view].options({query: query});
+ }
+
+ function getQuery() {
+ var query = {
+ conditions: [{key: 'name', operator: '=', value: state.find}],
+ operator: '&'
+ };
+ Ox.forEach(state.include, function(value, key) {
+ !value && query.conditions.push(
+ {key: key, operator: '=', value: void 0}
+ );
+ });
+ return query;
+ }
+
+ function include(data) {
+ state.include[data.id] = data.checked;
+ find();
+ }
+
+ function init(data) {
+ $status.html(
+ (data.items || 'No') + ' countr'
+ + (data.items == 1 ? 'y' : 'ies')
+ );
+ }
+
+ function order(data) {
+ state.sort[0].operator = state.sort[0].operator == '+' ? '-' : '+';
+ $order.options({title: state.sort[0].operator == '+' ? 'up' : 'down'});
+ $list[state.view].options({sort: Ox.clone(state.sort)});
+ }
+
+ function select(data) {
+ Ox.print('SELECT', data)
+ var id = data.ids[0];
+ if (id) {
+ state.selected = [id];
+ data = $list[state.view].value(id);
+ Ox.print(state.view, 'DATA:', data, id)
+ $title.options({title: data.name}).show();
+ $deselect.show();
+ showItem();
+ } else {
+ state.selected = [];
+ $title.hide();
+ $deselect.hide();
+ $item.empty();
+ }
+ }
+
+ function showItem() {
+ var code = state.selected[0];
+ $item.empty();
+ if (state.view == 'grid') {
+ Ox.TreeList({
+ data: Ox.getCountryByCode(code),
+ scrollbarVisible: true,
+ width: itemSize
+ })
+ .appendTo($item);
+ } else {
+ Ox.Element('')
+ .addClass('flag')
+ .attr({src: Ox.getFlagByCountryCode(code, 256)})
+ .appendTo($item);
+ }
+ }
+
+ function sort(data) {
+ state.sort = [{
+ key: data.value,
+ operator: Ox.getObjectById(columns, data.value).operator
+ }];
+ $order.options({title: state.sort[0].operator == '+' ? 'up' : 'down'});
+ $list[state.view].options({sort: state.sort});
+ }
+
+ function view(data) {
+ state.view = data.value;
+ $sort[state.view == 'grid' ? 'show' : 'hide']();
+ $order[state.view == 'grid' ? 'show' : 'hide']();
+ $listPanel.replaceElement(1, $list[state.view].options({
+ selected: state.selected
+ }));
+ }
+
+});
\ No newline at end of file