update cities example

This commit is contained in:
rolux 2012-06-27 17:46:12 +02:00
parent 83776fec5d
commit 27ea70784c

View file

@ -1,21 +1,24 @@
/* /*
... In this example, we will build a list of cities that interacts with a map.
*/ */
'use strict'; 'use strict';
/* /*
Since we will be doing some mapping, we have to load the Geo module. We load the `UI` and `Geo` modules. The latter provides a number of methods to
retrieve geographic and political information about countries.
*/ */
Ox.load({UI: {showScreen: true}, Geo: {}}, function() { Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
/*
We load the list of cities.
*/
Ox.getJSON('json/cities.json', function(cities) { Ox.getJSON('json/cities.json', function(cities) {
/* /*
The JSON data originally comes from The JSON data originally comes from
<a href="http://download.geonames.org/export/dump/cities1000.zip">geonames.org</a>. <a href="http://download.geonames.org/export/dump"
It's an array of 10,000 city objects, each of which has the following target="_blank">geonames.org</a>. It's an array of 10,000 city objects,
properties: each of which has the following properties:
<pre> <pre>
{ {
"country&#95;code": "CN", "country&#95;code": "CN",
@ -92,6 +95,11 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}; };
}); });
/*
The preview button opens or closes the preview dialog (which we will
create later). It does so by calling the `openPreview` or `closePreview`
method of the list (which we will also create in a moment).
*/
var $preview = Ox.Button({ var $preview = Ox.Button({
disabled: true, disabled: true,
selectable: true, selectable: true,
@ -114,6 +122,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
.bindEvent({ .bindEvent({
submit: function(data) { submit: function(data) {
$list.options({ $list.options({
/*
This query will find matches in either `name`,
`region` or `continent`.
*/
query: { query: {
conditions: data.value ? [ conditions: data.value ? [
{ {
@ -137,20 +149,30 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}); });
} }
}), }),
/*
The toolbar holds the preview button and the find element.
*/
$toolbar = Ox.Bar({size: 24}) $toolbar = Ox.Bar({size: 24})
.attr({id: 'toolbar'}) .attr({id: 'toolbar'})
.append($preview) .append($preview)
.append($find), .append($find),
/*
This is our list.
*/
$list = Ox.TableList({ $list = Ox.TableList({
/*
First of all, we define the columns.
*/
columns: [ columns: [
{ {
/* /*
We don't want to display the id, so we omit the We don't want to display the id, so we omit the
visible attribute, which defaults to false. We still `visible` attribute, which defaults to `false`. We
have to include the id here, since is the unique key still have to include the id in the list of columns,
of our table. In consequence, whenever the list since it's the unique key of our table. In
fires a select event, it will reference this value consequence, whenever the list fires a `select`
as the item's id. event, it will reference this value as the item's
id.
*/ */
id: 'id', id: 'id',
operator: '+', operator: '+',
@ -160,9 +182,9 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}, },
{ {
/* /*
We use the format function to display the region as We use the `format` function to display the region
a colored icon with a tooltip. The region class is as a colored icon with a tooltip. The region class
added to apply our own custom CSS, and is added to apply our own custom CSS, and
Ox.getGeoColor returns a color for the region. Note Ox.getGeoColor returns a color for the region. Note
that the actual value is 'Continent, Region, that the actual value is 'Continent, Region,
Country', which results in a nicer sort order than Country', which results in a nicer sort order than
@ -188,7 +210,7 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
operator: '+', operator: '+',
/* /*
We want the column title to be a symbol, so we pass We want the column title to be a symbol, so we pass
the 'icon' symbol at a titleImage. We can pick the 'icon' symbol as the `titleImage`. We can pick
anything from the collection of symbols that comes anything from the collection of symbols that comes
with Ox.UI. The column still needs a textual title, with Ox.UI. The column still needs a textual title,
to be displayed in the menu that allows to show or to be displayed in the menu that allows to show or
@ -223,6 +245,9 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 16 width: 16
}, },
{ {
/*
If `capital` is `true`, we display a star.
*/
format: function(value) { format: function(value) {
return value return value
? Ox.Element({ ? Ox.Element({
@ -248,7 +273,7 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
contains the values of all columns. This allows us contains the values of all columns. This allows us
to format a value dependent on other values. In this to format a value dependent on other values. In this
case, we want to display the name in bold if the case, we want to display the name in bold if the
value for capital is true. value for capital is `true`.
*/ */
format: function(value, data) { format: function(value, data) {
return data.capital return data.capital
@ -283,10 +308,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 80 width: 80
}, },
{ {
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',
@ -294,10 +319,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, 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',
@ -305,10 +330,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 80 width: 80
}, },
{ {
align: 'right',
format: function(value) { format: function(value) {
return Ox.formatNumber(value) + ' m'; return Ox.formatNumber(value) + ' m';
}, },
align: 'right',
id: 'elevation', id: 'elevation',
operator: '-', operator: '-',
title: 'Elevation', title: 'Elevation',
@ -327,25 +352,47 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
This makes sure the column titles get displayed This makes sure the column titles get displayed
*/ */
columnsVisible: true, columnsVisible: true,
/*
We pass our array of cities of `items`.
*/
items: cities, items: cities,
/* /*
FIXME: Actually, 'keys' doesn't make so much sense when the FIXME: Actually, 'keys' doesn't make so much sense when the
API is local, since the list has been passed all data anyway API is local, since the list has been passed all data anyway
*/ */
keys: ['capital'], keys: ['capital'],
/*
We don't want to allow simulaneous selection of multiple
items, so we set `max` to `1`.
*/
max: 1, max: 1,
scrollbarVisible: true, scrollbarVisible: true,
/* /*
We have to specify the default sort order. We have to specify the default sort order.
*/ */
sort: ['-population', '+name'], sort: ['-population', '+name'],
/*
When the list retrieves items, it fires an `init` event. By
default, this event has an `items` property, which is the
number of items. Via `sums`, we can add more properties.
In this case, the `init` event will have a `population`
property that is the sum of the population of all items.
*/
sums: ['population'] sums: ['population']
}) })
.bindEvent({ .bindEvent({
/*
The `closepreview` event fires when the user presses `space`
while preview is active. See `openpreview`, below.
*/
closepreview: function() { closepreview: function() {
$preview.options({value: false}); $preview.options({value: false});
$dialog.close(); $dialog.close();
}, },
/*
On `init`, we display the number of cities and the total
population.
*/
init: function(data) { init: function(data) {
$status.html( $status.html(
(data.items ? Ox.formatNumber(data.items) : 'No') (data.items ? Ox.formatNumber(data.items) : 'No')
@ -357,13 +404,24 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
) )
); );
}, },
/*
The `open` event fires when the user doubleclicks an item,
or presses `enter` while an item is selected. In this case,
we want the map to zoom to the selected place.
*/
open: function(data) { open: function(data) {
$map.zoomToPlace(); $map.zoomToPlace();
}, },
/*
The `openpreview` event fires when an item is selected and
the user presses `space`. It can be used to implement
functionality similar to the "QuickView" feature in the Mac
OS X Finder. In this case, we open a dialog that shows a
flag and a map.
*/
openpreview: function(data) { openpreview: function(data) {
var item = Ox.getObjectById(cities, data.ids[0]); var item = Ox.getObjectById(cities, data.ids[0]);
$flagImage = $('<img>') $flagImage = $('<img>').attr({
.attr({
src: Ox.getFlagByGeoname(item.country, 256) src: Ox.getFlagByGeoname(item.country, 256)
}); });
$mapImage = Ox.MapImage({ $mapImage = Ox.MapImage({
@ -382,6 +440,14 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
title: [item.name, item.country].join(', ') title: [item.name, item.country].join(', ')
}).open(); }).open();
}, },
/*
The `select` event passes an array of selected ids &mdash;
either one, as defined above, or none. We enable or disable
the preview button accordingly. Then we set the `selected`
option of the map to the selected id (or to `undefined`,
which will cause a deselect), and pan to that place (which
will do nothing if no place is selected).
*/
select: function(data) { select: function(data) {
$preview.options({disabled: data.ids.length == 0}); $preview.options({disabled: data.ids.length == 0});
$map.options({selected: data.ids[0]}).panToPlace(); $map.options({selected: data.ids[0]}).panToPlace();
@ -390,6 +456,11 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
$flagImage, $flagImage,
$mapImage, $mapImage,
$content = Ox.Element(), $content = Ox.Element(),
/*
This is the preview dialog. By setting `focus` to `false`, we make
it non-modal, i.e. the user can still interact with the rest of the
application while the dialog is open.
*/
$dialog = Ox.Dialog({ $dialog = Ox.Dialog({
closeButton: true, closeButton: true,
content: $content, content: $content,
@ -412,15 +483,32 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
setImageSizes(); setImageSizes();
} }
}), }),
/*
The status bar displays the list's totals.
*/
$status = $('<div>').css({ $status = $('<div>').css({
margin: '3px', margin: '3px',
fontSize: '9px', fontSize: '9px',
textAlign: 'center' textAlign: 'center'
}), }),
$statusbar = Ox.Bar({size: 16}).append($status), $statusbar = Ox.Bar({size: 16}).append($status),
/*
Now we create the map.
*/
$map = Ox.Map({ $map = Ox.Map({
/*
When `clickable` is `true`, clicking on the map will perform
a reverse geo lookup and select the matching geographic
entity.
*/
clickable: true, clickable: true,
keys: ['population'], keys: ['population'],
/*
Here, we add custom marker colors and sizes, depending on
population. Note that we have to handle `void 0` too, since
by clicking on the map, or using the map's find element, the
user may select a place that is not one of our cities.
*/
markerColor: function(place) { markerColor: function(place) {
return place.population === void 0 ? [128, 128, 128] return place.population === void 0 ? [128, 128, 128]
: place.population >= 10000000 ? [255, 0, 0] : place.population >= 10000000 ? [255, 0, 0]
@ -445,18 +533,35 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
: place.population >= 50000 ? 10 : place.population >= 50000 ? 10
: 8; : 8;
}, },
/*
We pass our array of cities of `places`.
*/
places: cities, places: cities,
/*
Finally, we enable a number of interface elements.
*/
showControls: true, showControls: true,
showToolbar: true, showToolbar: true,
showZoombar: true showZoombar: true
}) })
.bindEvent({ .bindEvent({
/*
The `select` event fires when a place is selected or
deselected. We set the `selected` option of the list to the
selected id, wrapped in an array. (Note that if the selected
place is not one of our cities, it will have a temporary id
that doesn't exist in our list. Selecting a non-existent id
will cause a deselect, which is what we want here.)
*/
select: function(data) { select: function(data) {
$list.options({ $list.options({
selected: data.place ? [data.place.id] : [] selected: data.place ? [data.place.id] : []
}); });
} }
}), }),
/*
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},
@ -465,9 +570,17 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
], ],
orientation: 'vertical' orientation: 'vertical'
}), }),
/*
The main panel holds the list panel and the map.
*/
$mainPanel = Ox.SplitPanel({ $mainPanel = Ox.SplitPanel({
elements: [ elements: [
{ {
/*
Elements of a split panel trigger `resize` and
`resizeend` events when they are resized. Here, we
make sure that the find element shrinks accordingly.
*/
element: $listPanel.bindEvent({ element: $listPanel.bindEvent({
resize: function(data) { resize: function(data) {
$find.options({ $find.options({
@ -478,12 +591,23 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
} }
}), }),
resizable: true, resizable: true,
/*
The `resize` option is usually `[min, max]`, but by
specifying additional values, we make the panel
"snappy" at these points. Here, the points are the
positions of our list columns.
*/
resize: [176, 256, 336, 416, 496].map(function(size) { resize: [176, 256, 336, 416, 496].map(function(size) {
return size + Ox.UI.SCROLLBAR_SIZE; return size + Ox.UI.SCROLLBAR_SIZE;
}), }),
size: 416 + Ox.UI.SCROLLBAR_SIZE size: 416 + Ox.UI.SCROLLBAR_SIZE
}, },
{ {
/*
The map uses the Google Maps API, which requires a
notification when the map size changes. The map's
`resizeMap` method takes care of that.
*/
element: $map.bindEvent({ element: $map.bindEvent({
resizeend: $map.resizeMap resizeend: $map.resizeMap
}) })
@ -493,6 +617,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}) })
.appendTo(Ox.$body); .appendTo(Ox.$body);
/*
Helper function that sets the flag and map image sizes when the preview
dialog is initialized, or resized.
*/
function setImageSizes() { function setImageSizes() {
var size = Math.floor(($dialog.options('width') - 64) / 2); var size = Math.floor(($dialog.options('width') - 64) / 2);
[$flagImage, $mapImage].forEach(function($image) { [$flagImage, $mapImage].forEach(function($image) {
@ -500,6 +628,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}); });
} }
/*
When the window size changes, the map size changes too, so we have to
notify the map.
*/
Ox.$window.bind({resize: $map.resizeMap}); Ox.$window.bind({resize: $map.resizeMap});
}); });