diff src/DML/MainVisBundle/Resources/assets/marionette/modules/MainRegionModule/MainRegionModule.20-ConfigGridCellsView.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DML/MainVisBundle/Resources/assets/marionette/modules/MainRegionModule/MainRegionModule.20-ConfigGridCellsView.js	Tue Feb 09 20:54:02 2016 +0100
@@ -0,0 +1,900 @@
+"use strict";
+
+App.module("MainRegionModule", function (MainRegionModule, App, Backbone, Marionette, $, _, Logger) {
+
+    MainRegionModule.ConfigGridCellsView = MainRegionModule.ConfigGridChildView.extend({
+
+        options: {
+            state: null,
+            configGrid: null,
+            parentConfigGridView: null,
+            scrollAnimationMinSpeed: 100,
+            scrollAnimationBaseSpeed: 400,
+            scrollAnimationBaseDistance: 500,
+            desiredPaddingAroundVisInstanceOnScroll: {
+                left: 35,
+                top: 20,
+                right: 35,
+                bottom: 33,
+            },
+            enableFixedHeaders: true, // headers become fixed when scrolling
+        },
+
+        _logger: null,
+
+        _$entityHeadersContainer: null,
+        _$entityHeadersBlind: null,
+        _$viewHeadersContainer: null,
+        _$viewHeadersBlind: null,
+        _$visInstancesContainer: null,
+        _$cornerBlind: null,
+        _$fixedContainer: null,
+
+        _$entityAdder: null,
+        _$viewAdder: null,
+
+        _distanceBetweenEntities: 10,
+        _distanceBetweenViews: 10,
+        _viewHeaderHeight: 0,
+        _entityAdderWidth: 0,
+        _viewAdderHeight: 0,
+        _cachedMinSpaceWidth: 0,
+        _cachedMinSpaceHeight: 0,
+        _spacePadding: null, // top, right, bottom, left, h = l + r, v = t + b
+        _scrollLeftBeforeLatestSelectionUpdate: 0,
+        _scrollTopBeforeLatestSelectionUpdate: 0,
+
+        _ignoreXOnNextScroll: false,
+        _ignoreYOnNextScroll: false,
+        _latestChangeWasAReset: false,
+
+        _cachedScrollPosGridHash: null,
+        _cachedScrollPosSelection: null,
+        _cachedScrollPosX: null,
+        _cachedScrollPosY: null,
+
+        _cachedSelectedEntityConfigClientId: null,  // string
+        _cachedSelectedViewConfigClientId: null,    // string
+
+        _cachedEntityHeaderViewsByClientId: {},     // string: Backbone view
+        _cachedViewHeaderViewsByClientId: {},       // string: Backbone view
+        _cachedVisInstanceViewsByClientIdPair: {},  // string: Backbone view
+
+        _cachedEntityConfigClientIds: null,         // [string] (keys of _cachedEntityHeaderViewsByClientId)
+        _cachedViewConfigClientIds: null,           // [string] (keys of _cachedViewHeaderViewsByClientId)
+
+        _cachedEntityWidth: null,
+        _cachedViewContentHeights: null,
+
+        initialize: function(options) {
+            var _this = this;
+
+            _this._logger = Logger.get("ConfigGridCellsView");
+            //_this._logger.setLevel(Logger.DEBUG);
+
+            _this.options = _.defaults(options || {}, this.options);
+            if (_this.options.enableFixedHeaders === null) {
+                _this.options.enableFixedHeaders = (navigator.userAgent.indexOf("afari") >= 0 && navigator.userAgent.indexOf("Chrom") == -1) || navigator.userAgent.indexOf("MSIE") >= 0 || navigator.userAgent.indexOf("MATM") >= 0 || navigator.userAgent.indexOf("Trident") >= 0;
+            }
+
+            var configGridType = _this.options.configGrid.getType();
+
+            _this.$el.empty();
+
+            _this._$entityHeadersContainer = $.bem.generateElement("config-grid-cells", "entity-headers-container");
+            _this._$entityHeadersBlind = $.bem.generateElement("config-grid-cells", "entity-headers-blind");
+            _this._$entityHeadersContainer.append(_this._$entityHeadersBlind);
+
+            _this._$viewHeadersContainer = $.bem.generateElement("config-grid-cells", "view-headers-container");
+            _this._$viewHeadersBlind = $.bem.generateElement("config-grid-cells", "view-headers-blind");
+            _this._$viewAdder = $.bem.generateElement("config-grid-cells", "view-header", ["kind_adder"]);
+            _this._$viewHeadersContainer.append(_this._$viewHeadersBlind, _this._$viewAdder);
+
+            _this._$visInstancesContainer = $.bem.generateElement("config-grid-cells", "vis-instances-container");
+
+            _this._$cornerBlind = $.bem.generateElement("config-grid-cells", "corner-blind");
+
+            _this._$space = $.bem.generateElement("config-grid-cells", "space");
+
+            _this._$containerOfScrollable = $.bem.generateElement("config-grid-cells", "container", ["position_scrollable"]);
+            _this._$containerOfFixed = $.bem.generateElement("config-grid-cells", "container", ["position_fixed"]);
+
+            // Entity and collection adders
+            if (configGridType == "collection") {
+                _this._$entityAdder = $.bem.generateElement("config-grid-cells", "entity-header", ["kind_adder"]);
+                var $entityAdderBackground = $.bem.generateElement("config-grid-cells", "entity-header-background");
+                var $entityAdderLabel = $.bem.generateElement("config-grid-cells", "entity-header-label");
+                $entityAdderLabel.html(Backbone.Marionette.TemplateCache.get("#config-grid_collection__entity-adder-label"));
+                _this._$entityAdder.append($entityAdderBackground, $entityAdderLabel);
+                _this._$entityHeadersContainer.append(_this._$entityAdder);
+                _this._$entityAdder.click(function() {
+                    _this.options.configGrid.addEntityAndSelectIt(new App.ContextModule.Config());
+                });
+            } else {
+                _this._$entityAdder = $();
+            }
+            _this._$viewAdder = $.bem.generateElement("config-grid-cells", "view-header", ["kind_adder"]);
+            var $viewAdderBackground = $.bem.generateElement("config-grid-cells", "view-header-background");
+            var $viewAdderLabel = $.bem.generateElement("config-grid-cells", "view-header-label");
+            $viewAdderLabel.html(Backbone.Marionette.TemplateCache.get("#config-grid__view-adder-label"));
+            _this._$viewAdder.append($viewAdderBackground, $viewAdderLabel);
+            _this._$viewHeadersContainer.append(_this._$viewAdder);
+            _this._$viewAdder.click(function() {
+                _this.options.configGrid.addViewAndSelectIt(new App.ContextModule.Config());
+            });
+
+            _this._$entityHeadersContainer.append();
+            _this._$viewHeadersContainer.append();
+            _this._$space.append(
+                    _this._$visInstancesContainer,
+                    _this._$entityHeadersContainer,
+                    _this._$viewHeadersContainer,
+                    _this._$cornerBlind
+                );
+
+            _this._$containerOfScrollable.append(_this._$space);
+            _this.$el.append(_this._$containerOfScrollable, _this._$containerOfFixed);
+
+            // extract some size- and position-related constants
+            _this._viewHeaderHeight = _this._$viewAdder.height();
+            _this._entityAdderWidth = _this._$entityAdder.width();
+            _this._viewAdderHeight = _this._viewHeaderHeight;
+            if (!_this._entityAdderWidth) {
+                _this._entityAdderWidth = -_this._distanceBetweenEntities;
+            }
+
+            // When defining space padding, it is sometimes necessary to wait a bit
+            // A grid that is on the "back side of the card" may sometimes become blank when scrolling otherwise
+            _this._spacePadding = {};
+            var interval;
+            var setSpaceInterval = function() {
+                if (_this._$space.css("padding-top")) {
+                    _this._spacePadding.top = parseInt(_this._$space.css("padding-top"), 10);
+                    _this._spacePadding.right = parseInt(_this._$space.css("padding-right"), 10);
+                    _this._spacePadding.bottom = parseInt(_this._$space.css("padding-bottom"), 10);
+                    _this._spacePadding.left = parseInt(_this._$space.css("padding-left"), 10);
+                    _this._spacePadding.h = _this._spacePadding.left + _this._spacePadding.right;
+                    _this._spacePadding.v = _this._spacePadding.top + _this._spacePadding.bottom;
+                    //_this._toggleFixedHeadersIfNeeded(false);
+                    _this._updateDemensionsForContainerOfFixed();
+                    clearInterval(interval);
+                }
+            };
+            if (_this._$space.css("padding-top")) {
+                setSpaceInterval();
+            } else {
+                interval = setInterval(setSpaceInterval, 50);
+            }
+
+            // subscribe to events
+            _this.listenTo(_this.options.configGrid, "change",  _this.renderIfParentConfigGridIsVisible);
+
+            var isSafari = navigator.userAgent.indexOf("afari") >= 0;
+            var isChrome = navigator.userAgent.indexOf("rome") >= 0;
+            var isScrolling = false;
+            if (_this.options.enableFixedHeaders) {
+                _this._$containerOfScrollable.mousewheel(_.debounce(function(event) {
+                    _this._toggleFixedHeadersIfNeeded(true);
+
+                    var visInstanceViews = _.values(_this._cachedVisInstanceViewsByClientIdPair);
+                    for (var i = visInstanceViews.length - 1; i >= 0; --i) {
+                        visInstanceViews[i].cancelPointerHighlights();
+                    }
+
+                    App.TooltipModule.update();
+                    if (isSafari || isChrome) {
+                        _this._$containerOfScrollable.scrollLeft(_this._$containerOfScrollable.scrollLeft() + event.deltaX);
+                        _this._$containerOfScrollable.scrollTop (_this._$containerOfScrollable.scrollTop()  - event.deltaY);
+                        event.preventDefault();
+                    }
+                }, 50, true));
+                _this._$containerOfScrollable.mousewheel(_.debounce(function(event) {
+                    if (!isScrolling) {
+                        _this._toggleFixedHeadersIfNeeded(false);
+                    }
+                }, 200));
+            }
+
+            _this._$containerOfScrollable.scroll(function(event) {
+                if (!isScrolling) {
+                    isScrolling = true;
+                }
+                _this._toggleFixedHeadersIfNeeded(true);
+            });
+
+            _this._$containerOfScrollable.scroll(_.debounce(function(event) {
+                    _this._toggleFixedHeadersIfNeeded(false);
+                    _this._reviseSpaceSize();
+                    isScrolling = false;
+                    _this._updateCachedScroll();
+                }, 200));
+
+            $(window).resize(_.throttle(function() {
+                    _this._reviseSpaceSize();
+                    _this._updateDemensionsForContainerOfFixed();
+                }, 100));
+            _this._toggleFixedHeadersIfNeeded(false);
+            _this._updateDemensionsForContainerOfFixed();
+
+            // Restore and save precise scroll position of the currently selected entity and view
+            _this._cachedScrollPosGridHash  = App.DataModule.Storage.getStrCache(MainRegionModule, _.str.sprintf("scroll-pos-grid-hash_%s", configGridType));
+            _this._cachedScrollPosSelection = App.DataModule.Storage.getStrCache(MainRegionModule, _.str.sprintf("scroll-pos-selection_%s", configGridType));
+            _this._cachedScrollPosX = 1 * App.DataModule.Storage.getStrCache(MainRegionModule, _.str.sprintf("scroll-pos-x_%s", configGridType));
+            _this._cachedScrollPosY = 1 * App.DataModule.Storage.getStrCache(MainRegionModule, _.str.sprintf("scroll-pos-y_%s", configGridType));
+
+            $(window).unload(function() {
+                App.DataModule.Storage.setStrCache(MainRegionModule, _.str.sprintf("scroll-pos-grid-hash_%s", configGridType), _this._cachedScrollPosGridHash);
+                App.DataModule.Storage.setStrCache(MainRegionModule, _.str.sprintf("scroll-pos-selection_%s", configGridType), _this._cachedScrollPosSelection);
+                App.DataModule.Storage.setStrCache(MainRegionModule, _.str.sprintf("scroll-pos-x_%s", configGridType), "" + _this._cachedScrollPosX);
+                App.DataModule.Storage.setStrCache(MainRegionModule, _.str.sprintf("scroll-pos-y_%s", configGridType), "" + _this._cachedScrollPosY);
+            });
+
+        },
+
+        render: function (deep, instant) {
+            var _this = this;
+
+            _this._updateLayout(deep, instant);
+            _this._updateSelection(deep, instant);
+            if (deep) {
+                _this._reviseSpaceSize();
+                _this._updateDemensionsForContainerOfFixed();
+            }
+            _this._adjustToScrollPos(deep, instant);
+            _this.scrollAccordingToSelection(deep, instant);
+
+            if (deep) {
+                var entityHeaderViews = _.values(_this._cachedEntityHeaderViewsByClientId);
+                for (var i = entityHeaderViews.length - 1; i >= 0; --i) {
+                    entityHeaderViews[i].render(deep, instant);
+                }
+
+                var viewHeaderViews = _.values(_this._cachedViewHeaderViewsByClientId);
+                for (var i = viewHeaderViews.length - 1; i >= 0; --i) {
+                    viewHeaderViews[i].render(deep, instant);
+                }
+
+                var visInstanceViews = _.values(_this._cachedVisInstanceViewsByClientIdPair);
+                for (var i = visInstanceViews.length - 1; i >= 0; --i) {
+                    visInstanceViews[i].render(deep, instant);
+                }
+            };
+        },
+
+        getEntityWidth: function() {
+            var _this = this;
+            return (_this.options.configGrid.get("entityWidth") || App.options.defaultEntityWidth) * 1;
+        },
+
+        _updateLayout: function(deep, instant) {
+            var _this = this;
+
+            // Check if entities of views have changed
+            var entityListHasChanged = false;
+            var viewListHasChanged = false;
+            var newEntityConfigs = _this.options.configGrid.get("entityConfigs");
+            var newViewConfigs = _this.options.configGrid.get("viewConfigs");
+            var newEntityConfigClientIds = _.pluck(newEntityConfigs.models, "cid");
+            var newViewConfigClientIds = _.pluck(newViewConfigs.models, "cid");
+
+            _this._scrollLeftBeforeLatestLayoutUpdate = _this.$el.scrollLeft();
+            _this._scrollTopBeforeLatestLayoutUpdate = _this.$el.scrollTop();
+
+            if (_this._cachedEntityConfigClientIds === null) {
+                entityListHasChanged = true;
+                viewListHasChanged = true;
+                _this._cachedEntityConfigClientIds = [];
+                _this._cachedViewConfigClientIds = [];
+            }
+
+            var createdEntityConfigClientIds = _.difference(newEntityConfigClientIds, _this._cachedEntityConfigClientIds);
+            var createdViewConfigClientIds   = _.difference(newViewConfigClientIds,   _this._cachedViewConfigClientIds);
+            var removedEntityConfigClientIds = _.difference(_this._cachedEntityConfigClientIds, newEntityConfigClientIds);
+            var removedViewConfigClientIds   = _.difference(_this._cachedViewConfigClientIds,   newViewConfigClientIds);
+
+            _this._latestChangeWasAReset = false;
+
+            if (createdEntityConfigClientIds.length + removedEntityConfigClientIds.length == 1) {
+                _this._ignoreYOnNextScroll = true;
+            }
+            if (createdViewConfigClientIds.length + removedViewConfigClientIds.length == 1) {
+                _this._ignoreXOnNextScroll = true;
+            }
+
+            if (!_.isEqual(newEntityConfigClientIds, _this._cachedEntityConfigClientIds)) {
+                entityListHasChanged = true;
+            }
+            if (!_.isEqual(newViewConfigClientIds, _this._cachedViewConfigClientIds)) {
+                viewListHasChanged = true;
+            }
+
+            if ((entityListHasChanged && createdEntityConfigClientIds.length + removedEntityConfigClientIds.length > 1)
+                    ||  (viewListHasChanged &&  createdViewConfigClientIds.length   + removedViewConfigClientIds.length   > 1)) {
+                _this._latestChangeWasAReset = true;
+                _this._ignoreXOnNextScroll = false;
+                _this._ignoreYOnNextScroll = false;
+            }
+
+
+            var newEntityHeaderViewsByClientId        = _this._cachedEntityHeaderViewsByClientId;
+            var newViewHeaderViewsByClientId          = _this._cachedViewHeaderViewsByClientId;
+            var newVisInstanceViewsByClientIdPair = _this._cachedVisInstanceViewsByClientIdPair;
+            var viewHeaderViewsToRender          = [];
+            var entityHeaderViewsToRender        = [];
+            var visInstanceViewsToRender         = [];
+
+            // Replacement of entity headers if needed
+            if (entityListHasChanged) {
+                newEntityHeaderViewsByClientId = {};
+                for (var i = 0; i < newEntityConfigClientIds.length; i++) {
+                    var currentEntityClientId = newEntityConfigClientIds[i];
+
+                    // Look for an existing entity header view
+                    var entityHeaderView = _this._cachedEntityHeaderViewsByClientId[currentEntityClientId];
+
+                    // Create a new entity header if it does not exist
+                    if (!entityHeaderView) {
+                        _this._logger.debug("generate entity header ", currentEntityClientId);
+                        entityHeaderView = _this._generateEntityHeaderView(newEntityConfigs.get(currentEntityClientId));
+                        entityHeaderViewsToRender.push(entityHeaderView);
+                        _this._$entityHeadersContainer.append(entityHeaderView.el);
+                    }
+                    newEntityHeaderViewsByClientId[currentEntityClientId] = entityHeaderView;
+                }
+
+                // Remove entity header views that are no longer needed
+                // The order of the views does not matter as the right layout is achieved with absolute positioning
+                for (var i = removedEntityConfigClientIds.length - 1; i >= 0; --i) {
+                    _this._cachedEntityHeaderViewsByClientId[removedEntityConfigClientIds[i]].remove();
+                    App.dynamicDerivedConfigDataProvider.retire(removedEntityConfigClientIds[i]);
+                }
+            } else {
+                newEntityHeaderViewsByClientId = _this._cachedEntityHeaderViewsByClientId;
+            }
+
+            // Replacement of view headers if needed
+            if (viewListHasChanged) {
+                newViewHeaderViewsByClientId = {};
+                for (var i = 0; i < newViewConfigClientIds.length; i++) {
+                    var currentViewClientId = newViewConfigClientIds[i];
+
+                    // Look for an existing entity header
+                    var viewHeaderView = _this._cachedViewHeaderViewsByClientId[currentViewClientId];
+
+                    // Create a new view header if it does not exist
+                    if (!viewHeaderView) {
+                        _this._logger.debug("generate view header ", currentViewClientId);
+                        viewHeaderView = _this._generateViewHeaderView(newViewConfigs.get(currentViewClientId));
+                        viewHeaderViewsToRender.push(viewHeaderView);
+                        _this._$viewHeadersContainer.append(viewHeaderView.el);
+                    }
+                    newViewHeaderViewsByClientId[currentViewClientId] = viewHeaderView;
+                }
+
+                // Remove entity header views that are no longer needed
+                // The order of the views does not matter as the right layout is achieved with absolute positioning
+                for (var i = removedViewConfigClientIds.length - 1; i >= 0; --i) {
+                    _this._cachedViewHeaderViewsByClientId[removedViewConfigClientIds[i]].remove();
+                    App.dynamicDerivedConfigDataProvider.retire(removedViewConfigClientIds[i]);
+                }
+            } else {
+                newViewHeaderViewsByClientId = _this._cachedViewHeaderViewsByClientId;
+            }
+
+            // Replacement of vis instances
+            if (viewListHasChanged || entityListHasChanged) {
+                newVisInstanceViewsByClientIdPair = {};
+                for (var i = 0; i < newEntityConfigClientIds.length; i++) {
+                    var currentEntityClientId = newEntityConfigClientIds[i];
+
+                    for (var j = 0; j < newViewConfigClientIds.length; j++) {
+                        var currentViewClientId = newViewConfigClientIds[j];
+                        var currentClientIdPair = currentEntityClientId + currentViewClientId;
+
+                        // Look for an existing vis instance view
+                        var visInstanceView = _this._cachedVisInstanceViewsByClientIdPair[currentClientIdPair];
+
+                        // Create a new vis instance view if it does not exist
+                        if (!visInstanceView) {
+                            _this._logger.debug("generate vis instance", currentEntityClientId, currentViewClientId);
+                            visInstanceView = _this._generateVisInstanceView(newEntityConfigs.get(currentEntityClientId), newViewConfigs.get(currentViewClientId));
+                            visInstanceViewsToRender.push(visInstanceView);
+                            _this._$visInstancesContainer.append(visInstanceView.el);
+                        }
+                        newVisInstanceViewsByClientIdPair[currentClientIdPair] = visInstanceView;
+                    }
+                }
+
+                // Add new vis instances and remove those that are no longer needed
+                // The order does not matter as the right layout is achieved with absolute positioning
+                for (var i = this._cachedEntityConfigClientIds.length - 1; i >= 0; --i) {
+                    for (var j = removedViewConfigClientIds.length - 1; j >= 0; --j) {
+                        var visInstanceToRemove = _this._cachedVisInstanceViewsByClientIdPair[this._cachedEntityConfigClientIds[i] + removedViewConfigClientIds[j]];
+                            visInstanceToRemove.remove();
+                    }
+                }
+                for (var i = removedEntityConfigClientIds.length - 1; i >= 0; --i) {
+                    for (var j = this._cachedViewConfigClientIds.length - 1; j >= 0; --j) {
+                        var visInstanceToRemove = _this._cachedVisInstanceViewsByClientIdPair[removedEntityConfigClientIds[i] + this._cachedViewConfigClientIds[j]];
+                        if (visInstanceToRemove) {
+                            visInstanceToRemove.remove();
+                        }
+                    }
+                }
+            }
+
+            // view heights
+            var viewHeightsHaveChanged = false;
+            var entityWidthHasChanged = false;
+
+            var entityWidth = _this.getEntityWidth();
+            var viewContentHeights = [];
+            for (var row = 0; row < newViewConfigClientIds.length; row++) {
+                var currentViewConfigClientId = newViewConfigClientIds[row];
+                var viewConfig = newViewConfigs.get(currentViewConfigClientId);
+                var viewContentHeight = App.RepresentationModule.getMasterForConfig(viewConfig).calculateVisInstanceContentHeight(viewConfig, entityWidth);
+                viewContentHeights.push(viewContentHeight);
+            }
+
+            if (entityWidth !== _this._cachedEntityWidth) {
+                entityWidthHasChanged = true;
+                _this._cachedEntityWidth = entityWidth;
+            }
+            if (!_.isEqual(viewContentHeights, _this._cachedViewContentHeights)) {
+                viewHeightsHaveChanged = true;
+                _this._cachedViewContentHeights = viewContentHeights;
+            }
+
+            // Set up positions and sizes for entities and views
+            if (viewListHasChanged || entityListHasChanged || entityWidthHasChanged || viewHeightsHaveChanged) {
+                var newEntityDimensions = []; //entityId, x, width
+                var newViewDimensions   = []; //viewId,   y, height
+                var x = 0;
+                var configGridType = _this.options.configGrid.getType();
+                for (var col = 0; col < newEntityConfigClientIds.length; col++) {
+                    var currentEntityConfigClientId = newEntityConfigClientIds[col];
+                    newEntityDimensions.push([currentEntityConfigClientId, x, entityWidth]);
+                    x += entityWidth + _this._distanceBetweenEntities;
+                }
+                var y = 0;
+                var viewHeaderHeight = _this._viewHeaderHeight;
+                for (var row = 0; row < newViewConfigClientIds.length; row++) {
+                    var currentViewConfigClientId = newViewConfigClientIds[row];
+                    newViewDimensions.push([currentViewConfigClientId, y, _this._cachedViewContentHeights[row]]);
+                    y += viewHeaderHeight + _this._cachedViewContentHeights[row] + _this._distanceBetweenViews;
+                }
+
+                // Reposition and resize view headers
+                for (var row = newViewDimensions.length - 1; row >= 0; --row) {
+                    var currentViewDimensions = newViewDimensions[row];
+                    var viewHeaderView = newViewHeaderViewsByClientId[currentViewDimensions[0]];
+                    viewHeaderView.$el.css("top", currentViewDimensions[1])
+                        .attr("data-top", currentViewDimensions[1])
+                        .attr("data-total-height", currentViewDimensions[2] + _this._viewHeaderHeight);
+                    viewHeaderView.setSize(currentViewDimensions[2]);
+                }
+                for (var col = newEntityDimensions.length - 1; col >= 0; --col) {
+                    var currentEntityDimensions = newEntityDimensions[col];
+
+                    // Reposition and resize entity headers
+                    var entityHeaderView = newEntityHeaderViewsByClientId[currentEntityDimensions[0]];
+                    entityHeaderView.$el.css("left", currentEntityDimensions[1])
+                        .attr("data-left", currentEntityDimensions[1])
+                        .attr("data-width", currentEntityDimensions[2]);
+                    entityHeaderView.setSize(currentEntityDimensions[2]);
+
+                    for (var row = newViewDimensions.length - 1; row >= 0; --row) {
+                        var currentViewDimensions = newViewDimensions[row];
+
+                        // Reposition and resize vis instances
+                        var visInstanceView = newVisInstanceViewsByClientIdPair[currentEntityDimensions[0] + currentViewDimensions[0]];
+                        visInstanceView.$el.css({
+                            "left": currentEntityDimensions[1],
+                            "top": currentViewDimensions[1] + viewHeaderHeight
+                        });
+                        visInstanceView.setSize(currentEntityDimensions[2], currentViewDimensions[2]);
+                    }
+                }
+
+                // Reposition entity and view adders
+                var needToResizeSpace = false;
+                if (x != _this._$entityAdder.css("left")) {
+                    _this._$entityAdder.css("left", x);
+                    needToResizeSpace = true;
+                }
+                if (y != _this._$viewAdder.css("top")) {
+                    _this._$viewAdder.css("top", y);
+                    needToResizeSpace = true;
+                }
+
+                // Resize the space
+                if (needToResizeSpace) {
+                    _this._cachedMinSpaceWidth = x + _this._entityAdderWidth;
+                    _this._cachedMinSpaceHeight = y + _this._viewAdderHeight;
+                    _this._reviseSpaceSize(!_this._latestChangeWasAReset);
+                }
+            }
+
+            // Update cached view data
+            if (entityListHasChanged) {
+                _this._cachedEntityHeaderViewsByClientId = newEntityHeaderViewsByClientId;
+                _this._cachedEntityConfigClientIds = newEntityConfigClientIds;
+            }
+            if (viewListHasChanged) {
+                _this._cachedViewHeaderViewsByClientId = newViewHeaderViewsByClientId;
+                _this._cachedViewConfigClientIds = newViewConfigClientIds;
+            }
+            if (viewListHasChanged || entityListHasChanged) {
+                _this._cachedVisInstanceViewsByClientIdPair = newVisInstanceViewsByClientIdPair;
+            }
+
+            // Render new vis instances and those that have changed their dimensions
+            // If deep rendering takes place, all view instances will be rendered later
+
+            if (!deep && entityHeaderViewsToRender) {
+                for (var i = entityHeaderViewsToRender.length - 1; i >= 0; --i) {
+                    entityHeaderViewsToRender[i].render();
+                }
+            }
+            if (!deep && viewHeaderViewsToRender) {
+                for (var i = viewHeaderViewsToRender.length - 1; i >= 0; --i) {
+                    viewHeaderViewsToRender[i].render();
+                }
+            }
+            if (!deep && visInstanceViewsToRender) {
+                for (var i = visInstanceViewsToRender.length - 1; i >= 0; --i) {
+                    visInstanceViewsToRender[i].render();
+                }
+            }
+
+            // Destroy detached vis instances
+            if (viewListHasChanged || entityListHasChanged) {
+                var destroyedVisInstanceViews = _.difference(_.values(this._cachedVisInstanceViewsByClientIdPair), _.values(newVisInstanceViewsByClientIdPair));
+                for (var i = destroyedVisInstanceViews.length - 1; i >= 0; --i) {
+                    destroyedVisInstanceViews[i].remove();
+                }
+            }
+        },
+
+        _updateSelection: function(deep, instant) {
+            var _this = this;
+
+            var selectedEntityConfig = _this.options.configGrid.getSelectedEntityConfig();
+            var selectedViewConfig = _this.options.configGrid.getSelectedViewConfig();
+            var selectedEntityConfigClientId = selectedEntityConfig ? selectedEntityConfig.getClientId() : null;
+            var selectedViewConfigClientId = selectedViewConfig ? selectedViewConfig.getClientId() : null;
+
+            if (_this._cachedSelectedEntityConfigClientId !== selectedEntityConfigClientId) {
+                _this._cachedSelectedEntityConfigClientId = selectedEntityConfigClientId;
+            }
+            if (_this._cachedSelectedViewConfigClientId !== selectedViewConfigClientId) {
+                _this._cachedSelectedViewConfigClientId = selectedViewConfigClientId;
+            }
+//
+//            // entity headers
+//            var selectionChanged = false;
+//                selectionChanged = true;
+//                if (_this._cachedSelectedEntityConfigClientId) {
+//                    $(_this._cachedEntityHeaderViewsByClientId[_this._cachedSelectedEntityConfigClientId])
+//                        .toggleSelected(false);
+//                }
+//                if (newSelectedEntityConfigClientId) {
+//                    $(_this._cachedEntityHeaderViewsByClientId[newSelectedEntityConfigClientId])
+//                        .toggleSelected(true);
+//                }
+//                _this._cachedSelectedEntityConfigClientId = newSelectedEntityConfigClientId;
+//            };
+//
+//            // view headers
+//            if (_this._cachedSelectedViewConfigClientId !== newSelectedViewConfigClientId) {
+//                selectionChanged = true;
+//                if (_this._cachedSelectedViewConfigClientId) {
+//                    $(_this._cachedViewHeaderViewsByClientId[_this._cachedSelectedViewConfigClientId])
+//                        .toggleSelected(false);
+//                }
+//                if (newSelectedViewConfigClientId) {
+//                    $(_this._cachedViewHeaderViewsByClientId[newSelectedViewConfigClientId])
+//                        .toggleSelected(true);
+//                }
+//                _this._cachedSelectedViewConfigClientId = newSelectedViewConfigClientId;
+//            };
+        },
+
+        scrollAccordingToSelection: function(deep, instant) {
+            var _this = this;
+
+            return;
+            var entityConfigClientId = _this._cachedSelectedEntityConfigClientId;
+            var viewConfigClientId   = _this._cachedSelectedViewConfigClientId;
+
+            var dimensions = {
+                    left: 0,
+                    top: 0,
+                    width: 0,
+                    height: 0
+            };
+
+            if (entityConfigClientId) {
+                dimensions.left =  parseInt(_this._cachedEntityHeaderViewsByClientId[entityConfigClientId].el.getAttribute("data-left"), 10);
+                dimensions.width = parseInt(_this._cachedEntityHeaderViewsByClientId[entityConfigClientId].el.getAttribute("data-width"), 10);
+            }
+            if (viewConfigClientId) {
+                dimensions.top    = parseInt(_this._cachedViewHeaderViewsByClientId[viewConfigClientId].el.getAttribute("data-top"), 10);
+                dimensions.height = parseInt(_this._cachedViewHeaderViewsByClientId[viewConfigClientId].el.getAttribute("data-total-height"), 10);
+            }
+            var desiredPaddingAroundVisInstanceOnScroll = _this.options.desiredPaddingAroundVisInstanceOnScroll;
+            var spacePadding = _this._spacePadding;
+
+            var targetScrollRange = {
+                    leftMax: dimensions.left - desiredPaddingAroundVisInstanceOnScroll.left,
+                    topMax:  dimensions.top  - desiredPaddingAroundVisInstanceOnScroll.top - _this._viewHeaderHeight,
+                    leftMin: dimensions.left + desiredPaddingAroundVisInstanceOnScroll.right  + dimensions.width  - _this._$containerOfScrollable[0].clientWidth  + spacePadding.left,
+                    topMin:  dimensions.top  + desiredPaddingAroundVisInstanceOnScroll.bottom + dimensions.height - _this._$containerOfScrollable[0].clientHeight + spacePadding.top
+            };
+
+            var currentScroll = {
+                    left: (_this._scrollLeftBeforeLatestLayoutUpdate == null ? _this._$containerOfScrollable.scrollLeft() : _this._scrollLeftBeforeLatestLayoutUpdate),
+                    top:  (_this._scrollTopBeforeLatestLayoutUpdate  == null ? _this._$containerOfScrollable.scrollTop()  : _this._scrollTopBeforeLatestLayoutUpdate)
+            };
+
+            var targetScrollLeft = currentScroll.left;
+            var targetScrollTop  = currentScroll.top;
+
+            if (_this._latestChangeWasAReset) {
+                _this._cachedScrollPosSelection = null;
+                targetScrollLeft = 0;
+                targetScrollTop  = 0;
+            }
+
+            var currentGridHash = _this.getEntityWidth() + "~" + _this._cachedEntityConfigClientIds.join("|")  + "~" + _this._cachedViewConfigClientIds.join("|");
+            //_this._logger.debug("scrollAccordingToSelection[1]:", _this._cachedScrollPosGridHash  === currentGridHash, _this._cachedScrollPosGridHash, currentGridHash);
+            //_this._logger.debug("scrollAccordingToSelection[2]:", _this._cachedScrollPosSelection === "" + entityConfigClientId + viewConfigClientId, _this._cachedScrollPosSelection, "" + entityConfigClientId + viewConfigClientId);
+            if (_this._cachedScrollPosGridHash  === currentGridHash
+             && _this._cachedScrollPosSelection === "" + entityConfigClientId + viewConfigClientId) {
+                targetScrollLeft = _this._cachedScrollPosX;
+                targetScrollTop = _this._cachedScrollPosY;
+                _this._cachedScrollPosSelection = "";
+            } else {
+                if (!_this._ignoreXOnNextScroll) {
+                    if (targetScrollLeft < targetScrollRange.leftMin) {
+                        targetScrollLeft = targetScrollRange.leftMin;
+                    }
+                    if (targetScrollLeft > targetScrollRange.leftMax) {
+                        targetScrollLeft = targetScrollRange.leftMax;
+                    }
+                }
+                if (!_this._ignoreYOnNextScroll) {
+                    if (targetScrollTop < targetScrollRange.topMin) {
+                        targetScrollTop = targetScrollRange.topMin;
+                    }
+                    if (targetScrollTop > targetScrollRange.topMax) {
+                        targetScrollTop = targetScrollRange.topMax;
+                    }
+                }
+                if (targetScrollLeft < 0) {
+                    targetScrollLeft = 0;
+                }
+                if (targetScrollTop < 0) {
+                    targetScrollTop = 0;
+                }
+            }
+
+            var scrollDiffX = targetScrollLeft - currentScroll.left;
+            var scrollDiffY = targetScrollTop  - currentScroll.top;
+
+            if (!entityConfigClientId && !viewConfigClientId && !_this._latestChangeWasAReset) {
+                _this._updateCachedScroll();
+            } else if (instant || _this._latestChangeWasAReset) {
+                _this._$containerOfScrollable.stop(true, true);
+                _this._$containerOfScrollable.scrollLeft(targetScrollLeft);
+                _this._$containerOfScrollable.scrollTop (targetScrollTop);
+                _this._updateCachedScroll();
+                _this._reviseSpaceSize();
+            } else {
+                _this._$containerOfScrollable.stop(true, false);
+                if (_this._scrollLeftBeforeLatestLayoutUpdate !== null) {
+                    _this._$containerOfScrollable.scrollLeft(_this._scrollLeftBeforeLatestLayoutUpdate);
+                    _this._$containerOfScrollable.scrollTop (_this. _scrollTopBeforeLatestLayoutUpdate);
+                }
+                _this._$containerOfScrollable.animate(
+                        {"scrollLeft": targetScrollLeft, "scrollTop": targetScrollTop},
+                        _this.options.scrollAnimationMinSpeed + Math.min(Math.max(Math.abs(scrollDiffX), Math.abs(scrollDiffY)), _this.options.scrollAnimationBaseDistance) / _this.options.scrollAnimationBaseDistance * _this.options.scrollAnimationBaseSpeed,
+                        function() {
+                            _this._reviseSpaceSize();
+                        });
+            }
+            _this._ignoreXOnNextScroll = false;
+            _this._ignoreYOnNextScroll = false;
+        },
+
+        /**
+         * Returns the positions of the currently selected items
+         * relative to the top-left corner of the top-left view
+         */
+        getPositionsOfSelectedHeaders: function() {
+            var _this = this;
+
+            var result =  [];
+
+            var selectedEntityHeaderView = _this._cachedEntityHeaderViewsByClientId[_this._cachedSelectedEntityConfigClientId];
+            var selectedViewHeaderView   = _this._cachedViewHeaderViewsByClientId  [_this._cachedSelectedViewConfigClientId];
+
+            if (selectedEntityHeaderView) {
+                var $selectedEntityHeader = selectedEntityHeaderView.$el;
+                result.push(parseInt($selectedEntityHeader.css("left"), 10) - _this._$containerOfScrollable.scrollLeft(), parseInt($selectedEntityHeader[0].style.width, 10));
+            } else {
+                result.push(null, null);
+            }
+
+            if (selectedViewHeaderView) {
+                var $selectedViewHeader = selectedViewHeaderView.$el;
+                result.push(parseInt($selectedViewHeader.css("top"), 10)  - _this._$containerOfScrollable.scrollTop(), _this._viewHeaderHeight);
+            } else {
+                result.push(null, null);
+            }
+
+            return result;
+        },
+
+        _updateCachedScroll: function() {
+            var _this = this;
+
+            _this._cachedScrollPosGridHash = _this.getEntityWidth() + "~" + _this._cachedEntityConfigClientIds.join("|")  + "~" + _this._cachedViewConfigClientIds.join("|");
+            _this._cachedScrollPosSelection = "" + _this._cachedSelectedEntityConfigClientId + _this._cachedSelectedViewConfigClientId;
+            _this._cachedScrollPosX = _this._$containerOfScrollable.scrollLeft();
+            _this._cachedScrollPosY = _this._$containerOfScrollable.scrollTop();
+
+            //_this._logger.debug(_.str.sprintf("ConfigGridCellsView[%s]._updateCachedScroll()", _this.options.configGrid.getType()), _this._cachedScrollPosGridHash, _this._cachedScrollPosSelection, _this._cachedScrollPosX, _this._cachedScrollPosY);
+        },
+
+        _toggleFixedHeadersIfNeeded: function(trueOrFalse) {
+            //trueOrFalse = true;
+            var _this = this;
+            if (_this.options.enableFixedHeaders && trueOrFalse == true) {
+                //_this._logger.debug("_toggleFixedHeadersIfNeeded(true)");
+                if (!_this._$containerOfFixed[0].childNodes.length) {
+                    _this._$containerOfFixed.append(_this._$visInstancesContainer, _this._$entityHeadersContainer, _this._$viewHeadersContainer, _this._$cornerBlind);
+                    //_this._$containerOfFixed.append(_this._$entityHeadersContainer, _this._$viewHeadersContainer, _this._$cornerBlind);
+                    _this._$cornerBlind.css({"transform": "translate(0px, 0px)"});
+                    _this._$containerOfFixed.show();
+                }
+            } else {
+                //_this._logger.debug("_toggleFixedHeadersIfNeeded(false)");
+                _this._$containerOfFixed.hide();
+                _this._$space.append(_this._$visInstancesContainer, _this._$entityHeadersContainer, _this._$viewHeadersContainer, _this._$cornerBlind);
+                //_this._$space.append(_this._$entityHeadersContainer, _this._$viewHeadersContainer, _this._$cornerBlind);
+                _this._$entityHeadersBlind.css({"transform": "translate(0px, 0px)"});
+                _this._$visInstancesContainer.css({"transform": "translate(0px, 0px)"});
+
+            }
+            _this._adjustToScrollPos();
+        },
+
+        _adjustToScrollPos: function() {
+            var _this = this;
+            if (_this._scrollLeftBeforeLatestLayoutUpdate !== null) {
+                _this._scrollLeftBeforeLatestLayoutUpdate = null;
+                _this._scrollTopBeforeLatestLayoutUpdate  = null;
+            }
+
+            var scrollLeft = _this._$containerOfScrollable.scrollLeft();
+            var scrollTop = _this._$containerOfScrollable.scrollTop();
+            if (_this._$containerOfFixed[0].childNodes.length) {
+                _this._$entityHeadersContainer.css({"transform": "translate(" + (-scrollLeft) + "px, 0px)"});
+                _this._$entityHeadersBlind.css({"transform": "translate(" + scrollLeft + "px, 0px)"});
+                _this._$visInstancesContainer.css({"transform": "translate(" + (-scrollLeft) + "px, " + (-scrollTop)  + "px)"});
+                _this._$viewHeadersContainer.css({"transform": "translate(0px, " + (-scrollTop) + "px)"});
+            } else {
+                _this._$entityHeadersContainer.css({"transform": "translate(0, " + scrollTop + "px)"});
+                _this._$viewHeadersContainer.css({"transform": "translate(" + scrollLeft + "px, 0)"});
+                _this._$cornerBlind.css({"transform": "translate(" + scrollLeft + "px, " + scrollTop  + "px)"});
+            }
+
+            _this._reportPositionsOfSelectedHeaders();
+        },
+
+        _reportPositionsOfSelectedHeaders: function() {
+            var _this = this;
+
+            var positionsOfSelectedHeaders = _this.getPositionsOfSelectedHeaders();
+            _this.trigger("change-positions-of-selected-headers", positionsOfSelectedHeaders[0], positionsOfSelectedHeaders[1], positionsOfSelectedHeaders[2], positionsOfSelectedHeaders[3]);
+        },
+
+
+        _reviseSpaceSize: function(increaseOnly) {
+            var _this = this;
+
+            var elInnerWidth = _this._$containerOfScrollable[0].clientWidth;
+            var elInnerHeight = _this._$containerOfScrollable[0].clientHeight;
+            var elScrollLeft = _this._$containerOfScrollable.scrollLeft();
+            var elScrollTop = _this._$containerOfScrollable.scrollTop();
+            var spaceWidth = _this._cachedMinSpaceWidth;
+            var spaceHeight = _this._cachedMinSpaceHeight;
+
+            var missingWidth =  elScrollLeft + elInnerWidth  - _this._spacePadding.h - spaceWidth;
+            var missingHeight = elScrollTop  + elInnerHeight - _this._spacePadding.v - spaceHeight;
+
+            spaceWidth += Math.max(0, missingWidth);
+            spaceHeight += Math.max(0, missingHeight);
+
+            if (!increaseOnly || (spaceWidth >= _this._$space.width() && spaceHeight >= _this._$space.height())) {
+                _this._$space.css({
+                    "width": spaceWidth,
+                    "height": spaceHeight
+                });
+                _this._$viewHeadersBlind.height(spaceHeight);
+            }
+        },
+
+        _updateDemensionsForContainerOfFixed: function() {
+            var _this = this;
+            _this._$containerOfFixed.width(_this._$containerOfScrollable[0].clientWidth);
+            _this._$containerOfFixed.height(_this._$containerOfScrollable[0].clientHeight);
+        },
+
+        _generateEntityHeaderView: function(entityConfig) {
+            var _this = this;
+
+            var $el = $.bem.generateElement("config-grid-cells", "entity-header");
+
+            var result = new App.MainRegionModule.ConfigHeaderView({
+                dimension: "entity",
+                el: $el,
+                state: _this.options.state,
+                configGrid: _this.options.configGrid,
+                config: entityConfig,
+                parentConfigGridView: _this.options.parentConfigGridView
+            });
+
+            result.$el.dblclick(function() {
+                if (entityConfig.getParameterValue("kind")) {
+                    return;
+                }
+                if (_this.options.configGrid.getSelectedEntityConfig() == entityConfig) {
+                    _this.options.configGrid.addEntityAndSelectIt(new App.ContextModule.Config({
+                        parameters: {
+                            "kind": "pair",
+                            "comparisonMode": "superposition"
+                        }
+                    }), _this.options.configGrid.getNextEntityNeighbour(entityConfig)) ;
+                }
+            });
+
+            return result;
+        },
+
+        _generateViewHeaderView: function(viewConfig) {
+            var _this = this;
+
+            var $el = $.bem.generateElement("config-grid-cells", "view-header");
+
+            var result = new App.MainRegionModule.ConfigHeaderView({
+                dimension: "view",
+                el: $el,
+                state: _this.options.state,
+                configGrid: _this.options.configGrid,
+                config: viewConfig,
+                parentConfigGridView: _this.options.parentConfigGridView
+            });
+
+            return result;
+        },
+
+        _generateVisInstanceView: function(entityConfig, viewConfig) {
+            var _this = this;
+
+            var $el = $.bem.generateElement("config-grid-cells", "vis-instance").addClass("vis-instance");
+
+            var result = new App.MainRegionModule.VisInstanceView({
+                el: $el,
+                state: _this.options.state,
+                configGrid: _this.options.configGrid,
+                entityConfig: entityConfig,
+                viewConfig: viewConfig,
+                parentConfigGridView: _this.options.parentConfigGridView
+            });
+
+            return result;
+        }
+    });
+}, Logger);
\ No newline at end of file