let CommonGridController = function()

in traffic_portal/app/src/common/modules/table/agGrid/CommonGridController.js [144:640]


let CommonGridController = function ($scope, $document, $state, userModel, dateUtils) {
	this.entry = null;
	this.quickSearch = "";
	this.pageSize = 100;
	this.showMenu = false;
	/**
	 * @type {{
	 * 	bottom?: string | 0;
	 * 	left: string | 0;
	 * 	right?: string | 0;
	 * 	top: string | 0;
	 * }}
	 */
	this.menuStyle = {
		left: 0,
		top: 0
	};
	this.mouseDownSelectionText = "";

	// Bound Variables
	/** @type string */
	this.tableTitle = "";
	/** @type string */
	this.tableName = "";
	/** @type CGC.GridSettings */
	this.options = {};
	/** @type any */
	this.gridOptions = {};
	/** @type any[] */
	this.columns = [];
	/** @type string[] */
	this.sensitiveColumns = [];
	/** @type any[] */
	this.data = [];
	/** @type any[] */
	this.selectedData = [];
	/** @type any */
	this.defaultData = {};
	/** @type CGC.DropDownOption[] */
	this.dropDownOptions = [];
	/** @type CGC.ContextMenuOption[] */
	this.contextMenuOptions = [];
	/** @type CGC.TitleButton */
	this.titleButton = {};
	/** @type CGC.TitleBreadCrumbs */
	this.breadCrumbs = [];

	function HTTPSCellRenderer() {}
	HTTPSCellRenderer.prototype.init = function(params) {
		this.eGui = document.createElement("a");
		this.eGui.href = "https://" + params.value;
		this.eGui.setAttribute("class", "link");
		this.eGui.setAttribute("target", "_blank");
		this.eGui.textContent = params.value;
	};
	HTTPSCellRenderer.prototype.getGui = function() {return this.eGui;};

	// browserify can't handle classes...
	function SSHCellRenderer() {}
	SSHCellRenderer.prototype.init = function(params) {
		this.eGui = document.createElement("a");
		this.eGui.href = "ssh://" + userModel.user.username + "@" + params.value;
		this.eGui.setAttribute("class", "link");
		this.eGui.textContent = params.value;
	};
	SSHCellRenderer.prototype.getGui = function() {return this.eGui;};

	function CheckCellRenderer() {}
	CheckCellRenderer.prototype.init = function(params) {
		this.eGui = document.createElement("i");
		if (params.value === null || params.value === undefined) {
			return;
		}

		this.eGui.setAttribute("aria-hidden", "true");
		this.eGui.setAttribute("title", String(params.value));
		this.eGui.classList.add("fa", "fa-lg");
		if (params.value) {
			this.eGui.classList.add("fa-check");
		} else {
			this.eGui.classList.add("fa-times");
		}
	};
	CheckCellRenderer.prototype.getGui = function() {return this.eGui;};

	function UpdateCellRenderer() {}
	UpdateCellRenderer.prototype.init = function(params) {
		this.eGui = document.createElement("i");

		this.eGui.setAttribute("aria-hidden", "true");
		this.eGui.setAttribute("title", String(params.value));
		this.eGui.classList.add("fa", "fa-lg");
		if (params.value) {
			this.eGui.classList.add("fa-clock-o");
		} else {
			this.eGui.classList.add("fa-check");
		}
	};
	UpdateCellRenderer.prototype.getGui = function() {return this.eGui;};

	function defaultTooltip(params) {
		return params.value;
	}

	function dateCellFormatterRelative(params) {
		return params.value ? dateUtils.getRelativeTime(params.value) : params.value;
	}

	function dateCellFormatterUTC(params) {
		return params.value ? params.value.toUTCString() : params.value;
	}

	this.hasContextItems = function() {
		return this.contextMenuOptions.length > 0;
	};

	this.hasSensitiveColumns = function() {
		return this.sensitiveColumns.length > 0;
	}

	/**
	 * @param {string} colID
	 */
	this.isSensitive = function(colID) {
		return this.sensitiveColumns.includes(colID);
	}

	this.sensitiveColumnsShown = false;

	this.toggleSensitiveFields = function() {
		if (this.sensitiveColumnsShown) {
			return;
		}
		for (const col of this.gridOptions.columnApi.getAllColumns()) {
			const id = col.getColId();
			if (this.isSensitive(id)) {
				this.gridOptions.columnApi.setColumnVisible(id, false);
			}
		}
	};

	this.getColumns = () => {
		/** @type {{colId: string}[]} */
		const cols = this.gridOptions.columnApi.getAllColumns();
		if (!this.hasSensitiveColumns || this.sensitiveColumnsShown) {
			return cols;
		}
		return cols.filter(c => !this.isSensitive(c.colId));
	}

	this.$onInit = () => {
		const tableName = this.tableName;

		if (this.defaultData !== undefined) {
			this.entry = this.defaultData;
		}

		for(let i = 0; i < this.columns.length; ++i) {
			if (this.columns[i].filter === "agDateColumnFilter") {
				if (this.columns[i].relative) {
					this.columns[i].tooltipValueGetter = dateCellFormatterRelative;
					this.columns[i].valueFormatter = dateCellFormatterRelative;
				}
				else {
					this.columns[i].tooltipValueGetter = dateCellFormatterUTC;
					this.columns[i].valueFormatter = dateCellFormatterUTC;
				}
			} else if (this.columns[i].filter === 'arrayTextColumnFilter') {
				this.columns[i].filter = 'agTextColumnFilter'
				this.columns[i].filterParams = {
					textCustomComparator: (filter, value, filterText) => {
						const filterTextLowerCase = filterText.toLowerCase();
						const valueLowerCase = value.toString().toLowerCase();
						const profileNameValue = valueLowerCase.split(",");
						switch (filter) {
							case 'contains':
								return valueLowerCase.indexOf(filterTextLowerCase) >= 0;
							case 'notContains':
								return valueLowerCase.indexOf(filterTextLowerCase) === -1;
							case 'equals':
								return profileNameValue.includes(filterTextLowerCase);
							case 'notEqual':
								return !profileNameValue.includes(filterTextLowerCase);
							case 'startsWith':
								return valueLowerCase.indexOf(filterTextLowerCase) === 0;
							case 'endsWith':
								let index = valueLowerCase.lastIndexOf(filterTextLowerCase);
								return index >= 0 && index === (valueLowerCase.length - filterTextLowerCase.length);
							default:
								// should never happen
								console.warn('invalid filter type ' + filter);
								return false;
						}
					}
				}
			}
		}

		// clicks outside the context menu will hide it
		$document.bind("click", e => {
			this.showMenu = false;
			e.stopPropagation();
			$scope.$apply();
		});

		this.gridOptions = {
			components: {
				httpsCellRenderer: HTTPSCellRenderer,
				sshCellRenderer: SSHCellRenderer,
				updateCellRenderer: UpdateCellRenderer,
				checkCellRenderer: CheckCellRenderer,
			},
			columnDefs: this.columns,
			enableCellTextSelection: true,
			suppressMenuHide: true,
			multiSortKey: 'ctrl',
			alwaysShowVerticalScroll: true,
			defaultColDef: {
				filter: true,
				sortable: true,
				resizable: true,
				tooltipValueGetter: defaultTooltip
			},
			rowClassRules: this.options.rowClassRules,
			rowData: this.data,
			pagination: true,
			paginationPageSize: this.pageSize,
			rowBuffer: 0,
			onColumnResized: () => {
				/** @type {{colId: string; hide?: boolean | null}[]} */
				const states = this.gridOptions.columnApi.getColumnState();
				for (const state of states) {
					state.hide = state.hide || this.isSensitive(state.colId);
				}
				localStorage.setItem(tableName + "_table_columns", JSON.stringify(states));
			},
			colResizeDefault: "shift",
			tooltipShowDelay: 500,
			allowContextMenuWithControlKey: true,
			preventDefaultOnContextMenu: this.hasContextItems(),
			onCellMouseDown: () => {
				const selection = window.getSelection();
				if (!selection) {
					this.mouseDownSelectionText = "";
				} else {
					this.mouseDownSelectionText = selection.toString();
				}
			},
			onCellContextMenu: params => {
				if (!this.hasContextItems()){
					return;
				}
				this.showMenu = true;
				this.menuStyle.left = String(params.event.clientX) + "px";
				this.menuStyle.top = String(params.event.clientY) + "px";
				this.menuStyle.bottom = "unset";
				this.menuStyle.right = "unset";
				$scope.$apply();
				const boundingRect = document.getElementById("context-menu")?.getBoundingClientRect();
				if (!boundingRect) {
					throw new Error("no bounding rectangle for context-menu; element possibly missing");
				}

				if (boundingRect.bottom > window.innerHeight){
					this.menuStyle.bottom = String(window.innerHeight - params.event.clientY) + "px";
					this.menuStyle.top = "unset";
				}
				if (boundingRect.right > window.innerWidth) {
					this.menuStyle.right = String(window.innerWidth - params.event.clientX) + "px";
					this.menuStyle.left = "unset";
				}
				this.entry = params.data;
				$scope.$apply();
			},
			onColumnVisible: params => {
				if (params.visible){
					return;
				}
				for (let column of params.columns) {
					if (column.filterActive) {
						const filterModel = this.gridOptions.api.getFilterModel();
						if (column.colId in filterModel) {
							delete filterModel[column.colId];
							this.gridOptions.api.setFilterModel(filterModel);
						}
					}
				}
			},
			onRowSelected: () => {
				this.selectedData = this.gridOptions.api.getSelectedRows();
				$scope.$apply();
			},
			onSelectionChanged: () => {
				this.selectedData = this.gridOptions.api.getSelectedRows();
				$scope.$apply();
			},
			onRowClicked: params => {
				if (params.event.target instanceof HTMLAnchorElement) {
					return;
				}
				const selection = window.getSelection();
				if (this.options.onRowClick !== undefined) {
					if (!selection || selection.toString() === "" || selection === $scope.mouseDownSelectionText) {
						this.options.onRowClick(params);
						$scope.$apply();
					}
				}
				$scope.mouseDownSelectionText = "";
			},
			onFirstDataRendered: () => {
				if(this.options.selectRows) {
					this.gridOptions.rowSelection = this.options.selectRows ? "multiple" : "";
					this.gridOptions.rowMultiSelectWithClick = this.options.selectRows;
					this.gridOptions.api.forEachNode(node => {
						if (node.data[this.options.selectionProperty] === true) {
							node.setSelected(true, false);
						}
					});
				}
				try {
					const filterState = JSON.parse(localStorage.getItem(tableName + "_table_filters") ?? "{}") || {};
					this.gridOptions.api.setFilterModel(filterState);
				} catch (e) {
					console.error("Failure to load stored filter state:", e);
				}
				// Set up filters from query string paramters.
				const params = new URLSearchParams(globalThis.location.hash.split("?").slice(1).join("?"));
				setUpQueryParamFilter(params, this.columns, this.gridOptions.api);
				this.gridOptions.api.onFilterChanged();

				this.gridOptions.api.addEventListener("filterChanged", () => {
					localStorage.setItem(tableName + "_table_filters", JSON.stringify(this.gridOptions.api.getFilterModel()));
				});
			},
			onGridReady: () => {
				try {
					// need to create the show/hide column checkboxes and bind to the current visibility
					const colstates = JSON.parse(localStorage.getItem(tableName + "_table_columns") ?? "null");
					if (colstates) {
						if (!this.gridOptions.columnApi.setColumnState(colstates)) {
							console.error("Failed to load stored column state: one or more columns not found");
						}
					} else {
						this.gridOptions.api.sizeColumnsToFit();
					}
				} catch (e) {
					console.error("Failure to retrieve required column info from localStorage (key=" + tableName + "_table_columns):", e);
				}

				try {
					const sortState = JSON.parse(localStorage.getItem(tableName + "_table_sort") ?? "{}");
					this.gridOptions.api.setSortModel(sortState);
				} catch (e) {
					console.error("Failure to load stored sort state:", e);
				}

				try {
					this.quickSearch = localStorage.getItem(tableName + "_quick_search") ?? "";
					this.gridOptions.api.setQuickFilter(this.quickSearch);
				} catch (e) {
					console.error("Failure to load stored quick search:", e);
				}

				try {
					const ps = Number(localStorage.getItem(tableName + "_page_size"));
					if (ps > 0) {
						this.pageSize = Number(ps);
						this.gridOptions.api.paginationSetPageSize(this.pageSize);
					}
				} catch (e) {
					console.error("Failure to load stored page size:", e);
				}

				try {
					const page = parseInt(localStorage.getItem(tableName + "_table_page") ?? "0", 10);
					if (page > 0 && page <= $scope.gridOptions.api.paginationGetTotalPages()-1) {
						$scope.gridOptions.api.paginationGoToPage(page);
					}
				} catch (e) {
					console.error("Failed to load stored page number:", e);
				}

				this.gridOptions.api.addEventListener("sortChanged", () => {
					localStorage.setItem(tableName + "_table_sort", JSON.stringify(this.gridOptions.api.getSortModel()));
				});

				this.gridOptions.api.addEventListener("columnMoved", () => {
					/** @type {{colId: string; hide?: boolean | null}[]} */
					const states = this.gridOptions.columnApi.getColumnState();
					for (const state of states) {
						state.hide = state.hide || this.isSensitive(state.colId);
					}

					localStorage.setItem(tableName + "_table_columns", JSON.stringify(this.gridOptions.columnApi.getColumnState()));
				});

				this.gridOptions.api.addEventListener("columnVisible", () => {
					this.gridOptions.api.sizeColumnsToFit();
					try {
						const colStates = this.gridOptions.columnApi.getColumnState();
						localStorage.setItem(tableName + "_table_columns", JSON.stringify(colStates));
					} catch (e) {
						console.error("Failed to store column defs to local storage:", e);
					}
				});
			}
		};

	};

	this.exportCSV = function() {
		const params = {
			allColumns: true,
			fileName: this.tableName + ".csv",
		};
		this.gridOptions.api.exportDataAsCsv(params);
	};

	this.toggleVisibility = function(col) {
		const visible = this.gridOptions.columnApi.getColumn(col).isVisible();
		this.gridOptions.columnApi.setColumnVisible(col, !visible);
	};

	this.onQuickSearchChanged = function() {
		this.gridOptions.api.setQuickFilter(this.quickSearch);
		localStorage.setItem(this.tableName + "_quick_search", this.quickSearch);
	};

	this.onPageSizeChanged = function() {
		const value = Number(this.pageSize);
		this.gridOptions.api.paginationSetPageSize(value);
		localStorage.setItem(this.tableName + "_page_size", value.toString());
	};

	this.clearTableFilters = () => {
		// clear the quick search
		this.quickSearch = '';
		this.onQuickSearchChanged();
		// clear any column filters
		this.gridOptions.api.setFilterModel(null);
	};

	this.contextMenuClick = function(menu, $event) {
		$event.stopPropagation();
		menu.onClick(this.entry);
	};

	this.getHref = function(menu) {
		if (menu.href !== undefined){
			return menu.href;
		}
		return menu.getHref(this.entry);
	};

	this.contextIsDisabled = function(menu) {
		if (menu.isDisabled !== undefined) {
			return menu.isDisabled(this.entry);
		}
		return false;
	};

	this.bcGetText = function (bc) {
		if(bc.text !== undefined){
			return bc.text;
		}
		return bc.getText();
	};

	this.bcHasHref = function(bc) {
		return bc.href !== undefined || bc.getHref !== undefined;
	};

	this.bcGetHref = function(bc) {
		if(bc.href !== undefined) {
			return bc.href;
		}
		return bc.getHref();
	};

	this.getText = function (menu) {
		if (menu.text !== undefined){
			return menu.text;
		}
		return menu.getText(this.entry);
	};

	this.isShown = function (menu) {
		if (menu.shown === undefined){
			return true;
		}
		return menu.shown(this.entry);
	};

	$scope.refresh = function() {
		$state.reload(); // reloads all the resolves for the view
	};
};