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}); });