function link()

in ui-modules/utils/table/index.js [112:300]


    function link(scope, element, attrs, ngModelCtrl) {
        scope.ctrl.rowUiState = attrs.rowUiState;
        scope.ctrl.rowUiStateParams = scope.$eval(attrs.rowUiStateParams);
        scope.ctrl.state = {
            columns: scope.$eval(attrs.columns),
            sorts: [],
            search: '',
            filters: {}
        };

        if (!(scope.ctrl.state.columns instanceof Array)) {
            throw new Error('Field "columns" in table options must be of type "Array"');
        }
        scope.ctrl.state.columnSpanCount = 0;
        scope.ctrl.state.columnExplicitWidthCount = 0;
        scope.ctrl.state.columnExplicitWidthSum = 0;
        scope.ctrl.tableLayoutFixed = false;
        scope.ctrl.state.columns.forEach((column, index) => {
            if (!(column instanceof Object)) {
                throw new Error(`Column with index "${index}" must be of type "Object"`);
            }
            let field = column.field;
            if (!column.hasOwnProperty('header')) {
                if (field) {
                    column.header = field.replace(/([a-z])([A-Z])/g, (_, a, A) => a+' '+A).replace(/^([a-z])(.*)/, (_, a, bc) => a.toUpperCase()+bc)
                } else {
                    throw new Error(`Column with index ${index} does not has the required field "header"`);
                }
            }
            if (!column.hasOwnProperty('template') && !column.hasOwnProperty('templateUrl')) {
                if (field) {
                    column.template = `{{ item['${field}'] }}`;
                } else {
                    throw new Error(`Column with index ${index} requires either "template" or "templateUrl" field`);
                }
            }
            if (!column.hasOwnProperty('id')) {
                if (field) {
                    column.id = field;
                } else {
                    column.id = 'col-'+index;
                    column.idAutogenerated = true;
                }
            } else {
                column.tdClass = column.tdClass || column.id;
            }
            if (!column.hasOwnProperty('orderBy') && field) {
                column.orderBy = field;
            }

            column.hidden = column.hidden || false;

            column.regex = new RegExp(`(?:\\s|^)${column.id}:(\\S*)(?:\\s|$)`, 'i')
            
            if (!column.idAutogenerated) {
              column.idForTypeahead = column.id;
            }
        });
        
        function recomputeSpanCount() {
            // this should be recomputed when columns are hidden/shown
            // without this, the "No results" message may be slightly too wide when columns are hidden
            
            var columnSpanCount = 0,
                columnExplicitWidthCount = 0,
                columnExplicitWidthSum = 0,
                tableLayoutFixed = false;
            
            scope.ctrl.state.columns.forEach((column, index) => {
                if (column.hidden) return;
                columnSpanCount += (column.colspan || 1);
                tableLayoutFixed |= column.colspan;
                if (column.width) {
                    columnExplicitWidthCount ++;
                    columnExplicitWidthSum += column.width;
                }
            });
            
            scope.ctrl.state.columnSpanCount = columnSpanCount;
            scope.ctrl.state.columnExplicitWidthCount = columnExplicitWidthCount;
            scope.ctrl.state.columnExplicitWidthSum = columnExplicitWidthSum;
            scope.ctrl.tableLayoutFixed = tableLayoutFixed;
            
            if (attrs.colWidth) {
                var minWidth = (scope.ctrl.state.columnExplicitWidthSum + 
                    (scope.ctrl.state.columnSpanCount - scope.ctrl.state.columnExplicitWidthCount) * attrs.colWidth);
                if (isNaN(parseFloat(minWidth)) || !isFinite(minWidth)) {
                    // not a valid number in the end: could install units-css library and do unit maths, but not worth it
                    $log.error(`Error computing column widths (got ${scope.ctrl.minWidth}): ensure no values have units`);
                } else {
                    scope.ctrl.minWidth = minWidth + 'px';
                }
            }
        }
        
        recomputeSpanCount();
        
        scope.hideColumn = (column,) => {
            column.hidden = !column.hidden;
            recomputeSpanCount();
        };

        let sha = new jssha('SHA-512', 'TEXT');
        sha.update(scope.ctrl.rowUiState || '');
        sha.update(JSON.stringify(scope.ctrl.rowUiStateParams) || '');
        sha.update(JSON.stringify(scope.ctrl.state.columns) || '');
        let hash = sha.getHash('HEX');

        if (sessionStorage) {
            let state = sessionStorage.getItem(`${MODULE_NAME}.state.${hash}`);
            if (state !== null) {
                scope.ctrl.state = Object.assign(scope.ctrl.state, JSON.parse(state));
                scope.ctrl.state.columns.forEach(column => column.regex = new RegExp(`(?:\\s|^)${column.id}:(\\S*)(?:\\s|$)`, 'i'));
            }

            scope.$watch('ctrl.state', (newValue, oldValue) => {
                if (!angular.equals(newValue, oldValue)) {
                    sessionStorage.setItem(`${MODULE_NAME}.state.${hash}`, JSON.stringify(newValue));
                }
            }, true);
        }

        scope.$watchCollection('ctrl.items', function(value) {
            ngModelCtrl.$setViewValue(value);
            ngModelCtrl.$validate();
        });

        ngModelCtrl.$render = function() {
            if (!ngModelCtrl.$viewValue) {
                ngModelCtrl.$viewValue = [];
            }
            scope.ctrl.items = ngModelCtrl.$viewValue;
        };

        scope.$applyAsync(() => {
            element[0].querySelectorAll('th div').forEach(elm => {
                angular.element(elm).data('initialWidth', elm.offsetWidth);
            });
            element[0].querySelectorAll('span.column-resizer').forEach(elm => {
                elm.ondragstart = function() { return false; };
                elm.addEventListener('mousedown', function(e) {
                    if (e.which === 1) {
                        // left mouse click
                        scope.ctrl.dragStart(e);
                    }
                }, false);
            });
        });

        scope.$watch('ctrl.state.search', (newValue, oldValue) => {
            if (newValue === oldValue) {
                return;
            }
            let filters = {};
            let remaining = newValue;
            let words = [];
            // get any phrases in double quotes
            let qw = /(?:\s|^)"([^"]*)"(?:\s|$)/;
            var match;
            while (match=qw.exec(remaining)) {
                words.push(match[1]);
                remaining = remaining.replace(qw, ' ');
            }
            // now get anything that matches column prefix
            scope.ctrl.state.columns.forEach(column => {
                if (column.hidden) {
                    return;
                }
                let matches = remaining.match(column.regex);
                if (matches === null) {
                    return;
                }
                filters[column.id] = matches[1];
                remaining = remaining.replace(column.regex, ' ');
            });
            // remaining items are split
            remaining = remaining.trim();
            if (remaining.length > 0) {
                words = words.concat(remaining.split(/\s+/));
            }
            words = words.length==0 ? null : words.length==1 ? words[0] : words;
            if (Object.keys(filters).length > 0) {
                if (words) {
                    filters[''] = words;
                }
            }
            scope.ctrl.state.filters = Object.keys(filters).length > 0 ? filters : words;
        });
    }