diff --git a/examples/lists/cities/js/example.js b/examples/lists/cities/js/example.js index 952378b0..ca8c29a5 100644 --- a/examples/lists/cities/js/example.js +++ b/examples/lists/cities/js/example.js @@ -1,21 +1,24 @@ /* -... +In this example, we will build a list of cities that interacts with a map. */ - '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() { + /* + We load the list of cities. + */ Ox.getJSON('json/cities.json', function(cities) { /* The JSON data originally comes from - geonames.org. - It's an array of 10,000 city objects, each of which has the following - properties: + geonames.org. It's an array of 10,000 city objects, + each of which has the following properties:
{
"country_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({
disabled: true,
selectable: true,
@@ -114,6 +122,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
.bindEvent({
submit: function(data) {
$list.options({
+ /*
+ This query will find matches in either `name`,
+ `region` or `continent`.
+ */
query: {
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})
.attr({id: 'toolbar'})
.append($preview)
.append($find),
+ /*
+ This is our list.
+ */
$list = Ox.TableList({
+ /*
+ First of all, we define the columns.
+ */
columns: [
{
/*
We don't want to display the id, so we omit the
- visible attribute, which defaults to false. We still
- have to include the id here, since is the unique key
- of our table. In consequence, whenever the list
- fires a select event, it will reference this value
- as the item's id.
+ `visible` attribute, which defaults to `false`. We
+ still have to include the id in the list of columns,
+ since it's the unique key of our table. In
+ consequence, whenever the list fires a `select`
+ event, it will reference this value as the item's
+ id.
*/
id: 'id',
operator: '+',
@@ -160,9 +182,9 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
},
{
/*
- We use the format function to display the region as
- a colored icon with a tooltip. The region class is
- added to apply our own custom CSS, and
+ We use the `format` function to display the region
+ as a colored icon with a tooltip. The region class
+ is added to apply our own custom CSS, and
Ox.getGeoColor returns a color for the region. Note
that the actual value is 'Continent, Region,
Country', which results in a nicer sort order than
@@ -188,7 +210,7 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
operator: '+',
/*
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
with Ox.UI. The column still needs a textual title,
to be displayed in the menu that allows to show or
@@ -223,6 +245,9 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 16
},
{
+ /*
+ If `capital` is `true`, we display a star.
+ */
format: function(value) {
return value
? Ox.Element({
@@ -248,7 +273,7 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
contains the values of all columns. This allows us
to format a value dependent on other values. In this
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) {
return data.capital
@@ -283,10 +308,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 80
},
{
+ align: 'right',
format: function(value) {
return Ox.formatDegrees(value, 'lat');
},
- align: 'right',
id: 'lat',
operator: '-',
title: 'Latitude',
@@ -294,10 +319,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 80
},
{
+ align: 'right',
format: function(value) {
return Ox.formatDegrees(value, 'lng');
},
- align: 'right',
id: 'lng',
operator: '+',
title: 'Longitude',
@@ -305,10 +330,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
width: 80
},
{
+ align: 'right',
format: function(value) {
return Ox.formatNumber(value) + ' m';
},
- align: 'right',
id: 'elevation',
operator: '-',
title: 'Elevation',
@@ -327,25 +352,47 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
This makes sure the column titles get displayed
*/
columnsVisible: true,
+ /*
+ We pass our array of cities of `items`.
+ */
items: cities,
/*
FIXME: Actually, 'keys' doesn't make so much sense when the
API is local, since the list has been passed all data anyway
*/
keys: ['capital'],
+ /*
+ We don't want to allow simulaneous selection of multiple
+ items, so we set `max` to `1`.
+ */
max: 1,
scrollbarVisible: true,
/*
We have to specify the default sort order.
*/
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']
})
.bindEvent({
+ /*
+ The `closepreview` event fires when the user presses `space`
+ while preview is active. See `openpreview`, below.
+ */
closepreview: function() {
$preview.options({value: false});
$dialog.close();
},
+ /*
+ On `init`, we display the number of cities and the total
+ population.
+ */
init: function(data) {
$status.html(
(data.items ? Ox.formatNumber(data.items) : 'No')
@@ -357,21 +404,32 @@ 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) {
$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) {
var item = Ox.getObjectById(cities, data.ids[0]);
- $flagImage = $('
')
- .attr({
- src: Ox.getFlagByGeoname(item.country, 256)
- });
+ $flagImage = $('
').attr({
+ src: Ox.getFlagByGeoname(item.country, 256)
+ });
$mapImage = Ox.MapImage({
- height: 256,
- markers: [item],
- place: Ox.getCountryByGeoname(item.country),
- width: 256
- });
+ height: 256,
+ markers: [item],
+ place: Ox.getCountryByGeoname(item.country),
+ width: 256
+ });
setImageSizes();
$preview.options({value: true});
$dialog.options({
@@ -382,6 +440,14 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
title: [item.name, item.country].join(', ')
}).open();
},
+ /*
+ The `select` event passes an array of selected ids —
+ 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) {
$preview.options({disabled: data.ids.length == 0});
$map.options({selected: data.ids[0]}).panToPlace();
@@ -390,6 +456,11 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
$flagImage,
$mapImage,
$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({
closeButton: true,
content: $content,
@@ -412,15 +483,32 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
setImageSizes();
}
}),
+ /*
+ The status bar displays the list's totals.
+ */
$status = $('').css({
margin: '3px',
fontSize: '9px',
textAlign: 'center'
}),
$statusbar = Ox.Bar({size: 16}).append($status),
+ /*
+ Now we create the 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,
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) {
return place.population === void 0 ? [128, 128, 128]
: place.population >= 10000000 ? [255, 0, 0]
@@ -445,18 +533,35 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
: place.population >= 50000 ? 10
: 8;
},
+ /*
+ We pass our array of cities of `places`.
+ */
places: cities,
+ /*
+ Finally, we enable a number of interface elements.
+ */
showControls: true,
showToolbar: true,
showZoombar: true
})
.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) {
$list.options({
selected: data.place ? [data.place.id] : []
});
}
}),
+ /*
+ The list panel holds the toolbar, the list, and the statusbar.
+ */
$listPanel = Ox.SplitPanel({
elements: [
{element: $toolbar, size: 24},
@@ -465,9 +570,17 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
],
orientation: 'vertical'
}),
+ /*
+ The main panel holds the list panel and the map.
+ */
$mainPanel = Ox.SplitPanel({
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({
resize: function(data) {
$find.options({
@@ -478,12 +591,23 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
}
}),
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) {
return size + 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({
resizeend: $map.resizeMap
})
@@ -493,6 +617,10 @@ Ox.load({UI: {showScreen: true}, Geo: {}}, function() {
})
.appendTo(Ox.$body);
+ /*
+ Helper function that sets the flag and map image sizes when the preview
+ dialog is initialized, or resized.
+ */
function setImageSizes() {
var size = Math.floor(($dialog.options('width') - 64) / 2);
[$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});
});