comindware.ui
Version:
Comindware Core UI provides the basic components like editors, lists, dropdowns, popups that we so desperately need while creating Marionette-based single-page applications.
248 lines (217 loc) • 10.3 kB
JavaScript
/**
* Developer: Stepan Burguchev
* Date: 7/7/2014
* Copyright: 2009-2016 Comindware®
* All Rights Reserved
* Published under the MIT license
*/
import { Handlebars } from 'lib';
import { helpers, htmlHelpers } from 'utils';
import template from '../templates/grid.hbs';
import ListView from './ListView';
import RowView from './RowView';
import GridHeaderView from './GridHeaderView';
import NoColumnsDefaultView from './NoColumnsView';
import LoadingChildView from './LoadingRowView';
/*
Public interface:
This view produce:
trigger: positionChanged (sender, { oldPosition, position })
trigger: viewportHeightChanged (sender, { oldViewportHeight, viewportHeight })
This view react on:
collection change (via Backbone.Collection events)
position change (when we scroll with scrollbar for example): updatePosition(newPosition)
*/
const constants = {
gridRowHeight: 32,
gridHeaderHeight: 51
};
/**
* @name GridView
* @memberof module:core.list.views
* @class GridView
* @constructor
* @description View-контейнер для заголовка и контента
* @extends Marionette.LayoutView
* @param {Object} options Constructor options
* @param {Array} options.collection массив элементов списка
* @param {Array} options.columns массив колонок
* @param {Backbone.View} options.emptyView View для отображения пустого списка (нет строк)
* @param {Number} options.childHeight высота строки списка (childView)
* @param {Backbone.View} [options.childView] view строки списка
* @param {Backbone.View} [options.childViewOptions] опции для childView
* @param {Function} options.childViewSelector ?
* @param {Object} [options.emptyViewOptions] опции для emptyView
* @param {Backbone.View} options.gridColumnHeaderView View заголовка списка
* @param {String} options.height задает как определяется высота строки, значения: fixed, auto
* @param {Backbone.View} [options.loadingChildView] view-лоадер, показывается при подгрузке строк
* @param {Backbone.View} options.noColumnsView View для отображения списка без колонок
* @param {Object} [options.noColumnsViewOptions] опции для noColumnsView
* @param {Number} options.maxRows максимальное количество отображаемых строк (используется с опцией height: auto)
* @param {Boolean} options.useDefaultRowView использовать RowView по умолчанию. В случае, если true — обязательно должны быть указаны cellView для каждой колонки
* @param {Boolean} options.forbidSelection запретить выделять элементы списка при помощи мыши
* */
const GridView = Marionette.LayoutView.extend({
initialize(options) {
if (this.collection === undefined) {
throw 'You must provide a collection to display.';
}
if (options.columns === undefined) {
throw 'You must provide columns definition ("columns" option)';
}
options.onColumnSort && (this.onColumnSort = options.onColumnSort); //jshint ignore:line
this.headerView = new GridHeaderView({
columns: options.columns,
gridEventAggregator: this,
gridColumnHeaderView: options.gridColumnHeaderView
});
this.listenTo(this.headerView, 'onColumnSort', this.onColumnSort, this);
if (options.noColumnsView) {
this.noColumnsView = options.noColumnsView;
} else {
this.noColumnsView = NoColumnsDefaultView;
}
options.noColumnsViewOptions && (this.noColumnsViewOptions = options.noColumnsViewOptions); // jshint ignore:line
this.forbidSelection = _.isBoolean(options.forbidSelection) ? options.forbidSelection : true;
let childView = options.childView;
if (options.useDefaultRowView) {
_.each(options.columns, column => {
if (column.cellView === undefined) { throw 'You must specify cellView for each column (useDefaultRowView flag is true)'; }
});
childView = RowView;
options.childHeight = constants.gridRowHeight;
} else if (options.childHeight === undefined) {
throw 'You must provide a childHeight for the child item view (in pixels).';
}
const childViewOptions = _.extend(options.childViewOptions || {}, {
columns: options.columns,
gridEventAggregator: this
});
this.listView = new ListView({
collection: this.collection,
childView,
childViewSelector: options.childViewSelector,
emptyView: options.emptyView,
emptyViewOptions: options.emptyViewOptions,
noColumnsView: options.noColumnsView,
noColumnsViewOptions: options.noColumnsViewOptions,
childHeight: options.childHeight,
childViewOptions,
loadingChildView: options.loadingChildView || LoadingChildView,
maxRows: options.maxRows,
height: options.height,
forbidSelection: this.forbidSelection
});
this.listenTo(this.listView, 'all', (eventName, view, eventArguments) => {
if (_.string.startsWith(eventName, 'childview')) {
this.trigger.apply(this, [eventName, view ].concat(eventArguments));
}
});
this.listenTo(this.listView, 'positionChanged', (sender, args) => {
this.trigger('positionChanged', this, args);
});
this.listenTo(this.listView, 'viewportHeightChanged', this.__updateHeight, this);
this.updatePosition = this.listView.updatePosition.bind(this.listView);
if (this.collection.length) {
this.__presortCollection(options.columns);
}
this.listenTo(this.collection, 'reset', (collection, options) => {
// fixing display:table style if there were not rows
if (options && options.previousModels.length === 0) {
this.listView.visibleCollectionRegion.currentView.$el.css('display', 'none');
// forcing browser to rebuild DOM accessing the attribute
this.listView.visibleCollectionRegion.currentView.$el.css('display');
this.listView.visibleCollectionRegion.currentView.$el.css('display', 'table');
this.__presortCollection(this.getOption('columns'));
}
});
},
__updateHeight(sender, args) {
args.gridHeight = args.listViewHeight + constants.gridHeaderHeight;
this.$el.height(args.gridHeight);
this.trigger('viewportHeightChanged', this, args);
},
onColumnSort(column, comparator) {
this.collection.comparator = comparator;
this.collection.sort();
},
regions: {
headerRegion: '.grid-header-view',
contentViewRegion: '.grid-content-view',
noColumnsViewRegion: '.js-nocolumns-view-region'
},
className: 'grid',
template: Handlebars.compile(template),
onShow() {
const elementWidth = this.$el.width();
if (this.options.columns.length === 0) {
const noColumnsView = new this.noColumnsView(this.noColumnsViewOptions);
this.noColumnsViewRegion.show(noColumnsView);
}
this.headerRegion.show(this.headerView);
this.contentViewRegion.show(this.listView);
const updatedElementWidth = this.$el.width();
if (elementWidth !== updatedElementWidth) {
// A native scrollbar was displayed after we showed the content, which triggered width change and requires from us to recalculate the columns.
this.headerView.handleResize();
this.listView.handleResize();
}
},
onRender() {
if (this.forbidSelection) {
htmlHelpers.forbidSelection(this.el);
}
},
sortBy(columnIndex, sorting) {
const column = this.options.columns[columnIndex];
if (sorting) {
_.each(this.options.columns, c => {
c.sorting = null;
});
column.sorting = sorting;
switch (sorting) {
case 'asc':
this.collection.comparator = column.sortAsc;
break;
case 'desc':
this.collection.comparator = column.sortDesc;
break;
}
} else {
sorting = column.sorting;
_.each(this.options.columns, c => {
c.sorting = null;
});
switch (sorting) {
case 'asc':
column.sorting = 'desc';
this.collection.comparator = column.sortDesc;
break;
case 'desc':
column.sorting = 'asc';
this.collection.comparator = column.sortAsc;
break;
default:
column.sorting = 'asc';
this.collection.comparator = column.sortAsc;
break;
}
}
this.onColumnSort(column, this.collection.comparator);
this.headerView.updateSorting();
},
handleResize() {
this.headerView.handleResize();
},
__presortCollection(columns) {
const sortingColumn = columns.find(column => column.sorting);
if (sortingColumn) {
if (sortingColumn.sorting === 'asc') {
this.onColumnSort(sortingColumn, sortingColumn.sortAsc);
} else {
this.onColumnSort(sortingColumn, sortingColumn.sortDesc);
}
}
}
});
export default GridView;