table.js 7.38 KB
/*
 * Copyright 2015 Open Networking Laboratory
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 ONOS GUI -- Widget -- Table Service
 */
(function () {
    'use strict';

    // injected refs
    var $log, $window, fs, mast, is;

    // constants
    var tableIconTdSize = 33,
        mastPdg = 8,
        h2Pdg = 40,
        thPdg = 12,
        tbodyPdg = 5,
        colWidth = 'col-width',
        tableIcon = 'table-icon';

    // internal state
    var currCol = {},
        prevCol = {};

    // Functions for creating a fixed header on a table (Angular Directive)

    function setElemWidth(elem, size) {
        elem.style('width', size + 'px')
    }

    function setColWidth(th, td, size) {
        setElemWidth(th, size);
        setElemWidth(td, size);
    }

    // count number of headers of
    //   - assigned width,
    //   - icon width,
    //   - and default width
    // assumes assigned width is not given to icons
    // returns the width of all columns that are not icons
    // or have an assigned width
    function getDefaultWidth(headers) {
        var winWidth = fs.windowSize().width,
            iconCols = 0,
            regCols = 0,
            cstmColWidth = 0;

        headers.each(function (d, i) {
            var thElement = d3.select(this),
                cstmWidth = thElement.attr(colWidth);

            if (cstmWidth) {
                cstmColWidth += fs.noPx(cstmWidth);
            } else if (thElement.classed(tableIcon)) {
                iconCols += 1;
            } else {
                regCols += 1;
            }
        });

        return Math.floor((winWidth - cstmColWidth -
                            (iconCols * tableIconTdSize)) / regCols);
    }

    function setTableWidth(t) {
        var tHeaders = t.selectAll('th'),
            defaultColWidth = getDefaultWidth(tHeaders);

        tHeaders.each(function (d, i) {
            var thElement = d3.select(this),
                tdElement = t.select('td:nth-of-type(' + (i + 1) + ')'),
                custWidth = thElement.attr(colWidth);

            if (custWidth) {
                setColWidth(thElement, tdElement, fs.noPx(custWidth));
            } else if (thElement.classed(tableIcon)) {
                setColWidth(thElement, tdElement, tableIconTdSize);
            } else {
                setColWidth(thElement, tdElement, defaultColWidth);
            }
        });
    }

    // get the size of the window and then subtract the extra space at the top
    // to get the height of the table
    function setTableHeight(thead, tbody) {
        var titleHeight = h2Pdg + fs.noPxStyle(d3.select('h2'), 'height'),
            thHeight = thPdg + fs.noPxStyle(d3.select('th'), 'height'),
            totalHeight = titleHeight + thHeight + tbodyPdg + mastPdg,
            tableHeight = fs.windowSize(mast.mastHeight() + totalHeight).height;

        thead.style('display', 'block');
        tbody.style({'display': 'block',
            'height': (tableHeight + 'px'),
            'overflow': 'auto'
        });
    }

    function fixTable(t, th, tb) {
        setTableWidth(t);
        setTableHeight(th, tb);
    }

    // Functions for sorting table rows by header and choosing appropriate icon

    function updateSortingIcons(thElem, api) {
        var div;
        currCol.colId = thElem.attr('colId');

        if (currCol.colId === prevCol.colId) {
            (currCol.icon === 'tableColSortDesc') ?
                currCol.icon = 'tableColSortAsc' :
                currCol.icon = 'tableColSortDesc';
            prevCol.icon = currCol.icon;
        } else {
            currCol.icon = 'tableColSortAsc';
            prevCol.icon = 'tableColSortNone';
        }

        div = thElem.select('div');
        api.sortNone(div);
        div = thElem.append('div');

        if (currCol.icon === 'tableColSortAsc') {
            api.sortAsc(div);
        } else {
            api.sortDesc(div);
        }

        if (prevCol.colId !== undefined &&
            prevCol.icon === 'tableColSortNone') {
            api.sortNone(prevCol.elem.select('div'));
        }

        prevCol.colId = currCol.colId;
        prevCol.elem = thElem;
    }

    function sortRequestParams() {
        return {
            sortCol: currCol.colId,
            sortDir: (currCol.icon === 'tableColSortAsc' ? 'asc' : 'desc')
        };
    }

    angular.module('onosWidget')
        .directive('onosFixedHeader', ['$window', 'FnService', 'MastService',
            function (_$window_, _fs_, _mast_) {
            return function (scope, element) {
                $window = _$window_;
                fs = _fs_;
                mast = _mast_;

                var w = angular.element($window),
                    table = d3.select(element[0]),
                    thead = table.select('thead'),
                    tbody = table.select('tbody'),
                    canAdjust = false;

                scope.$watch(function () {
                    return {
                        h: window.innerHeight,
                        w: window.innerWidth
                    };
                }, function (newVal) {
                    var wsz = fs.windowSize(0, 30);
                    scope.windowHeight = newVal.h;
                    scope.windowWidth = newVal.w;

                    // default table size in case no data elements
                    table.style('width', wsz.width + 'px');

                    scope.$on('LastElement', function () {
                        // only adjust the table once it's completely loaded
                        fixTable(table, thead, tbody);
                        canAdjust = true;
                    });

                    if (canAdjust) {
                        fixTable(table, thead, tbody);
                    }
                }, true);

                w.bind('onos-fixed-header', function () {
                    scope.$apply();
                });
            };
        }])

        .directive('onosSortableHeader', ['$log', 'IconService',
            function (_$log_, _is_) {
            return {
                scope: {
                    ctrlCallback: '&sortCallback'
                },
                link: function (scope, element) {
                    $log = _$log_;
                    is = _is_;
                    var table = d3.select(element[0]),
                        sortIconAPI = is.createSortIcon();

                    // when a header is clicked, change its icon tag
                    // and get sorting order to send to the server.
                    table.selectAll('th').on('click', function () {
                        var thElem = d3.select(this);

                        if (thElem.attr('sortable') === '') {
                            updateSortingIcons(thElem, sortIconAPI);
                            scope.ctrlCallback({
                                requestParams: sortRequestParams()
                            });
                        }
                    });
                }
            };
        }]);

}());