Rebaseline-server: sorting of columns in asc/desc order in frontend.
authorstephana <stephana@google.com>
Fri, 8 Aug 2014 14:21:00 +0000 (07:21 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 8 Aug 2014 14:21:00 +0000 (07:21 -0700)
NOTRY=true

BUG=skia:1907
R=epoger@google.com

Author: stephana@google.com

Review URL: https://codereview.chromium.org/449843002

gm/rebaseline_server/static/constants.js
gm/rebaseline_server/static/live-loader.js
gm/rebaseline_server/static/live-view.html
gm/rebaseline_server/static/loader.js
gm/rebaseline_server/static/view.css
gm/rebaseline_server/static/view.html

index 150c895..c397452 100644 (file)
@@ -80,5 +80,9 @@ module.constant('constants', (function() {
     KEY__IMAGEPAIRS__ROWSPAN: 'rowspan',
     URL_KEY__SCHEMA_VERSION: 'urlSchemaVersion',
     URL_VALUE__SCHEMA_VERSION__CURRENT: 1,
+
+    // Utility constants only used on the client side. 
+    ASC: 'asc',
+    DESC: 'desc',
   }
 })())
index 0080068..16a19aa 100644 (file)
@@ -133,6 +133,9 @@ Loader.controller(
     $scope.setBSection = $location.search().setBSection;
     $scope.loadingMessage = "please wait...";
 
+    var currSortAsc = true; 
+
+
     /**
      * On initial page load, load a full dictionary of results.
      * Once the dictionary is loaded, unhide the page elements so they can
@@ -175,8 +178,11 @@ Loader.controller(
           $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER];
           $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS];
           $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS];
+
+          // set the default sort column and make it ascending.
           $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES;
           $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF;
+          currSortAsc = true;
 
           $scope.showSubmitAdvancedSettings = false;
           $scope.submitAdvancedSettings = {};
@@ -610,14 +616,7 @@ Loader.controller(
       // array copies?  (For better performance.)
 
       if ($scope.viewingTab == $scope.defaultTab) {
-
-        // TODO(epoger): Until we allow the user to reverse sort order,
-        // there are certain columns we want to sort in a different order.
-        var doReverse = (
-            ($scope.sortColumnKey ==
-             constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS) ||
-            ($scope.sortColumnKey ==
-             constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF));
+        var doReverse = !currSortAsc;
 
         $scope.filteredImagePairs =
             $filter("orderBy")(
index 5292f3b..2f8241e 100644 (file)
@@ -5,7 +5,7 @@
 <head>
   <title ng-bind="windowTitle"></title>
   <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
-  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.js"></script>
   <script src="constants.js"></script>
   <script src="live-loader.js"></script>
   <script src="utils.js"></script>
         <tr>
           <!-- Most column headers are displayed in a common fashion... -->
           <th ng-repeat="columnName in orderedColumnNames">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="{{columnName}}"
-                   ng-checked="(sortColumnKey == columnName)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)">
-            {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}
+              <a ng-class="'sort-' + sortedByColumnsCls(columnName)"
+                 ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)"
+                 href=""
+                 class="sortable-header">
+               {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}
+             </a>
           </th>
           <!-- ... but there are a few columns where we display things differently. -->
           <th>
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="bugs"
-                   ng-checked="(sortColumnKey == constants.KEY__EXPECTATIONS__BUGS)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)">
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__EXPECTATIONS__BUGS)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)"
+               href=""
+               class="sortable-header">
+                  bugs
+            </a>
             bugs
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="imageA"
-                   ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
-                   ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)">
-            {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
+               ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
+               href=""
+               class="sortable-header">
+                   {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="imageB"
-                   ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
-                   ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)">
-            {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
+               ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
+               href=""
+               class="sortable-header">
+                  {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="percentDifferingPixels"
-                   ng-checked="(sortColumnKey == constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)">
-            differing pixels in white
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
+               href=""
+               class="sortable-header">
+                  differing pixels in white
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="perceptualDiff"
-                   ng-checked="(sortColumnKey == constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)">
-            perceptual difference
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
+               href=""
+               class="sortable-header">
+               perceptual difference
+            </a>
             <br>
             <input type="range" ng-model="pixelDiffBgColorBrightness"
                    ng-init="pixelDiffBgColorBrightness=64; pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)"
index 1ec7305..e6030ad 100644 (file)
@@ -130,6 +130,9 @@ Loader.controller(
     $scope.resultsToLoad = $location.search().resultsToLoad;
     $scope.loadingMessage = "please wait...";
 
+    var currSortAsc = true; 
+
+
     /**
      * On initial page load, load a full dictionary of results.
      * Once the dictionary is loaded, unhide the page elements so they can
@@ -167,8 +170,11 @@ Loader.controller(
           $scope.orderedColumnNames = data[constants.KEY__ROOT__EXTRACOLUMNORDER];
           $scope.imagePairs = data[constants.KEY__ROOT__IMAGEPAIRS];
           $scope.imageSets = data[constants.KEY__ROOT__IMAGESETS];
+
+          // set the default sort column and make it ascending.
           $scope.sortColumnSubdict = constants.KEY__IMAGEPAIRS__DIFFERENCES;
           $scope.sortColumnKey = constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF;
+          currSortAsc = true;
 
           $scope.showSubmitAdvancedSettings = false;
           $scope.submitAdvancedSettings = {};
@@ -320,7 +326,7 @@ Loader.controller(
           $scope.toggleValueInArray(index, $scope.selectedImagePairs);
         }
       }
-    }
+    };
 
     /**
      * Deselect all currently showing tests.
@@ -333,7 +339,7 @@ Loader.controller(
           $scope.toggleValueInArray(index, $scope.selectedImagePairs);
         }
       }
-    }
+    };
 
     /**
      * Toggle selection of all currently showing tests.
@@ -344,7 +350,7 @@ Loader.controller(
         var index = $scope.limitedImagePairs[i].index;
         $scope.toggleValueInArray(index, $scope.selectedImagePairs);
       }
-    }
+    };
 
     /**
      * Toggle selection state of a subset of the currently showing tests.
@@ -359,7 +365,7 @@ Loader.controller(
         var index = $scope.limitedImagePairs[i].index;
         $scope.toggleValueInArray(index, $scope.selectedImagePairs);
       }
-    }
+    };
 
 
     //
@@ -374,7 +380,7 @@ Loader.controller(
     $scope.setViewingTab = function(tab) {
       $scope.viewingTab = tab;
       $scope.updateResults();
-    }
+    };
 
     /**
      * Move the imagePairs in $scope.selectedImagePairs to a different tab,
@@ -386,7 +392,7 @@ Loader.controller(
       $scope.moveImagePairsToTab($scope.selectedImagePairs, newTab);
       $scope.selectedImagePairs = [];
       $scope.updateResults();
-    }
+    };
 
     /**
      * Move a subset of $scope.imagePairs to a different tab.
@@ -404,7 +410,7 @@ Loader.controller(
         $scope.imagePairs[imagePairIndex].tab = newTab;
       }
       $scope.numResultsPerTab[newTab] += numImagePairs;
-    }
+    };
 
 
     //
@@ -597,16 +603,8 @@ Loader.controller(
       // another copy of the array.  Is there a way we can filter out
       // the imagePairs as they are displayed, rather than storing multiple
       // array copies?  (For better performance.)
-
       if ($scope.viewingTab == $scope.defaultTab) {
-
-        // TODO(epoger): Until we allow the user to reverse sort order,
-        // there are certain columns we want to sort in a different order.
-        var doReverse = (
-            ($scope.sortColumnKey ==
-             constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS) ||
-            ($scope.sortColumnKey ==
-             constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF));
+        var doReverse = !currSortAsc;
 
         $scope.filteredImagePairs =
             $filter("orderBy")(
@@ -616,7 +614,8 @@ Loader.controller(
                     $scope.showingColumnValues,
                     $scope.viewingTab
                 ),
-                [$scope.getSortColumnValue, $scope.getSecondOrderSortValue],
+                // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue],
+                $scope.getSortColumnValue,
                 doReverse);
         $scope.limitedImagePairs = $filter("mergeAndLimit")(
             $scope.filteredImagePairs, $scope.displayLimit, $scope.mergeIdenticalRows);
@@ -628,7 +627,8 @@ Loader.controller(
                     {tab: $scope.viewingTab},
                     true
                 ),
-                [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]);
+                // [$scope.getSortColumnValue, $scope.getSecondOrderSortValue]);
+                $scope.getSortColumnValue);
         $scope.limitedImagePairs = $filter("mergeAndLimit")(
             $scope.filteredImagePairs, -1, $scope.mergeIdenticalRows);
       }
@@ -645,7 +645,7 @@ Loader.controller(
     $scope.resultsUpdatedCallback = function() {
       $scope.renderEndTime = window.performance.now();
       $log.debug("renderEndTime: " + $scope.renderEndTime);
-    }
+    };
 
     /**
      * Re-sort the displayed results.
@@ -656,10 +656,33 @@ Loader.controller(
      * @param key (string): sort by value associated with this key in subdict
      */
     $scope.sortResultsBy = function(subdict, key) {
-      $scope.sortColumnSubdict = subdict;
-      $scope.sortColumnKey = key;
+      // if we are already sorting by this column then toggle between asc/desc
+      if ((subdict === $scope.sortColumnSubdict) && ($scope.sortColumnKey === key)) {
+        currSortAsc = !currSortAsc;
+      } else {
+        $scope.sortColumnSubdict = subdict;
+        $scope.sortColumnKey = key;
+        currSortAsc = true; 
+      }
       $scope.updateResults();
-    }
+    };
+
+    /**
+     * Returns ASC or DESC (from constants) if currently the data
+     * is sorted by the provided column. 
+     *
+     * @param colName: name of the column for which we need to get the class.
+     */
+
+    $scope.sortedByColumnsCls = function (colName) {
+      if ($scope.sortColumnKey !== colName) {
+        return '';
+      }
+
+      var result = (currSortAsc) ? constants.ASC : constants.DESC;
+      console.log("sort class:", result);
+      return result;
+    };
 
     /**
      * For a particular ImagePair, return the value of the column we are
@@ -676,7 +699,7 @@ Loader.controller(
       } else {
         return undefined;
       }
-    }
+    };
 
     /**
      * For a particular ImagePair, return the value we use for the
@@ -691,7 +714,7 @@ Loader.controller(
     $scope.getSecondOrderSortValue = function(imagePair) {
       return imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" +
           imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
-    }
+    };
 
     /**
      * Set $scope.columnStringMatch[name] = value, and update results.
@@ -702,7 +725,7 @@ Loader.controller(
     $scope.setColumnStringMatch = function(name, value) {
       $scope.columnStringMatch[name] = value;
       $scope.updateResults();
-    }
+    };
 
     /**
      * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName]
@@ -717,7 +740,7 @@ Loader.controller(
       $scope.showingColumnValues[columnName] = {};
       $scope.toggleValueInSet(columnValue, $scope.showingColumnValues[columnName]);
       $scope.updateResults();
-    }
+    };
 
     /**
      * Update $scope.showingColumnValues[columnName] and $scope.columnStringMatch[columnName]
@@ -732,7 +755,7 @@ Loader.controller(
       $scope.toggleValuesInSet($scope.allColumnValues[columnName],
                                $scope.showingColumnValues[columnName]);
       $scope.updateResults();
-    }
+    };
 
 
     //
@@ -842,7 +865,7 @@ Loader.controller(
             "Please see server-side log for details.");
         $scope.submitPending = false;
       });
-    }
+    };
 
 
     //
@@ -860,7 +883,7 @@ Loader.controller(
      */
     $scope.setSize = function(set) {
       return Object.keys(set).length;
-    }
+    };
 
     /**
      * Returns true if value "value" is present within set "set".
@@ -871,7 +894,7 @@ Loader.controller(
      */
     $scope.isValueInSet = function(value, set) {
       return (true == set[value]);
-    }
+    };
 
     /**
      * If value "value" is already in set "set", remove it; otherwise, add it.
@@ -885,7 +908,7 @@ Loader.controller(
       } else {
         set[value] = true;
       }
-    }
+    };
 
     /**
      * For each value in valueArray, call toggleValueInSet(value, set).
@@ -898,7 +921,7 @@ Loader.controller(
       for (var i = 0; i < arrayLength; i++) {
         $scope.toggleValueInSet(valueArray[i], set);
       }
-    }
+    };
 
 
     //
@@ -915,7 +938,7 @@ Loader.controller(
      */
     $scope.isValueInArray = function(value, array) {
       return (-1 != array.indexOf(value));
-    }
+    };
 
     /**
      * If value "value" is already in array "array", remove it; otherwise,
@@ -931,7 +954,7 @@ Loader.controller(
       } else {
         array.splice(i, 1);
       }
-    }
+    };
 
 
     //
@@ -959,7 +982,7 @@ Loader.controller(
         slice.push(array[row][column]);
       }
       return slice;
-    }
+    };
 
     /**
      * Returns a human-readable (in local time zone) time string for a
@@ -970,7 +993,7 @@ Loader.controller(
     $scope.localTimeString = function(secondsPastEpoch) {
       var d = new Date(secondsPastEpoch * 1000);
       return d.toString();
-    }
+    };
 
     /**
      * Returns a hex color string (such as "#aabbcc") for the given RGB values.
@@ -993,7 +1016,7 @@ Loader.controller(
         bString = "0" + bString;
       }
       return '#' + rString + gString + bString;
-    }
+    };
 
     /**
      * Returns a hex color string (such as "#aabbcc") for the given brightness.
@@ -1006,7 +1029,7 @@ Loader.controller(
     $scope.brightnessStringToHexColor = function(brightnessString) {
       var v = parseInt(brightnessString);
       return $scope.hexColorString(v, v, v);
-    }
+    };
 
     /**
      * Returns the last path component of image diff URL for a given ImagePair.
@@ -1024,7 +1047,7 @@ Loader.controller(
           imagePair[constants.KEY__IMAGEPAIRS__IMAGE_A_URL] + "-vs-" +
           imagePair[constants.KEY__IMAGEPAIRS__IMAGE_B_URL];
       return before.replace(/[^\w\-]/g, "_") + ".png";
-    }
+    };
 
   }
 );
index ee9be32..80f2809 100644 (file)
     padding: 10px;
     border: 2px solid #222;
 }
+
+.sort-desc {
+     background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJZAMIcakQZjNtyhFxwEIIRofAookUnapu26t+6KFLYe1TgQ5VwQAOw%3D%3D);
+}
+
+.sort-asc {
+    background:no-repeat left center url(data:image/gif;base64,R0lGODlhCgAKALMAAHFxcYKCgp2dnaampq+vr83NzeHh4f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAAAgAIf/8SUNDUkdCRzEwMTIAAAUwYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkc2NtAAABCAAAAvJkZXNjAAAD/AAAAG9nWFlaAAAEbAAAABR3dHB0AAAEgAAAABRyWFlaAAAElAAAABRiWFlaAAAEqAAAABRyVFJDAAAEvAAAAA5jcHJ0AAAEzAAAADhjaGFkAAAFBAAAACxn/1RSQwAABLwAAAAOYlRSQwAABLwAAAAObWx1YwAAAAAAAAARAAAADGVuVVMAAAAmAAACfmVzRVMAAAAmAAABgmRhREsAAAAuAAAB6mRlREUAAAAsAAABqGZpRkkAAAAoAAAA3GZyRlUAAAAoAAABKml0SVQAAAAoAAACVm5sTkwAAAAoAAACGG5iTk8AAAAmAAABBHB0QlIAAAAmAAABgnN2U0UAAAAmAAABBGphSlAAAAAaAAABUmtvS1IAAAAWAAACQHpoVFcAAAAWAAABbHpoQ04AAAAWAAAB1HJ1UlUAAAAiAAACpHBsUEwAAAAsAAACxgBZAGwAZQBpAG4AZf8AbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAHMAawAgAFIARwBCAC0AcAByAG8AZgBpAGwAUAByAG8AZgBpAGwAIABHAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCTgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOuQGnUoACAAUgBHAEIAIIJyX2ljz4/wAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8AQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbGZukBoAIABSAEcAQgAgY8+P8GX/h072AEcAZQBuAGUAcgBlAGwAIABSAEcAQgAtAGIAZQBzAGsAcgBpAHYAZQBsAHMAZQBBAGwAZwBlAG0AZQBlAG4AIABSAEcAQgAtAHAAcgBvAGYAaQBlAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8AFAAcgBvAGYAaQBsAG8AIABSAEcAQgAgAEcAZQBuAGUAcgBpAGMAbwBHAGUAbgBlAHIAaQBjACAAUgBHAEIAIABQAHIAbwBmAGkAbABlBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgBVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBm/wBpAGwAIABSAEcAQgAAZGVzYwAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAFEdlbmVyaWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0AAD3uAAAD0FhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUgSW5jLkMsIGFsbCByaWdodHMgcmVzZXJ2ZWQuAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBsACwAAAAACgAKAAAEJRBJREKZsxQDsCSGIVzZFnYTGIqktp7fG46uzAn2TAyCMPC9QAQAOw%3D%3D);
+}
+
+.sortable-header {
+    padding-right: 3px;
+    padding-left: 13px;
+    margin-left: 4px;
+}
index bafa3f2..7142fca 100644 (file)
@@ -5,7 +5,7 @@
 <head>
   <title ng-bind="windowTitle"></title>
   <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
-  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
+  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.20/angular.js"></script>
   <script src="constants.js"></script>
   <script src="loader.js"></script>
   <script src="utils.js"></script>
 
     <table border="0"><tr><td> <!-- table holding results header + results table -->
       </td></tr><tr><td>
-      <table border="1" ng-app="diff_viewer"> <!-- results -->
+      <table border="1"> <!-- results -->
         <tr>
           <!-- Most column headers are displayed in a common fashion... -->
           <th ng-repeat="columnName in orderedColumnNames">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="{{columnName}}"
-                   ng-checked="(sortColumnKey == columnName)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)">
-            {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}
+              <a ng-class="'sort-' + sortedByColumnsCls(columnName)"
+                 ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXTRACOLUMNS, columnName)"
+                 href=""
+                 class="sortable-header">
+               {{extraColumnHeaders[columnName][constants.KEY__EXTRACOLUMNHEADERS__HEADER_TEXT]}}
+             </a>
           </th>
+
           <!-- ... but there are a few columns where we display things differently. -->
           <th>
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="bugs"
-                   ng-checked="(sortColumnKey == constants.KEY__EXPECTATIONS__BUGS)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)">
-            bugs
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__EXPECTATIONS__BUGS)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__EXPECTATIONS, constants.KEY__EXPECTATIONS__BUGS)"
+               href=""
+               class="sortable-header">
+                  bugs
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="imageA"
-                   ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
-                   ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)">
-            {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
+               ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_A_URL)"
+               href=""
+               class="sortable-header">
+                   {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_A][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="imageB"
-                   ng-checked="(sortColumnKey == constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
-                   ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)">
-            {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
+               ng-click="sortResultsBy('none', constants.KEY__IMAGEPAIRS__IMAGE_B_URL)"
+               href=""
+               class="sortable-header">
+                  {{imageSets[constants.KEY__IMAGESETS__SET__IMAGE_B][constants.KEY__IMAGESETS__FIELD__DESCRIPTION]}}
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="percentDifferingPixels"
-                   ng-checked="(sortColumnKey == constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)">
-            differing pixels in white
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCENT_DIFF_PIXELS)"
+               href=""
+               class="sortable-header">
+                  differing pixels in white
+            </a>
           </th>
           <th width="{{imageSize}}">
-            <input type="radio"
-                   name="sortColumnRadio"
-                   value="perceptualDiff"
-                   ng-checked="(sortColumnKey == constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
-                   ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)">
-            perceptual difference
+            <a ng-class="'sort-' + sortedByColumnsCls(constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
+               ng-click="sortResultsBy(constants.KEY__IMAGEPAIRS__DIFFERENCES, constants.KEY__DIFFERENCES__PERCEPTUAL_DIFF)"
+               href=""
+               class="sortable-header">
+               perceptual difference
+            </a>
             <br>
             <input type="range" ng-model="pixelDiffBgColorBrightness"
                    ng-init="pixelDiffBgColorBrightness=64; pixelDiffBgColor=brightnessStringToHexColor(pixelDiffBgColorBrightness)"