UNPKG

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.

293 lines (247 loc) 10.7 kB
/** * Developer: Stepan Burguchev * Date: 8/20/2014 * Copyright: 2009-2016 Comindware® * All Rights Reserved * Published under the MIT license */ 'use strict'; import { Handlebars } from 'lib'; import template from '../templates/gridheader.hbs'; import GlobalEventService from '../../services/GlobalEventService'; /* * * Fires: columnsResize(this, { changes: { 0: 0.5, 1: 0.5 } }) * * */ /** * @name GridHeaderView * @memberof module:core.list.views * @class GridHeaderView * @constructor * @description View используемый для отображения заголовка (шапки) списка * @extends Marionette.ItemView * @param {Object} options Constructor options * @param {Array} options.columns массив колонок * @param {Object} options.gridEventAggregator ? * @param {Backbone.View} options.gridColumnHeaderView View используемый для отображения заголовка (шапки) списка * */ const GridHeaderView = Marionette.ItemView.extend({ initialize(options) { if (!options.columns) { throw new Error('You must provide columns definition ("columns" option)'); } if (!options.gridEventAggregator) { throw new Error('You must provide grid event aggregator ("gridEventAggregator" option)'); } if (!options.gridColumnHeaderView) { throw new Error('You must provide grid column header view ("gridColumnHeaderView" option)'); } this.gridEventAggregator = options.gridEventAggregator; this.gridColumnHeaderView = options.gridColumnHeaderView; this.gridColumnHeaderViewOptions = options.gridColumnHeaderViewOptions; this.columns = options.columns; this.$document = $(document); _.bindAll(this, '__draggerMouseUp', '__draggerMouseMove', '__handleResizeInternal', '__handleColumnSort', 'handleResize'); this.listenTo(GlobalEventService, 'window:resize', this.handleResize); }, template: Handlebars.compile(template), className: 'grid-header', ui: { gridHeaderColumn: '.grid-header-column', gridHeaderColumnContent: '.grid-header-column-content-view' }, events: { 'mousedown .grid-header-dragger': '__handleDraggerMousedown' }, constants: { MIN_COLUMN_WIDTH: 20 }, templateHelpers() { return { columns: this.columns }; }, onRender() { const self = this; this.ui.gridHeaderColumnContent.each((i, el) => { const column = self.columns[i]; const view = new self.gridColumnHeaderView(_.extend(self.gridColumnHeaderViewOptions || {}, { model: column.viewModel, column })); self.listenTo(view, 'columnSort', self.__handleColumnSort); const childEl = view.render().el; el.appendChild(childEl); }); }, onShow() { this.__handleResizeInternal(); }, updateSorting() { this.render(); this.__handleResizeInternal(); }, __handleColumnSort(sender, args) { const column = args.column; const sorting = column.sorting; let comparator; _.each(this.columns, c => { c.sorting = null; }); switch (sorting) { case 'asc': column.sorting = 'desc'; comparator = column.sortDesc; break; case 'desc': column.sorting = 'asc'; comparator = column.sortAsc; break; default: column.sorting = 'asc'; comparator = column.sortAsc; break; } this.updateSorting(); this.trigger('onColumnSort', column, comparator); }, __handleDraggerMousedown(e) { this.__stopDrag(); this.__startDrag(e); return false; }, __getElementOuterWidth(el) { return $(el)[0].getBoundingClientRect().width; }, __startDrag(e) { const $dragger = $(e.target); const $column = $dragger.parent(); const affectedColumns = _.chain($column.nextAll()).toArray().map(function(el) { return { $el: $(el), initialWidth: this.__getElementOuterWidth(el) }; }, this).value(); const draggedColumn = { $el: $column, initialWidth: this.__getElementOuterWidth($column), index: $column.index() }; const unaffectedWidth = _.reduce($column.prevAll(), (m, v) => m + this.__getElementOuterWidth(v), 0); const fullWidth = this.__getFullWidth(); this.dragContext = { pageOffsetX: e.pageX, $dragger, fullWidth, unaffectedWidth, draggedColumn, affectedColumns, maxColumnWidth: fullWidth - affectedColumns.length * this.constants.MIN_COLUMN_WIDTH - unaffectedWidth }; $dragger.addClass('active'); this.$document.mousemove(this.__draggerMouseMove).mouseup(this.__draggerMouseUp); }, __stopDrag() { if (!this.dragContext) { return; } this.dragContext.$dragger.removeClass('active'); this.dragContext = null; this.$document.unbind('mousemove', this.__draggerMouseMove); this.$document.unbind('mouseup', this.__draggerMouseUp); }, __draggerMouseMove(e) { if (!this.dragContext) { return; } const ctx = this.dragContext; let delta = e.pageX - ctx.pageOffsetX; if (delta !== 0) { const draggedColumn = ctx.draggedColumn; let index = ctx.draggedColumn.index; const changes = {}; this.columns[index].absWidth = Math.min(ctx.maxColumnWidth, Math.max(this.constants.MIN_COLUMN_WIDTH, draggedColumn.initialWidth + delta)); delta = this.columns[index].absWidth - draggedColumn.initialWidth; draggedColumn.$el.outerWidth(this.columns[index].absWidth); var newColumnWidthPc = this.columns[index].absWidth / ctx.fullWidth; this.columns[index].width = newColumnWidthPc; changes[index] = this.columns[index].absWidth; index++; var affectedColumnsWidth = ctx.fullWidth - ctx.unaffectedWidth - draggedColumn.initialWidth, sumDelta = 0, sumGap = 0; for (let i = 0; i < ctx.affectedColumns.length; i++) { let c = ctx.affectedColumns[i], newColumnWidth = c.initialWidth - delta * c.initialWidth / affectedColumnsWidth; if (newColumnWidth < this.constants.MIN_COLUMN_WIDTH) { sumDelta += this.constants.MIN_COLUMN_WIDTH - newColumnWidth; newColumnWidth = this.constants.MIN_COLUMN_WIDTH; } else { sumGap += newColumnWidth - this.constants.MIN_COLUMN_WIDTH; } this.columns[index].absWidth = newColumnWidth; index++; } let fullSum = 0; index = ctx.draggedColumn.index + 1; for (let i = 0; i < ctx.affectedColumns.length; i++) { const c = ctx.affectedColumns[i]; if (sumDelta > 0 && this.columns[index].absWidth > this.constants.MIN_COLUMN_WIDTH) { const delta = (this.columns[index].absWidth - this.constants.MIN_COLUMN_WIDTH) * sumDelta / sumGap; this.columns[index].absWidth -= delta; } fullSum += this.columns[index].absWidth; if (i === ctx.affectedColumns.length - 1) { var sumDelta = ctx.fullWidth - ctx.unaffectedWidth - this.columns[ctx.draggedColumn.index].absWidth - fullSum; this.columns[index].absWidth += sumDelta; } var newColumnWidthPc = this.columns[index].absWidth / ctx.fullWidth; this.columns[index].width = newColumnWidthPc; c.$el.outerWidth(this.columns[index].absWidth); changes[index] = this.columns[index].absWidth; index++; } this.gridEventAggregator.trigger('columnsResize'); } return false; }, __draggerMouseUp() { this.__stopDrag(); return false; }, handleResize() { if (this.isDestroyed) { return; } this.__handleResizeInternal(); this.gridEventAggregator.trigger('columnsResize'); }, __getFullWidth() { return this.$el.parent().width() - 2; // Magic cross browser pixels, don't remove them }, __handleResizeInternal() { let fullWidth = this.__getFullWidth(), // Grid header's full width columnWidth = fullWidth / this.columns.length, // Default column width sumWidth = 0; // Columns' sum width // Iterate all but first columns counting their sum width this.ui.gridHeaderColumn.not(':first').each((i, el) => { const child = $(el); const col = this.columns[i + 1]; if (col.width) { // If column has it's custom width col.absWidth = Math.floor(col.width * fullWidth); // Calculate absolute custom column width (rounding it down) } else { col.absWidth = Math.floor(columnWidth); // Otherwise take default column width (rounding it down) } child.outerWidth(col.absWidth); // Set absolute column width sumWidth += col.absWidth; // And add it to columns' sum width }); // Take remaining (or only) first column to calculate it's absolute width as difference between grid header's full width and // other columns' sum width. This logic is necessary because other columns' widths may have been rounded down during calculations if (this.columns.length) { this.columns[0].absWidth = Math.floor(fullWidth - sumWidth); // Calculate remainig (or only) first column's absolute width this.ui.gridHeaderColumn.first().outerWidth(this.columns[0].absWidth); // And set it } } }); export default GridHeaderView;