Katello/katello

View on GitHub
engines/bastion/app/assets/javascripts/bastion/components/nutupane.factory.js

Summary

Maintainability
F
3 days
Test Coverage
/**
 * @ngdoc service
 * @name  Bastion.components.service:Nutupane
 *
 * @requires $location
 * @requires $q
 * @requires entriesPerPage
 * @requires TableCache
 * @requires Notification
 *
 * @description
 *   Defines the Nutupane factory for adding common functionality to the Nutupane master-detail
 *   pattern.  Note that the API Nutupane uses must provide a response of the following structure:
 *
 *   {
 *      page: 1,
 *      subtotal: 50,
 *      total: 100,
 *      results: [...]
 *   }
 *
 * @example
 *   <pre>
       angular.module('example').controller('ExampleController',
           ['Nutupane', function (Nutupane)) {
               var nutupane                = new Nutupane(ExampleResource);
               $scope.table                = nutupane.table;
           }]
       );
    </pre>
 */
angular.module('Bastion.components').factory('Nutupane',
    ['$location', '$q', '$stateParams', 'entriesPerPage', 'TableCache', 'Notification', function ($location, $q, $stateParams, entriesPerPage, TableCache, Notification) {
        var Nutupane = function (resource, params, action, nutupaneParams) {
            var locationPage, self = this;

            function getTableName() {
                return self.tableName || $location.path().split('/').join('-').slice(1);
            }

            function setQueryStrings() {
                // Don't manipulate the query string for params of a modal view
                if (document.body.className.indexOf("modal-open") >= 0) {
                    return;
                }

                if (self.table.params.paged) {
                    $location.search("page", self.table.params.page).replace();
                    $location.search("per_page", self.table.params['per_page']).replace();
                }

                if (self.table.params.search) {
                    $location.search(self.searchKey, self.table.params.search).replace();
                }

                if (self.table.params.sort_by) {
                    $location.search("sortBy", self.table.params['sort_by']).replace();
                }

                if (self.table.params['sort_order']) {
                    $location.search("sortOrder", self.table.params['sort_order']).replace();
                }
            }

            params = params || {};
            params.paged = true;

            locationPage = $location.search().page;
            // the $location page can return NaN as a String, explicitly checking for that here
            if (!locationPage || isNaN(locationPage)) {
                params.page = 1;
            } else {
                params.page = locationPage;
            }
            params['per_page'] = $location.search()['per_page'] || entriesPerPage;

            nutupaneParams = nutupaneParams || {};
            self.disableAutoLoad = nutupaneParams.disableAutoLoad || false;
            self.searchKey = action ? action + 'Search' : 'search';

            self.table = {
                action: action || 'queryPaged',
                params: params,
                resource: resource,
                rows: [],
                searchTerm: $stateParams[self.searchKey] || $location.search()[self.searchKey] || "",
                initialLoad: true
            };

            self.loadParamsFromExistingTable = function (existingTable) {
                _.extend(params, existingTable.params);
                if (!self.table.searchTerm) {
                    self.table.searchTerm = existingTable.searchTerm;
                }
            };

            self.load = function () {
                var deferred = $q.defer(),
                    responsePage,
                    table = self.table,
                    existingTable = TableCache.getTable(getTableName());

                table.working = true;

                if (table.initialLoad) {
                    table.refreshing = true;
                    table.searchCompleted = false;
                }

                if (existingTable) {
                    self.loadParamsFromExistingTable(existingTable);
                }

                params.search = table.searchTerm || "";
                params.search = self.searchTransform(params.search);

                resource[table.action](params).$promise.then(function (response) {
                    angular.forEach(response.results, function (row) {
                        row.selected = table.allResultsSelected;
                    });

                    table.rows = response.results;

                    responsePage = response.page || 1;
                    table.resource.page = parseInt(responsePage, 10);
                    params.page = table.params.page = parseInt(responsePage, 10);

                    if (table.initialSelectAll) {
                        table.selectAll(true);
                        table.initialSelectAll = false;
                    }

                    deferred.resolve(response);
                    table.resource = response;
                    table.resource.page = parseInt(responsePage, 10);

                    if (self.selectAllMode) {
                        table.selectAll(true);
                    }

                    if (table.resource.page > 1) {
                        table.resource.offset = (table.resource.page - 1) * table.resource['per_page'] + 1;
                    } else {
                        table.resource.offset = 1;
                    }

                    TableCache.setTable(getTableName(), table);
                    setQueryStrings();

                    table.working = false;
                    table.refreshing = false;
                    table.initialLoad = false;
                }).catch(function(response) {
                    table.working = false;
                    table.refreshing = false;

                    if (response && response.data && response.data.error && response.data.error.message) {
                        Notification.setErrorMessage(response.data.error.message);
                    }
                });

                return deferred.promise;
            };

            self.getParams = function () {
                return params;
            };

            self.table.autocomplete = function (term) {
                var data, promise, localParams;

                if (resource.autocomplete) {
                    localParams = self.getParams();
                    localParams.search = term;
                    data = resource.autocomplete(params);
                } else {
                    data = self.table.fetchAutocomplete(term);
                }

                promise = data.$promise;
                if (promise) {
                    return promise.then(function (response) {
                        return self.table.transformScopedSearch(response);
                    });
                }

                return data;
            };

            self.table.transformScopedSearch = function (results) {
                var rows = [],
                    categoriesFound = [];
                angular.forEach(results, function (row) {
                    if (row.category && row.category.length > 0) {
                        if (categoriesFound.indexOf(row.category) === -1) {
                            categoriesFound.push(row.category);
                            rows.push({category: row.category, isCategory: true});
                        }
                    }
                    rows.push(row);
                });

                return rows;
            };

            //Overridable by real controllers, but default to nothing
            self.table.fetchAutocomplete = function () {
                return [];
            };

            self.enableSelectAllResults = function () {
                self.table.selectAllResultsEnabled = true;
                self.table.allResultsSelected = false;
            };

            self.setParams = function (newParams) {
                params = newParams;
            };

            self.addParam = function (param, value) {
                params[param] = value;
            };

            self.searchTransform = function (term) {
                return term;
            };

            self.query = function () {
                var table = self.table;
                if (table.rows.length === 0) {
                    table.resource.page = 0;
                }
                return self.load();
            };

            self.refresh = function () {
                var promise, existingTable;
                existingTable = TableCache.getTable(getTableName());

                if (existingTable) {
                    self.loadParamsFromExistingTable(existingTable);
                }

                self.table.refreshing = true;
                self.table.numSelected = 0;
                promise = self.load();
                promise.then(function () {
                    self.table.selectAllResults(false);
                });
                return promise;
            };

            self.invalidate = function () {
                TableCache.removeTable(getTableName());
            };

            self.removeRow = function (id, field) {
                var foundItem, table = self.table;

                field = field || 'id';

                angular.forEach(table.rows, function (item) {
                    if (item[field] === id) {
                        foundItem = item;
                    }
                });

                table.rows = _.reject(table.rows, function (item) {
                    return item[field] === id;
                }, this);

                table.resource.total = table.resource.total - 1;
                table.resource.subtotal = table.resource.subtotal - 1;
                if (foundItem && foundItem.selected) {
                    table.numSelected = table.numSelected - 1;
                }

                return self.table.rows;
            };

            self.getAllSelectedResults = function (identifier) {
                var selected, selectedRows;
                identifier = identifier || 'id';
                selected = {
                    included: {
                        ids: [],
                        resources: [],
                        search: null,
                        params: params
                    },
                    excluded: {
                        ids: []
                    },
                    all: false
                };

                if (self.table.allResultsSelected) {
                    selected.included.search = self.table.searchTerm || '';
                    selected.excluded.ids = _.map(self.getDeselected(), identifier);
                    selected.all = true;
                } else {
                    selectedRows = self.table.getSelected();
                    selected.included.ids = _.map(selectedRows, identifier);
                    selected.included.resources = selectedRows;
                }
                return selected;
            };

            self.getDeselected = function () {
                var deselectedRows = [];
                angular.forEach(self.table.rows, function (row, rowIndex) {
                    if (row.selected !== true) {
                        deselectedRows.push(self.table.rows[rowIndex]);
                    }
                });
                return deselectedRows;
            };

            self.table.search = function (searchTerm) {
                $location.search(self.searchKey, searchTerm);
                self.table.searchTerm = searchTerm;
                self.table.resource.page = 1;
                self.table.params.page = 1;
                self.table.rows = [];
                self.table.selectAllResults(false);

                if (!self.table.working) {
                    self.table.refreshing = true;
                    if (searchTerm) {
                        self.table.searchCompleted = true;
                    } else {
                        self.table.searchCompleted = false;
                    }
                    self.query();
                }
            };

            self.table.clearSearch = function () {
                self.table.search(null);
                self.table.searchCompleted = true;
            };

            self.table.replaceRow = function (row) {
                var index, selected;
                index = null;
                angular.forEach(self.table.rows, function (item, itemIndex) {
                    if (item.id === row.id) {
                        index = itemIndex;
                        selected = item.selected;
                    }
                });

                if (index >= 0) {
                    row.selected = selected; //Preserve selectedness
                    self.table.rows[index] = row;
                }
            };

            self.table.addRow = function (row) {
                self.table.rows.unshift(row);
                self.table.resource.subtotal += 1;
                self.table.resource.total += 1;
            };

            self.table.hasPagination = function () {
                return self.table.resource && self.table.resource.subtotal && self.table.resource.page &&
                    self.table.resource.per_page && self.table.resource.offset;
            };

            self.table.onFirstPage = function () {
                return self.table.resource.page === 1;
            };

            self.table.getLastPage = function () {
                return Math.ceil(self.table.resource.subtotal / self.table.resource.per_page);
            };

            self.table.onLastPage = function () {
                return self.table.resource.page >= self.table.getLastPage();
            };

            self.table.pageExists = function (pageNumber) {
                return (pageNumber >= 1) && (pageNumber <= self.table.getLastPage());
            };

            self.table.getPageStart = function () {
                var table = self.table, pageStart = 0;

                if (table.rows.length > 0) {
                    pageStart = table.resource.offset;
                }

                return pageStart;
            };

            self.table.getPageEnd = function () {
                var table = self.table, pageEnd;

                pageEnd = table.resource.offset + table.rows.length - 1;

                if (pageEnd > table.resource.subtotal) {
                    pageEnd = table.resource.subtotal;
                }

                return pageEnd;
            };

            self.table.firstPage = function () {
                return self.table.changePage(1);
            };

            self.table.previousPage = function () {
                var previousPage = parseInt(params.page, 10) - 1;
                return self.table.changePage(previousPage);
            };

            self.table.nextPage = function () {
                var nextPage = parseInt(params.page, 10) + 1;
                return self.table.changePage(nextPage);
            };

            self.table.lastPage = function () {
                var table = self.table;
                return table.changePage(self.table.getLastPage());
            };

            self.table.changePage = function (pageNumber) {
                if (pageNumber && self.table.pageExists(pageNumber)) {
                    self.invalidate();
                    params.page = pageNumber;
                    self.table.resource.page = pageNumber;
                    return self.load();
                }
            };

            self.table.pageSizes = _.uniq(_([25, 50, 75, 100, entriesPerPage]).sortBy().value());

            self.table.updatePageSize = function () {
                params.page = 1;
                self.query();
            };

            // Wraps the table.selectAll() function if selectAllResultsEnabled is not set
            // Otherwise provides expanded functionality

            self.table.selectAllResults = function (selectAll) {
                if (self.table.allSelected() || selectAll !== 'undefined') {
                    self.table.selectAll(selectAll);
                } else if (selectAll) {
                    self.table.initialSelectAll = true;
                }

                if (self.table.selectAllResultsEnabled) {
                    if (selectAll) {
                        self.table.disableSelectAll();
                    } else {
                        self.table.enableSelectAll();
                    }

                    self.table.allResultsSelected = selectAll;
                    self.table.numSelected = selectAll ? self.table.resource.subtotal : 0;
                }
            };

            self.table.allResultsSelectCount = function () {
                return self.table.resource.subtotal - self.getDeselected().length;
            };

            self.table.sortBy = function (column) {
                if (!column) {
                    return;
                }
                if (column.id) {
                    params["sort_by"] = column.id;
                }
                if (column.id === params["sort_by"] || column.id) {
                    params["sort_order"] = (params["sort_order"] === 'ASC') ? 'DESC' : 'ASC';
                } else {
                    params["sort_order"] = 'ASC';
                }

                column.sortOrder = params["sort_order"];
                column.active = true;
                self.table.rows = [];
                self.query();
            };

            self.setSearchKey = function (newKey) {
                self.searchKey = newKey;
                self.table.searchTerm = $location.search()[self.searchKey];
            };

            // Useful when there are multiple tables in one location
            self.setTableName = function(name) {
                self.tableName = name;
            };

            if (!self.disableAutoLoad) {
                self.load();
            }
        };

        return Nutupane;
    }]
);