view src/DML/MainVisBundle/Resources/assets/marionette/modules/MainRegionModule/MainRegionModule.20-ConfigGridCellsView.js @ 1:f38015048f48 tip

Added GPL
author Daniel Wolff
date Sat, 13 Feb 2016 20:43:38 +0100
parents 493bcb69166c
children
line wrap: on
line source
"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);