<body data-ng-app="rxApp" data-ng-controller="indexController">
    <div class="container body-content">
        <div class="row form-inline">
            <select class="form-control"
                data-ng-change='vm.setGroups()'
                data-ng-model="vm.indexGroupingSelected"
                data-ng-options="i as i.label for i in options">
            </select>
            <button class="form-control" data-ng-click="vm.loadIndex()">Load Index</button>
            <span class="glyphicon glyphicon-refresh" aria-hidden="true" data-ng-if="vm.isLoadingIndex"></span>
        </div>
        <accordion close-others="true">
            <accordion-group is-open="$first" data-ng-repeat="i in groups | orderBy: 'groupName' : vm.indexGroupingSelected.sortDescending ">
                <accordion-heading>
                    <span data-ng-bind-html="i.groupName"></span>
                </accordion-heading>
                <ul>
                    <li data-ng-repeat="j in i.group | orderBy: 'SortOrdinal' : true ">
                        <code>{{ j.SortOrdinal }}</code><br />
                        <a href="https://songhayblog.azurewebsites.net/#/entry/{{ j.Slug }}" target="_blank">{{ j.Title }}</a>
                    </li>
                </ul>
            </accordion-group>
        </accordion>
    </div>
</body>
body {
    font-family: "Segoe UI", Candara, "Bitstream Vera Sans", "DejaVu Sans", "Bitstream Vera Sans", "Trebuchet MS", Verdana, "Verdana Ref", sans-serif;
    padding-top: 4em;
}

.container.body-content {
    max-width: 960px;
}

.row.form-inline {
    margin-bottom: 2em;
}

/* #region animation for index-loading: */

.glyphicon-refresh {
    -moz-animation-name: infinite-spinning;
    -o-animation-name: infinite-spinning;
    -webkit-animation-name: infinite-spinning;
    animation-name: infinite-spinning;
    -moz-animation-delay: 0;
    -o-animation-delay: 0;
    -webkit-animation-delay: 0;
    animation-delay: 0;
    -moz-animation-direction: normal;
    -o-animation-direction: normal;
    -webkit-animation-direction: normal;
    animation-direction: normal;
    -moz-animation-duration: .5s;
    -o-animation-duration: .5s;
    -webkit-animation-duration: .5s;
    animation-duration: .5s;
    -moz-animation-fill-mode: forwards;
    -o-animation-fill-mode: forwards;
    -webkit-animation-fill-mode: forwards;
    animation-fill-mode: forwards;
    -moz-animation-iteration-count: infinite;
    -o-animation-iteration-count: infinite;
    -webkit-animation-iteration-count: infinite;
    animation-iteration-count: infinite;
    -moz-animation-timing-function: linear;
    -o-animation-timing-function: linear;
    -webkit-animation-timing-function: linear;
    animation-timing-function: linear;
    font-size: 4em;
}

@-moz-keyframes infinite-spinning {
    from {
        transform: rotate(0);
    }

    to {
        transform: rotate(360deg);
    }
}

@-webkit-keyframes infinite-spinning {
    from {
        -webkit-transform: rotate(0);
    }

    to {
        -webkit-transform: rotate(360deg);
    }
}

@keyframes infinite-spinning {
    from {
        transform: rotate(0);
    }

    to {
        transform: rotate(360deg);
    }
}

/*#endregion*/
/*jslint this, white, browser */
/*global _, angular, jQuery */
(function($) {
    "use strict";

    var rxApp = angular.module("rxApp",
        [
            "ngSanitize",
            "ui.bootstrap",
            "rxApp.services",
            "rxApp.controllers"
        ]);

    /* Services */
    var doDataService = function($http) {
        return {
            loadIndex: function() {
                if (!$http) {
                    return;
                }
                var uri = "https://songhayblog.azurewebsites.net/api/index/";
                return $http.get(uri).then(
                    function(result) {
                        var getItemCategoryProperties = function(i) {
                            var o = angular.fromJson("{" + i.ItemCategory + "}");
                            var topics = _(Object.keys(o)).filter(function(v) {
                                return v ? v.indexOf("topic-") === 0 : false;
                            });
                            o.topic = _(topics).isEmpty() ?
                                "<!--zzz-->[no topic]" :
                                "<!--" + _(topics).first() + "-->" + o[_(topics).first()];
                            o.topics = _(topics).map(function(v) {
                                return {
                                    key: v,
                                    value: o[v]
                                };
                            });
                            return o;
                        };

                        var getSortOrdinal = function(i) {
                            var pad = function pad(num, size) {
                                var s = num + "";
                                while (s.length < size) {
                                    s = "0" + s;
                                }
                                return s;
                            };
                            return i.year + "-" +
                                pad(i.month, 2) + "-" +
                                pad(i.day, 2) + "-" +
                                i.Slug;
                        };

                        _(result.data).each(function(o) {
                            _(o).extend(getItemCategoryProperties(o));
                            o.SortOrdinal = getSortOrdinal(o);
                        });
                        return result.data;
                    });
            }
        };
    };

    var services = angular.module("rxApp.services", []);
    services.factory("dataService", ["$http", doDataService]);

    /* Controllers */

    $(function () {
        $(".navbar").toggleClass("index");
        $(".jumbotron").toggleClass("index");
    });
    
    var doIndexController = function($scope, dataService) {
        $scope.options = [{
            label: "by Date",
            sortDescending: true,
            value: "dateGroup"
        }, {
            label: "by Topic",
            sortDescending: false,
            value: "topic"
        }];
        $scope.vm = {
            data: null,
            indexGroupingSelected: $scope.options[0],
            isLoadingIndex: false,
            loadIndex: function() {
                var that = this;
                this.isLoadingIndex = true;
                dataService.loadIndex().then(function(data) {
                    $scope.vm.data = data;
                    $scope.vm.setGroups();
                    that.isLoadingIndex = false;
                });
            },
            setGroups: function() {
                $scope.groups = _($scope.vm.data)
                    .chain()
                    .groupBy(this.indexGroupingSelected.value)
                    .pairs()
                    .map(function(o) {
                        console.log(o[1]);
                        return {
                            groupName: o[0],
                            group: o[1]
                        };
                    })
                    .value();
            }
        };
    };

    var controllers = angular.module("rxApp.controllers", []);
    controllers
        .controller("indexController", ["$scope", "dataService", doIndexController]);
}(jQuery));

External CSS

  1. https://songhay.blob.core.windows.net/shared-styles-bootstrap/css/bootstrap.min.css

External JavaScript

  1. https://songhay.blob.core.windows.net/shared-scripts/underscore.js
  2. https://songhay.blob.core.windows.net/shared-scripts-jquery-legacy/jquery.min.js
  3. https://songhay.blob.core.windows.net/shared-scripts/bootstrap.min.js
  4. https://songhay.blob.core.windows.net/shared-scripts-angular/angular.js
  5. https://songhay.blob.core.windows.net/shared-scripts-angular/angular-sanitize.js
  6. https://songhay.blob.core.windows.net/shared-scripts-angular/angular-animate.js
  7. https://songhay.blob.core.windows.net/shared-scripts-angular/ui-bootstrap.min.js
  8. https://songhay.blob.core.windows.net/shared-scripts-angular/ui-bootstrap-tpls.min.js