"use strict"; /** * Licensed Materials - Property of IBM * IBM Cognos Products: Cognos Analytics * Copyright IBM Corp. 2015, 2018 * US Government Users Restricted Rights - * Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ define(['doT', 'q', 'jquery', 'underscore', 'bi/glass/app/ContentView', 'bi/commons/utils/BidiUtil', 'bacontentnav/utils/WidgetNavigator', 'bi/admin/nls/StringResource', 'bi/admin/common/actions/AddInputRow', 'text!bi/admin/common/ui/listview/templates/ListViewTemplate.html', 'text!bi/admin/common/ui/listview/templates/EmptyListViewTemplate.html', 'datatables', 'bi/commons/utils/Utils', 'bi/glass/utils/ClassFactory'], function (dot, Q, $, _, ContentView, BidiUtil, WidgetNavigator, StringResource, AddInputRow, ListViewTemplate, EmptyListViewTemplate, datatables, Utils, ClassFactory) { 'use strict'; //NOSONAR: meant to be strict var ListView = ContentView.extend({ aSelectedRows: [], multiSelect: true, touchMultiSelectEnabled: false, init: function init(options) { ListView.inherited('init', this, arguments); if (options !== undefined) { this.accessibleLabel = options.accessibleLabel || ""; this.formatContent = options.formatContent || true; } else { this.accessibleLabel = ""; this.formatContent = true; } $.extend(this, options); this.aSelectedRows = []; }, createColumnObject: function createColumnObject(columnSpec, ColumnModule) { return new ColumnModule($.extend(columnSpec, { 'listControl': this, 'glassContext': this.glassContext })); }, isMultitouchActive: function isMultitouchActive() { return this.multiSelect; }, _createColumns: function _createColumns() { return Promise.all(_.map(this.dataAdaptor.getColumnSpecs(), function (colSpec, index) { if (colSpec.type || colSpec.module) { var path = colSpec.module || 'bi/content_apps/common/ui/list_columns/' + colSpec.type; return ClassFactory.instantiate(path, _.extend({}, colSpec, { 'listControl': this, 'glassContext': this.glassContext })).then(function (col) { delete col.type; return col.getSpec().then(function (spec) { _.extend(col, spec); col.aTargets = [index]; col.bSortable = !!colSpec.sortable; if (colSpec.width) { col.sWidth = colSpec.width; } return col; }); }.bind(this)); } else { return null; } }.bind(this))); }, setFocus: function setFocus() { var $firstFocusEl = this.$el.find("td[tabindex='0']"); if ($firstFocusEl.length === 0) { return false; } else { $firstFocusEl.focus(); return true; } }, clearShowWorking: function clearShowWorking() { this.$el.find("tbody tr[role='row']").remove(); this.showWorking(); }, showWorking: function showWorking() { var loadingAnimation = Utils.getLoadingAnimation(1); var $container = $('
'); $container.append(loadingAnimation); this.$el.append($container); }, hideWorking: function hideWorking() { var workingEl = this.$el.find(".bi-admin-working"); $(workingEl).remove(); }, render: function render() { this.showWorking(); return Promise.all([this._createColumns(), this.dataAdaptor.getRows()]).then(function (resp) { this.$el.empty(); var columns = resp[0]; var rows = resp[1].rows; if (this.dataAdaptor.checkboxSelection && this.contentView.getSearchTerm) { var text = this.contentView.getSearchTerm().toLowerCase(); rows = _.filter(rows, function (row) { return (row['defaultName'] || "").toLowerCase().indexOf(text) !== -1; }); } var sHtml; this.hideWorking(); if (rows.length > 0) { sHtml = dot.template(ListViewTemplate)({ sortable: this.dataAdaptor.supportSorting, accessibleLabel: this.accessibleLabel, tableCaption: StringResource.get('listViewTableCaption'), columns: columns }); this.$el.append(sHtml); return this._renderTable(columns, rows).then(function () { this.widgetKeyController = new WidgetNavigator({ $el: this.$el.find(".listControl"), focusClass: "contentListFocusable", fCallBack: function fCallBack() {} }); }.bind(this)); } else { sHtml = '
' + StringResource.get('noEntries') + '
'; this.$el.append(sHtml); } }.bind(this)); }, resize: function resize() { var sh = this._calcYBound(); var sb = this.$el.find('.dataTables_scrollBody'); if (sb) { sb.height(sh); } }, reload: function reload(append) { var deferred = Q.defer(); this.showWorking(); if (!this._dTable) { this.render().done(function () { this.hideWorking(); deferred.resolve(); }.bind(this)); } else { if (this._reloading) { return; } this.waitForReload = true; this._reloading = true; if (!append) { this._dTable.clear(); this._dTable.draw(); } this.dataAdaptor.getRows().done(function (result) { var rows = result.rows; if (!append && rows.length === 0) { var emptyHTML = '
' + StringResource.get('noEntries') + '
'; this.$el.append(emptyHTML); } else { this.$el.find(".emptyTableContent").remove(); this._dTable.rows.add(result.rows).draw(false); this._setTextDirection(); this._clearRows(true); this.resize(); } this._reloading = false; this.hideWorking(); setTimeout(function () { delete this.waitForReload; }.bind(this), 1000); deferred.resolve(); }.bind(this)); } return deferred.promise; }, _sortListener: function _sortListener() {}, filter: function filter(text, propertyName) { if (this.dataAdaptor.checkboxSelection) { this._clearRows(true); } this.removeEmptyTableMessage(); var rows = this.dataAdaptor.getFilteredRows(); if (this._dTable && rows) { propertyName = propertyName || 'defaultName'; text = text.toLowerCase(); var filteredRows = _.filter(rows, function (row) { return (row[propertyName] || "").toLowerCase().indexOf(text) !== -1; }); this._dTable.rows().remove(); this._dTable.rows.add(filteredRows).draw(false); if (filteredRows.length < 1) { this._noResults(); } else { this._setTextDirection(); } } }, _renderTable: function _renderTable(columns, rows) { //NOSONAR var deferred = Q.defer(); var xbound = this.$el.width(); var datatableSettings = { 'data': rows, 'sScrollY': this._calcYBound(), 'sScrollX': xbound, 'sDom': 'rtS', 'bServerSide': false, 'bFilter': false, 'bInfo': false, 'bAutoWidth': false, 'bPaginate': false, 'bDeferRender': true, 'orderClasses': false, 'bSort': this.dataAdaptor.isServerSorting(), 'asStripeClasses': [''], 'aoColumnDefs': columns, 'fnInitComplete': function (oSettings) { this.isInitialized = true; var tabEl = $(oSettings.nScrollHead).find("table"); this._scrollNode = this.$el.find('.dataTables_scrollBody'); var lab = tabEl.attr("aria-label"); tabEl.removeAttr("aria-label"); tabEl.attr("aria-labelledby", "adminTab_tableHeaderLab1 adminTab_tableHeaderLab2 "); var hiddenDiv1 = $(''); var hiddenDiv2 = $(''); tabEl.parent().append(hiddenDiv1); tabEl.parent().append(hiddenDiv2); this._scrollNode.bind('scroll', this._onScroll.bind(this)); deferred.resolve(); }.bind(this), 'fnDrawCallback': function (oSettings) { this._drawCallback(oSettings); }.bind(this) }; var defaultOrder = this.dataAdaptor.sortOrder; if (datatableSettings.bSort && defaultOrder) { datatableSettings.aaSorting = defaultOrder; } var $listTable = this.$el.find('table.listControl.bi-admin-table-list'); // cache all indices the selected tr elements var aSelectedIndices = this._cachedSelectedRows(); this._dTable = $listTable.DataTable(datatableSettings); $listTable.attr("role", "application"); this._reApplySelections(aSelectedIndices); // process row events this._dTable.on('click', 'tr', this.handleClick.bind(this)); this._dTable.on('keydown', 'tr', this._handlekeydown.bind(this)); this._dTable.on('hold', this.handleHoldEvent.bind(this)); var self = this; // Locate checkbox header (if supported) if (this.dataAdaptor.checkboxSelection) { this._renderSelectAllCheckBox(); } // Customize datatable's sorting behavior if (this.dataAdaptor.supportSorting && !datatableSettings.bSort) { var $headers = this.$el.find("thead th"); $headers.each(function () { var $h = $(this); var index = $h.attr('colindex') * 1; var col = columns[index]; if (col && col.sortable) { $h.removeClass('sorting_disabled'); if (defaultOrder && index === defaultOrder[0]) { $h.addClass('sorting_' + defaultOrder[1]); } $h.on('primaryaction', function () { var asc = $h.hasClass('sorting_asc'); var nextOrder = asc ? 'desc' : 'asc'; $headers.removeClass('sorting_asc sorting_desc'); $h.addClass('sorting_' + nextOrder); self.dataAdaptor.setOrder(index, nextOrder); self.reload(); }); } }); } this.$el.find('div.dataTables_scrollBody').on('scroll', function () { if ($(this).scrollTop() + $(this).innerHeight() >= this.scrollHeight && self.dataAdaptor.hasMore()) { self.reload(true); } }); this._setTextDirection(); return deferred.promise; }, _isClickableCol: function _isClickableCol(evt) { var toElem = evt.relatedTarget || evt.toElement || evt.target; return $(toElem).hasClass('nameColumnDiv'); }, _handlekeydown: function _handlekeydown(evt) { if (evt.which === 13 || evt.which === 32) { this.handleClick(evt); } }, handleClick: function handleClick(evt) { //NOSONAR var trNode = this._findRowNode(evt.currentTarget); if ($(evt.currentTarget).hasClass("ellipsesButton")) { if (this.aSelectedRows.length === 0) { this._selectRow(trNode); } else if (this.aSelectedRows.length === 1) { var match = false; _.each(this.aSelectedRows, function (oRow) { if ($(oRow)[0].rowIndex === trNode.rowIndex) { match = true; } }); if (!match) { this._clearRows(true); this._selectRow(trNode); } } if (this._isClickableCol(evt)) { this._handleSingleSelect(trNode, evt); } else { this._handleContextMenu(evt); } return false; } // click still fired by browser for hold events on iPad, this is to catch and nullify the click if (evt.type === 'click' && this.multiSelectEvent && this.multiSelectEvent.type === "hold") { return; } if (evt.which === 3 && (evt.shiftKey || evt.ctrlKey || evt.metaKey || $(trNode).hasClass('selected'))) { return false; } // process click if (evt.shiftKey && this.multiSelect) { // get last selected row var oLastSelectedRow = _.last(this.aSelectedRows); var iLastSelectedRowIndex = 0; if (oLastSelectedRow) { iLastSelectedRowIndex = oLastSelectedRow.rowIndex; } // clear all rows of 'selected' class this._clearRows(true); var iEndRow = Math.max(iLastSelectedRowIndex, trNode.rowIndex); var iStartRow = Math.min(iLastSelectedRowIndex, trNode.rowIndex); // Get filtered table rows var aNodes = this._dTable.$('tr', { "filter": "applied" }); var i; for (i = iStartRow; i <= iEndRow; i = i + 1) { this._handleMultiSelect($(aNodes[i - 1]), evt.target); } } // process click else if ((evt.ctrlKey || evt.metaKey) && this.multiSelect || evt.type === 'tap' && this.touchMultiSelectEnabled || this.dataAdaptor.checkboxSelection && evt.currentTarget.classList.contains('admin-checkbox')) { this._handleMultiSelect(trNode, evt.target); } // process normal mouse click else if (this._handleSingleSelect(trNode, evt) === false) { return false; } }, _setTextDirection: function _setTextDirection() { var $divName = this.$el.find('div.nameColumnDiv'); $divName.each(function () { $(this).attr('dir', BidiUtil.resolveBaseTextDir(this.innerHTML)); }); }, _noResults: function _noResults() { var emptyHTML = '
' + StringResource.get('noEntries') + '
'; this.$el.append(emptyHTML); }, _handleMultiSelect: function _handleMultiSelect(trNode, target, fromCheckbox) { // toggle 'selected' class of current row if (!($(trNode).hasClass('selected') && $(target).hasClass('forceRowSelection'))) { $(trNode).toggleClass('selected'); if (this.dataAdaptor.checkboxSelection && !fromCheckbox) { var $checkbox = $(trNode).find('.admin-checkbox'); $checkbox.prop('checked', !$checkbox.prop('checked')); } } // maintain the array of selected rows var iIndex = this.aSelectedRows.indexOf(trNode); if (iIndex > -1) { if (!$(target).hasClass('forceRowSelection')) { this._updateSelectedRows('-', iIndex); } } else { this._updateSelectedRows('+', trNode); } }, _handleSingleSelect: function _handleSingleSelect(trNode, evt) { if (this.aSelectedRows.length === 1 && trNode.rowIndex === this.aSelectedRows[0].rowIndex) { if (this.deselectCallback) { this.deselectCallback(); } $(trNode).removeClass('selected'); this._checkRow(trNode, true); this._updateSelectedRows('c'); } this._selectSingleRow(trNode, evt); }, getActionPayload: function getActionPayload() { //method required by content apps column class...need to return a payload I guess var deferred = Q.defer(); //don't want the content apps column to do a thing.... deferred.reject(); return deferred.promise; }, _selectSingleRow: function _selectSingleRow(trNode, evt) { this._clearRows(true); this._selectRow(trNode); //trigger event when row is highlighted this.$el.trigger('com.ibm.admin.listItemHighlighted', this.getSelectedObjects()[0]); if (this.singleSelectCallback && this._isClickableCol(evt)) { this.singleSelectCallback(this.getSelectedObjects()[0]); } }, _handleContextMenu: function _handleContextMenu(evt) { var position = {}; if (evt.pageX === undefined || evt.gesture === undefined || evt.gesture.center === undefined || evt.gesture.center.pageX === undefined) { position = $(evt.target).offset(); } else { position.left = evt.pageX || evt.gesture.center.pageX; position.top = evt.pageY || evt.gesture.center.pageY; } var data = { 'position': { "pageX": position.left, "pageY": position.top }, 'selectedObject': this.getSelectedObjects() }; if (this.contextMenuCallback) { this.contextMenuCallback(data); } }, handleHoldEvent: function handleHoldEvent(evt) { this._clearRows(true); this.touchMultiSelectEnabled = true; var node = this._findRowNode(evt.target.parentNode); $(node).addClass('selected'); this._updateSelectedRows('+', node); this.multiSelectEvent = evt; }, _findRowNode: function _findRowNode(node) { while (node.nodeName.toLowerCase() !== 'tr') { node = node.parentNode; } return node; }, /** Clear all selected rows in dataTable **/ _clearRows: function _clearRows(isFromAdmin) { if (isFromAdmin) { if (this.dataAdaptor.checkboxSelection) { var $checkboxHeader = this.$el.find('thead th .admin-header-checkbox input'); $checkboxHeader.prop('checked', false); } _.each(this.aSelectedRows, function (oRow) { $(oRow).removeClass('selected'); this._checkRow(oRow, false); }.bind(this)); this._updateSelectedRows('c'); // clear button and counter for multi-select on touch devices this.touchMultiSelectEnabled = false; } }, /** Returns an array of objects that are being represented by the selected rows **/ getSelectedObjects: function getSelectedObjects() { var i; var rowObjects = []; for (i = 0; i < this.aSelectedRows.length; i += 1) { var obj = this._dTable.row(this.aSelectedRows[i]).data(); rowObjects.push(obj); } return rowObjects; }, /** Select a row in dataTable **/ _selectRow: function _selectRow(trNode) { $(trNode).addClass('selected'); this._updateSelectedRows('+', trNode); if (this.dataAdaptor.checkboxSelection) { var $checkbox = $(trNode).find('.admin-checkbox'); $checkbox.prop('checked', true); } }, _formatContentHelper: function _formatContentHelper(oSettings, i, rowData) { //add in the role of gridcell to the td's if (rowData.anCells) { for (var cellIndex = 0; cellIndex < rowData.anCells.length; cellIndex += 1) { var $cell = $(rowData.anCells[cellIndex]); var scope = oSettings.aoColumns[cellIndex].scope; if (scope === 'row') { $cell.attr("role", "rowheader"); } else { $cell.attr("role", "gridcell"); } $cell.attr("tabindex", "-1"); } } this._processColumnWeights(oSettings); if (this.formatContent && rowData.anCells && rowData.anCells[i]) { oSettings.aoColumns[i].formatContent(rowData.anCells[i]); } }, _drawCallback: function _drawCallback(oSettings) { for (var i = 0; i < oSettings.aoColumns.length; i++) { if (oSettings.aoColumns[i].formatContent) { oSettings.aoData.forEach(this._formatContentHelper.bind(this, oSettings, i)); } } if (this.widgetKeyController) { this.widgetKeyController.setInitialTabIndex(); } }, _processColumnWeights: function _processColumnWeights(oSettings) { var weightSum = 0; var percentSum = 0; oSettings.aoColumns.forEach(function (column) { if (column.weight) { weightSum += column.weight; } else if (column.sWidth && column.sWidth.slice(-1) === '%') { percentSum += parseInt(column.sWidth.slice(0, -1), 10); } }); oSettings.aoColumns.forEach(function (column) { if (column.weight) { column.sWidth = Math.floor(column.weight / weightSum * (100 - percentSum)) + '%'; } }); }, _calcYBound: function _calcYBound() { var headerHeight = this.$el.find('th').height(); if (this.$el.height() - headerHeight < headerHeight) { return "70%"; } else { return this.$el.height() - headerHeight; } }, addInput: function addInput(type, pid, accountExplorer, listAdaptor) { //running multiple instances of AddInputRow simultaneously leads to unnecessary bugs, //therefore, only manage one instance at a time, instances will terminate when finished if (this.activeInputForm === null) { this.activeInputForm = new AddInputRow({ 'oListControl': this, 'glassContext': this.glassContext, 'data': this._dTable.rows().data(), 'accountExplorer': accountExplorer, 'listAdaptor': listAdaptor }); this.activeInputForm.execute(type, pid); } }, insertToTable: function insertToTable(rowObj) { this._dTable.rows.add([rowObj]).draw(true); }, removeEmptyTableMessage: function removeEmptyTableMessage() { this.$el.find('.emptyTableContent').remove(); }, adjustScrollForPageDown: function adjustScrollForPageDown() { var $sb = $(this.$el.find('.dataTables_scrollBody')); $sb.scrollTop($sb.scrollTop() + 50); }, _clearTable: function _clearTable() { this._dTable.clear().draw(); }, setPaging: function setPaging(isPaged) { this.pagingOn = isPaged; }, _onScroll: function _onScroll(event) { if (!this.scrollIgnore && !this.waitForReload) { var $target = $(event.target); // If we've scrolled to the bottom if ($target.scrollTop() + $target.innerHeight() >= $target[0].scrollHeight - 20) { $(this).trigger("nextPage", event); this.scrollIgnore = true; setTimeout(function () { delete this.scrollIgnore; }.bind(this), 500); } else if ($target.scrollTop() === 0) { $(this).trigger("previousPage", event); this.scrollIgnore = true; setTimeout(function () { delete this.scrollIgnore; }.bind(this), 500); } } }, _isShortTable: function _isShortTable() { var listContainer = this.$el.find('.ca-listContainer'); return listContainer.height() < 200; }, _updateSelectedRows: function _updateSelectedRows(action, options) { switch (action) { case '-': var removeAtIndex = this.aSelectedRows.indexOf(options); if (removeAtIndex !== -1) { this.aSelectedRows.splice(removeAtIndex, 1); } break; case '+': this.aSelectedRows.push(options); break; case 'c': this.aSelectedRows = []; break; case '=': this.aSelectedRows = options; break; default: break; } if (this.onSelectionChange) { this.onSelectionChange(); } }, _cachedSelectedRows: function _cachedSelectedRows() { // cache all indices the selected tr elements var aSelectedIndices = []; if (this._dTable && this.aSelectedRows.length > 0) { var selectedRows = this._dTable.$('tr.selected'); for (var index = 0; index < selectedRows.length; ++index) { aSelectedIndices.push($(selectedRows[index]).index()); } } return aSelectedIndices; }, _reApplySelections: function _reApplySelections(aSelectedIndices) { this.aSelectedRows = []; // reapply previous selections for (var index = 0; index < aSelectedIndices.length; ++index) { var trNode = this._dTable.$('tr')[aSelectedIndices[index]]; trNode.classList.add('selected'); this._checkRow(trNode, true); this._updateSelectedRows('+', trNode); } }, _renderSelectAllCheckBox: function _renderSelectAllCheckBox() { var $checkboxHeader = this.$el.find('thead th .admin-header-checkbox:not(:has(input))'); $checkboxHeader.html(''); $checkboxHeader.find('input').click(this._toggleSelectAll.bind(this)); }, _toggleSelectAll: function _toggleSelectAll(evt) { if (evt.currentTarget.checked) { var rows = this._dTable.$('tr:not(.selected)'); for (var index = 0; index < rows.length; ++index) { this._selectRow(rows[index]); } } else { this._clearRows(true); } }, _deselectRow: function _deselectRow(trNode) { if (this.deselectCallback) { this.deselectCallback(); } $(trNode).removeClass('selected'); this._checkRow(trNode, false); this._updateSelectedRows('-', trNode); }, _checkRow: function _checkRow(trNode, check) { if (this.dataAdaptor.checkboxSelection) { var $checkbox = $(trNode).find('.admin-checkbox'); $checkbox.prop('checked', check); } } }); return ListView; });