text: /**
   * Created with JetBrains PhpStorm.
   * User: tm.baeva
   * Date: 22.04.13
   * Time: 1:05
   * To change this template use File | Settings | File Templates.
   */
  define('Deprecated/Controls/TableView/TableView', [
     'Core/helpers/String/escapeHtml',
     'View/Executor/Utils',
     'Core/helpers/String/escapeTagsFromStr',
     'Core/helpers/Object/isEmpty',
     'Lib/Control/Infobox/Infobox',
     'Core/core-merge',
     'Core/helpers/Function/debounce',
     'Core/helpers/String/format',
     'Deprecated/RecordSet',
     'Deprecated/helpers/Hcontrol/getTextWidth',
     'Core/defaultRenders',
     'Env/Env',
     'Deprecated/Controls/DataViewAbstract/DataViewAbstract',
     'html!Deprecated/Controls/TableView/TableViewRow',
     'html!Deprecated/Controls/TableView/TableView',
     'Core/DependencyResolver',
     'css!Deprecated/Controls/TableView/TableView',
     'i18n!Deprecated/Controls/TableView/TableView'
  ], function(
     escapeHtml,
     Utils,
     escapeTagsFromStr,
     objIsEmpty,
     CInfobox,
     cMerge,
     debounce,
     stringFormat,
     dTransportRecordSet,
     getTextWidth,
     defaultRenders,
     Env,
     DataViewAbstract,
     dotTplFnForRow,
     dotTplFn,
     DependencyResolver
  ) {
  
     'use strict';
  
     var MAX_DELEGATED_SELECTORS = 10;
  
     Env.constants.Browser = Env.constants.Browser || {};
     Env.constants.Browser.iconWidth = 16;
     Env.constants.Browser.tabWidth = 16;
     Env.constants.Browser.defaultCellPadding = 7;
     Env.constants.Browser.iconPadding = 3;
     Env.constants.Browser.minColumnWidth = 8;
     Env.constants.Browser.defColWidth = {
        'Логическое' : '20px',
        'Число целое' : '40px',
        'Деньги' : '85px',
        'Дата' : '80px',
        'Дата и время' : '80px',
        'another' : '50px'
     };
     Env.constants.Browser.type2ClassMap = {
        'Деньги': 'money',
        'Число целое': 'integer',
        'Число вещественное': 'integer'
     };
     Env.constants.Browser.selectionCheckboxWidth = 24;
     Env.constants.Browser.pathSelectorHeight = 21;
  
     Env.constants.TableView = {
        tableRowPadding: 5 //Отступ текста внутри ячейки таблицы
     };
  
     DependencyResolver.register('Deprecated/Controls/TableView/TableView', function(config){
        var deps = {};
  
        if(config) {
           var display = config.display || {};
           if(display.ladder && display.ladder.length) {
              deps['Deprecated/Controls/TableView/plugins/Ladder-plugin'] = 1;
           }
  
           if(display.columns !== undefined){
              var allowEditAtThePlace = false,
                 needResults = false,
                 columns = display.columns,
                 l = columns.length,
                 column = {};
              for(var i = 0; i < l; i++){
                 column = columns[i];
                 if(column !== undefined){
                    if(column.allowEditAtThePlace) {
                       allowEditAtThePlace = true;
                    }
                    if(column.isResultField) {
                       needResults = true;
                    }
                 }
                 if(allowEditAtThePlace && needResults) {
                    break;
                 }
              }
              if(display.editAtThePlaceTemplate) {
                 allowEditAtThePlace = true;
              }
              if(allowEditAtThePlace) {
                 deps['Deprecated/Controls/TableView/plugins/AtPlaceEdit-plugin'] = 1;
              }
              if(needResults) {
                 deps['Deprecated/Controls/TableView/plugins/Results-plugin'] = 1;
              }
           }
  
           if(config.colorMark) {
              deps['Deprecated/Controls/TableView/plugins/ColorMark-plugin'] = 1;
           }
           if(/ws-right-accordion/.test(config.className) || display.isRightAccordion){
              deps['Deprecated/Controls/TableView/plugins/RightAccordion-plugin/RightAccordion-plugin'] = 1;
           }
        }
        return Object.keys(deps);
     }, 'Deprecated/Controls/DataViewAbstract/DataViewAbstract');
  
     /**
      *
      * @class Deprecated/Controls/TableView/TableView
      * @extends Deprecated/Controls/DataViewAbstract/DataViewAbstract
      * @author Крайнов Д.О.
      * @control
      * @public
      * @initial
      * <component data-component='Deprecated/Controls/TableView/TableView'>
      * <options name='display'>
      * <options name='columns' type='array'>
      * <options>
      *    <option name='title' value='поле1'></option>
      *    <option name='field' value='поле1'></option>
      *    <option name='width' value='114'></option>
      * </options>
      * <options>
      *    <option name='title' value='поле2'></option>
      *    <option name='field' value='поле2'></option>
      *    <option name='width' value='114'></option>
      * </options>
      * </options>
      * </options>
      * </component>
      * @category Table
      * @designTime plugin /design/DesignPlugin
      * @deprecated Используйте класс {@link SBIS3.CONTROLS/DataGridView}.
      */
     var TableView = DataViewAbstract.extend(/** @lends Deprecated/Controls/TableView/TableView.prototype */{
        /**
         * @event onBeforeRender Перед началом рендеринга
         * Происходит до отображения в таблице пришедших данных.
         * @param {Env/Event:Object} eventObject Дескриптор события.
         * @param {Array} columns Текущий набор колонок.
         * @return {Boolean|Array|*} Если передать:
         * <ol>
         * <li>false – отмена отображения данных,</li>
         * <li>array – воспринимается как массив новых колонок для отображения, на экран будут выведены колонки,
         * соответствующие этому описанию,</li>
         * <li>любой иной тип данных – табличный браузер отобразит данные в соответствии с текущей конфигурацией.</li>
         * </ol>
         * @example
         * <pre>
         *    dataView.subscribe('onBeforeRender', function(event, columns){
         *       // если данных не достаточно, не будем их показывать
         *       if(this.getRecordsCount() <= 10)
         *          event.setResult(false);
         *       else {
         *          // усложненный пример как можно скрыть колонку "Примечание"
         *          var newColumns = [];
         *          for(var i = 0, l = columns.length; i < l; i++ ){
         *             if(columns[i].title !== 'Примечание')
         *                newColumns.push(columns[i]);
         *          }
         *          event.setResult(newColumns);
         *       }
         *    });
         * </pre>
         */
        /**
         * @cfg {Boolean} Автовысота
         * @name Deprecated/Controls/TableView/TableView#autoHeight
         * @description
         * Будет ли контрол подстраиваться по высоте под своё содержимое.
         * Значение по умолчанию true
         *
         * Возможные значения:
         * <ol>
         *    <li>true - контрол будет подстраиваться по высоте под своё содержимое.</li>
         *    <li>false - не будет подстраиваться по высоте.</li>
         * </ol>
         * @example
         * <pre>
         *     var dfr = attachInstance('Lib/Control/TemplatedArea/TemplatedArea', {
         *        autoHeight: true,
         *        element: container,
         *        parent: this.getParent()
         *     });
         * </pre>
         * @see autoWidth
         */
        /**
         * @cfg {Boolean} Автоширина
         * @name Deprecated/Controls/TableView/TableView#autoWidth
         * @description
         * Будет ли контрол подстраиваться по ширине под своё содержимое.
         * Значение по умолчанию true
         *
         * Возможные значения:
         * <ol>
         *    <li>true - контрол будет подстраиваться по ширине под своё содержимое.</li>
         *    <li>false - не будет подстраиваться по ширине.</li>
         * </ol>
         * @example
         * <pre>
         *     var dfr = attachInstance('Lib/Control/TemplatedArea/TemplatedArea', {
         *        autoWidth : true,
         *        element : container,
         *        parent : this.getParent()
         *     });
         * </pre>
         * @see autoHeight
         */
        /**
         * @event onResetColumnFilter При сбросе фильтрации по колонке
         *
         * Происходит при изменении, сбросе параметра фильтрации, связанного с какой-либо колонкой, по которой ведётся
         * фильтрация в табличном браузере.
         * @param {Env/Event:Object} eventObject Дескриптор события.
         * @param {Object} filter Устанавливаемые параметры фильтрации.
         * @param {String} name Имя сбрасываемого параметра фильтрации.
         * @param {String} title Имя колонки, по которой сбрасываем фильтр.
         * @return {Object|*} Принимаем переданный фильтр за тот, который должны установить. Любой иной тип – применяем
         * фильтр без параметра, связанного с колонкой сброса фильтра.
         * @Example
         * <pre>
         *    tableView.subscribe('onResetColumnFilter', function(event, filter, name, title){
         *       if(name == 'ТипДокумента')
         *          event.setResult(this.getQuery(true));
         *    });
         * </pre>
         */
        /**
         * @event onRecordUp При перемещении записи вверх
         *
         * Событие, происходящее при перемещении записи на строку вверх. Работает при использовании порядковых номеров.
         * @param {Env/Event:Object} eventObject Дескриптор события.
         */
        /**
         * @event onRecordDown При перемещении записи вниз
         *
         * Событие, происходящее при перемещении записи на строку вниз. Работает при использовании порядковых номеров.
         * @param {Env/Event:Object} eventObject Дескриптор события.
         */
        /**
         * @event onBeforeChangeOrder Перед изменением порядка записей
         *
         * Событие, происходящее перед изменением порядка записей с помощью порядковых номеров.
         * @param {Env/Event:Object} eventObject Дескриптор события.
         * @param record {Deprecated/Record} Запись, которую перемещаем.
         * @param recordAbove {Deprecated/Record} Запись, над которой хотим вставить.
         * @param recordBelow {Deprecated/Record} Запись, под которой хотим вставить.
         * @param toUp {Boolean} Запись перемещается вверх, если запись перемещается вниз параметр принимает значение false,
         * при DragAndDrop перемещении параметр равен undefined.
         * @return {Boolean|*} Если передать false, то не изменяем порядок записи. Любой другой результат - изменяем порядок записи.
         * @example
         * При попытке переместить запись, значение поля "Категория" которой не совпадает со значениями этого поля записей,
         * между которыми хотим поместить перемещаемую запись, то запрещаем перемещение:
         * <pre>
         *    tableView.subscribe('onBeforeChangeOrder', function(event, record, recordAbove, recordBelow, toUp){
         *       if (recordAbove && recordBelow) {
         *          var category = recordBelow.get('Категория');
         *          if(category === recordAbove.get('Категория') && record.get('Категория') !== category) {
         *             event.setResult(false);
         *          }
         *       }
         *    });
         * </pre>
         */
        $protected: {
           _options: {
              display: {
                 /**
                  * @cfg {Columns[]} Устанавливает конфигурация колонок.
                  *
                  * Изменить видимость колонки можно с помощью метода {@link toggleColumn}. Изменить набор колонок можно с помощью метода {@link setColumns}, получить текущий набор - методом {@link getColumns}.
                  *
                  * <b>ОБЪЕДИНЕНИЕ ЗАГОЛОВКОВ</b>
                  * Чтобы объединить несколько заголовков колонок, в опцию title устанавливают одинаковое значение.
                  * Чтобы отображать объединение в две строки: в первой строки общий заголовок, во второй - частный, только для конкретной колонки - в опцию title устанавливают значение вида "ОбщееНазвание.ЧастноеНазвание".
                  * Разделение производят с помощью точки. До точки - общий заголовок, после точки - заголовок для конкретной колонки.
                  *
                  * <b>ФИЛЬТРАЦИЯ И СОРТИРОВКА</b>
                  * В описании каждой колонки могут быть добавлены следующие параметры:
                  * <ol>
                  *    <li>isSortable - разрешена ли сортировка по колонке;</li>
                  *    <li>filterDialog - диалог фильтрации по колонке;</li>
                  *    <li>filterName - имя параметра фильтрации, связанного с колонкой;</li>
                  *    <li>visualFilterFunction - функция отображения установленного фильтра, в качестве аргументов принимает устанавливаемый фильтр.</li>
                  * </ol>
                  *
                  * <b>ПОЛЬЗОВАТЕЛЬСКИЕ ФУНКЦИИ ОТОБРАЖЕНИЯ</b>
                  * Для каждой колонки можно установить пользовательские функции с помощью опций render и captionRender.
                  *
                  * <b>РЕДАКТИРОВАНИЕ И ДОБАВЛЕНИЕ ПО МЕСТУ</b>
                  * Для каждой колонки помимо ранее рассмотренных параметров могут быть заданы следующие опции:
                  * <ol>
                  *    <li>allowEditAtThePlace - в этой колонке разрешено редактирование по месту.</li>
                  *    <li>validators - набор валидаторов при редактировании по месту.</li>
                  *    <li>decimals - количество знаков после запятой (только для вещественного числа).</li>
                  * </ol>
                  *
                  * <b>СТРОКА ИТОГОВ</b>
                  * Для каждой колонки может быть установлен признак isResultField - необходим ли подсчет итогов по этой колонке.
                  * @see toggleColumn
                  * @see setColumns
                  * @see getColumns
                  * @editorConfig itemCaption title
                  */
                 columns : [],
                 /**
                  * @cfg {Boolean} Использовать сортировку
                  *
                  *
                  * Разрешена ли сортировка хотя бы по одной из колонок или же запретить по всем.
                  */
                 useSorting: false,
                 /**
                  * @cfg {Boolean} Отображать черезполосицу
                  *
                  * При установленном флаге строки таблицы будут разделены серой линией.
                  * <pre>
                  *    tableView.zebraBody(true);
                  * </pre>
                  * @see zebraBody
                  */
                 hasZebra: true,
                 /**
                  * @cfg {Boolean} Обрезать длинные строки
                  *
                  * Если флаг установлен, то все данные ячейки будут выведены в одну строку без переноса слов.
                  * В случае нехватки места в ячейке для отображения данных текст обрывается многоточием.
                  * Возможные значения:
                  * <ol>
                  *    <li>true - обрезать длинные строки;</li>
                  *    <li>false - не обрезать сроки (будет происходить увеличение высоты ячейки).</li>
                  * </ol>
                  */
                 cutLongRows: false,
                 /**
                  * @cfg {Function} Функция рендеринга строки
                  *
                  * Влияет на отображение строки таблицы.
                  * В качестве аргументов принимает текущую запись, отображение которой строим, и соответствующий ей jQuery-элемент.
                  * <pre>
                  *    rowRender: function(record, row){
                  *       if(record.get('Активен') !== true)
                  *          row.addClass('employee-inactive');
                  *    }
                  * </pre>
                  * Изменяется методом {@link setRowRender}
                  * <pre>
                  *    tableView.setRowRender(function(record, row){
                  *       if(record.get('Активен') !== true)
                  *          row.addClass('employee-inactive');
                  *    );
                  * </pre>
                  * @see setRowRender
                  */
                 rowRender : '',
                 /**
                  * @cfg {Function} Функция рендеринга ячейки шапки
                  *
                  * Изменяет отображение заголовка столбца.
                  * На входе получит имя отображаемой колонки (будет выведено в качестве заголовка колонки).
                  * Выполнится только если для этого столбца не задана функция рендеринга заголовка.
                  * Возвращает визуальное отображение заголовка колонки.
                  * @example
                  * <pre>
                  *    headColumnRender: function(columnName){
                  *       if(columnName == "Абонент")
                  *          return '<div style="text-decoration: underline;">' + columnName + '</div>';
                  *       else
                  *          return columnName;
                  *    }
                  * </pre>
                  * @see headRender
                  * @group Head
                  */
                 headColumnRender : '',
                 /**
                  * @cfg {Boolean} Отображать заголовок
                  *
                  * Задает необходимость отображения заголовков столбцов таблицы.
                  * @see headRender
                  * @group Head
                  */
                 showHead: true,
                 /**
                  * @cfg {Function} Функция рендеринга шапки
                  *
                  * Влияет на отображение заголовка таблицы в целом.
                  * В качестве аргументов принимает jQuery-элемент, соответствующий заголовку таблицы.
                  * @see showHead
                  * @see headColumnRender
                  * @group Head
                  */
                 headRender : '',
                 /**
                  * @cfg {Boolean} Отображать ли активную строку не в фокусе
                  *
                  * @group Display
                  */
                 showActiveRowInFocus: true,
                 /**
                  * @cfg {Boolean} Отображать флаг для выбора записи
                  *
                  * Возможные значения:
                  * <ul>
                  *    <li>true - слева от записей отображаются флаги;</li>
                  *    <li>false - флагов у записей нет.</li>
                  * </ul>
                  * @example
                  * <pre>
                  *     <option name="showSelectionCheckbox">true</option>
                  * </pre>
                  */
                 showSelectionCheckbox : false,
                 /**
                  * @cfg {Boolean} Отображать разделители
                  *
                  * Задает необходимость отображать разделители между колонками в шапке таблицы.
                  * @group Display
                  */
                 useSeparating: false,
                 /**
                  * @cfg {Boolean} Отображать линии таблицы
                  *
                  * Задает необходимость отображать все границы таблицы и ее ячеек.
                  * @group Display
                  */
                 useDrawingLines: false,
                 /**
                  * @cfg {String} Шаблон строки
                  *
                  * Позволяет задать собственный шаблон для строки таблицы
                  * ВАЖНО! Шаблон необходимо предзагрузить, т.е. указать в зависимостях своего компонента
                  * <pre>
                  *    rowTemplate: 'html!SBIS3.EDO.EdoTemplate'
                  * </pre>
                  * @group Display
                  */
                 rowTemplate: '',
                 /**
                  * @cfg {String} Опции для шаблона строки
                  *
                  * Позволяет задать опции для собственного шаблона для строки таблицы
                  * <pre>
                  *    rowTemplateOptions: {
                  *       'keys': []
                  *       'myOptionName': 'myOptionValue'
                  *    }
                  * </pre>
                  * @group Display
                  */
                 rowTemplateOptions: {}
              }
           },
           //Параметры, задаваемые в конструкторе
           _columnMap : [],
           _bodyColumns: '',
           _headColumns: '',
           _emptyDataScroller: undefined,          //Блок для отображения скролла при отсутствии данных
           _columnFilterMap: [],                   //Колонки на которых установлен фильтр
           _rowSelection: undefined,               //Элемент для выделения строк в новой теме
           _rowsMap: {},                           //Мап "первичный ключ" => хтмл-объект строки этой записи
           _colgroupCache: undefined,
           _markContainer: undefined,
           _selectAllCheckbox: undefined,          //Чекбокс выбора всех записей
           _selectAllCheckboxRow: undefined,       //Строка с чекбоксом выбора всех записей
           _showExtendedTooltipBinded: null,
           _hideExtendedTooltipBinded: null,
           _userColumnWidth: [],                   //Ширина колонок, заданная через метод _setColumns
           _columnMinWidthArray: [],               //Минимальная ширина каждой колонки
           _delegatedEventsCount: {},
           _cellTemplate: {},
           _rowOptionsContainer: 'ws-browser-row-options-container', //Класс для контейнера с опциями
           _rowSelector: '.ws-browser-table-row'
        },
        _dotTplFn: dotTplFn,
        $constructor: function(){
           this._publish('onResetColumnFilter', 'onRecordUp', 'onRecordDown', 'onBeforeChangeOrder');
           this.subscribe('onFilterChange', $.proxy(this._onFilterChangeHandler, this));
           this.subscribe('onNewAction', $.proxy(this._onNewAction, this));
           this.subscribe('onAfterLoad', function(){
              if (this._needShowSelectionCheckbox() && this._selectAllCheckbox instanceof Object && 'jquery' in this._selectAllCheckbox) {
                 this._selectAllCheckbox.toggleClass('ws-disabled', this._currentRecordSet.getRecordCount() === 0);
              }
           }.bind(this));
           var parentWindow = this.getParentWindow();
           if(parentWindow !== undefined) {
              this._container.addClass('ws-browser-in-dialog');
           }
  
           if (!this._container.hasClass('ws-browser-small-paddings')){
              this._container.addClass('ws-browser-normal-size');
           }
        },
        _dataBind: function(){
           var def = TableView.superclass._dataBind.apply(this, arguments);
           def.addCallback(function(){
              if (this._options.display.alwaysDisplayResults){
                 this._refreshResults();
              }
           }.bind(this));
        },
        _keyboardHover: function(e){
           var res = TableView.superclass._keyboardHover.apply(this, arguments);
           if(this.isActive()) {
              if (!e.shiftKey) {
                 if (e.ctrlKey && e.which === Env.constants.key.up) {
                    this._onRecordUpHandler();
                 } else if (e.ctrlKey && e.which === Env.constants.key.down) {
                    this._onRecordDownHandler();
                 }
              }
              if(this.isRowOptionsVisible() && this._options.useSelection && this._options.useHoverRowAsActive) {
                 if(e.which === Env.constants.key.down || e.which === Env.constants.key.up) {
                    this._hideRowOptions();
                 }
              }
           }
           return res;
        },
        _onNewAction: function(event, optionConfig){
           if(this._options.display.rowOptions){
              this.addRowOption(optionConfig);
           }
        },
        /**
         * Обработчик изменения фильтра браузера
         */
        _onFilterChangeHandler: function(event, filter){
           var id, colDef, filterName;
           if(!objIsEmpty(this._options.display.columns)){
              for(var key in this._options.display.columns){
                 if(this._options.display.columns.hasOwnProperty(key)){
                    if(this._options.display.columns[key].filterDialog){
                       colDef = this._options.display.columns[key];
                       filterName = colDef.filterName ? colDef.filterName : colDef.title;
                       if(filter[filterName]) {
                          this._columnFilterMap[filterName] = key;
                       } else{
                          delete this._columnFilterMap[filterName];
                          this._clearMarkColumnFilter(key, colDef.title);
                       }
                    }
                 }
              }
           }
           if(this._columnMap.length > 0){
              for(var value in this._columnFilterMap){
                 if(this._columnFilterMap.hasOwnProperty(value)){
                    if(filter[value]){
                       id = this._columnFilterMap[value];
                       this._markFilteredColumn(filter, id);
                    }
                 }
              }
           }
        },
        setActive: function(active) {
           TableView.superclass.setActive.apply(this, arguments);
           if(active && this._tooltipSettings.handleFocus && this._showExtendedTooltipBinded) {
              this._showExtendedTooltipBinded();
           }
        },
        /**
         *
         * Метод маркирует контрол, строку или ячейку в случае ошибки.
         * Если задан только message, то вызовется аналогичный метод предка.
         * Если заданы message и rowkey, то выделится указанная строка.
         * Если определены все параметры, то выделится указанная ячейка.
         * Не проводит валидацию.
         * @param {Array|String} message Сообщение об ошибке
         * @param {String} rowkey Первичный ключ записи
         * @param {String} columnName Название колонки
         */
        markControl: function(message, rowkey, columnName){
           if(rowkey && columnName) {
              this._markContainer = this._findCell(rowkey, columnName);
           } else if(rowkey) {
              this._markContainer = $(this.findRow(rowkey));
           } else {
              TableView.superclass.markControl.apply(this, arguments);
              this._markContainer = this._container;
              return;
           }
           TableView.superclass._createErrorMessage.apply(this, [message]);
           this._calcValidationErrorCount(message);
           this._markContainer.addClass('ws-TableView__redBorder-true');
           this._bindTooltips(this._markContainer, message);
        },
        _bindTooltips: function(container, message) {
           this._unbindTooltips();
           this._showExtendedTooltipBinded = this._showExtendedTooltip.bind(this, container, message);
           this._hideExtendedTooltipBinded = this._hideExtendedTooltip.bind(this, container);
           this._markContainer.bind('mouseenter', this._showExtendedTooltipBinded);
           this._markContainer.bind('mouseleave', this._hideExtendedTooltipBinded);
        },
        _unbindTooltips: function() {
           if(this._markContainer && this._markContainer !== this._container) {
              this._markContainer.unbind('mouseenter', this._showExtendedTooltipBinded);
              this._markContainer.unbind('mouseleave', this._hideExtendedTooltipBinded);
              this._showExtendedTooltipBinded = null;
              this._hideExtendedTooltipBinded = null;
           }
        },
        _showExtendedTooltip: function(container, message) {
           CInfobox.show(container, message);
        },
        _hideExtendedTooltip: function(container) {
           if(CInfobox.isCurrentTarget(container)) {
              CInfobox.hide();
           }
        },
        _getExtendedTooltipTarget: function() {
           return this._markContainer;
        },
  
        /**
         *
         * Снять отметку об ошибке.
         * Снимает с контрола маркировку ошибочного ввода.
         */
        clearMark : function(rowkey, columnName){
           this._unbindTooltips();
           var markContainer;
           if(rowkey && columnName) {
              markContainer = this._findCell(rowkey, columnName);
           } else if(rowkey) {
              markContainer = $(this.findRow(rowkey));
           } else {
              TableView.superclass.clearMark.apply(this, arguments);
              this._body.find('.ws-TableView__redBorder-true').removeClass('ws-TableView__redBorder-true');
              this._markContainer = undefined;
              return;
           }
           if(markContainer) {
              markContainer.removeClass('ws-TableView__redBorder-true');
           }
        },
        /**
         *
         * Очищает фильтр, связанный с указанным столбцом данных.
         * @param {String} columnName Название столбца
         * @example
         * <pre>
         *    tableView.resetFilterByColumnName("Фамилия");
         * </pre>
         */
        resetFilterByColumnName:function(columnName){
           var colDef = {}, id;
           if(columnName){
              for(var i = 0, len = this._columnMap.length; i < len; ++i){
                 if(this._columnMap[i].title === columnName){
                    colDef = this._columnMap[i];
                    id = i;
                    break;
                 }
              }
              if(!objIsEmpty(colDef) && colDef.filterDialog){
                 var filter = this.getQuery();
                 if(filter[colDef.filterName || colDef.title]) {
                    delete filter[colDef.filterName || colDef.title];
                 }
                 var clearFilter = this._notify('onResetColumnFilter', filter, colDef.filterName || colDef.title, colDef.title);
                 this.setQuery(clearFilter && Object.prototype.toString.call(clearFilter) == "[object Object]" ? clearFilter : filter);
                 this._clearMarkColumnFilter(id, colDef.title);
              }
           }
        },
        _clearMarkColumnFilter: function(id, title){
           var filterLink = this._head.find('#'+id+'.ws-browser-head-link');
           $(filterLink).next('.ws-browser-filter').css('display', 'none');
           if(filterLink.html() !== title)
              filterLink.text(title);
        },
        /**
         * Меняет название колонки и добавляет иконку сброса
         * @param {Object} filter фильтр
         * @param {String} id номер колонки
         */
        _markFilteredColumn: function(filter, id){
           var renderResult,
              colDef = {},
              filterName,
              container;
           if(filter && id){
              renderResult = false;
              colDef = this._columnMap[id];
              filterName = colDef.filterName || colDef.title;
              container = this._head.find('#'+id+'.ws-browser-head-link');
              if(container.length > 0){
                 // TODO проверить случай, когда функция что-то вернула а в фильтре - ничего
                 if(colDef.visualFilterFunction)
                    renderResult = colDef.visualFilterFunction.apply(this, [filter]);
                 if(!renderResult && colDef.title)
                    container.empty().append(colDef.title);
                 if(filter[filterName]){
                    if(renderResult instanceof Object && 'jquery' in renderResult)
                       container.empty().append(renderResult);
                    else if(typeof(renderResult) === 'string')
                       container.html(escapeHtml(renderResult));
                    else
                       container.text(filter[filterName]);
                    container.next('.ws-browser-filter').show();
                 }
              }
           }
        },
        _bindHeaderEvents: function(){
           var self = this;
  
           if(self._head.length){
              this._head.on('click', '.ws-browser-filter-trigger', function(event){
                 var colDef,
                    id,
                    $target = $(this);
                 id = this.id ? this.id : $target.prev('.ws-browser-head-link').attr('id');
                 colDef = self._columnMap[id];
                 if($target.attr('hasFilter') === 'true')
                    self.createFiltersDialog.apply(self, [colDef.filterDialog, id]);
                 else
                    self.resetFilterByColumnName.apply(self, [colDef.title]);
                 event.preventDefault();
                 event.stopPropagation();
                 return false;
              });
  
              this._head.on('click', '.ws-browser-head-hover', { self: this }, this._headCellClick);
              // навешивать клик по чекбоксу в шапке имеет смысл только при наличии шапки
              self._selectAllCheckbox = self._head.find('.ws-browser-checkbox-holder');
              self._selectAllCheckboxRow = self._selectAllCheckbox.closest('.ws-browser-head-row');
              self._selectAllCheckbox.on('click', function(event){
                 if (!self._selectAllCheckbox.hasClass('ws-disabled')) {
                    var isSelected = self._selectAllCheckboxRow.hasClass('ws-browser-selected');
                    if(!isSelected){
                       self.selectCurrentPage();
                    }else{
                       self.removeSelection();
                    }
                    self.setCheckboxState(!isSelected);
                    event.preventDefault();
                    event.stopPropagation();
                 }
                 return false;
              });
           }
        },
        _unbindHeaderEvents: function(){
           if (this._head.length){
              $('.ws-browser-filter-trigger', this._head.get(0)).off('click');
              $('.ws-browser-head-hover', this._head.get(0)).off('click');
              this._selectAllCheckbox.off('click');
           }
        },
        _useShowingFooter: function () {
           return this._options.display.showRecordsCount || this._options.display.showPaging || this._needShowSelectionCheckbox();
        },
        _getHeadHtml: function ( ) {
           var headHtml, res;
           if (this._options.display.showHead) {
              res = this._renderHead();
              headHtml = [
                 '<div class="ws-browser-head-container">',
                 '<div class="ws-browser-head-scroller">' ,
                 res.html,
                 '</div>',
                 '</div>'].join('');
  
           } else {
              headHtml = '';
           }
           return headHtml;
        },
        /**
         * Создает основную структуру html браузера
         */
        _createContainer: function(){
           var containerStyles = this._getBrowserDataContainerStyles(),
              colgroup = this._renderColgroups(),
              body = this._createBodyContainer(this._options.display.useDrawingLines ? 'ws-browser-border' : '', this._options.display.showHead);
           // tabindex равный -1 поставлен в данном блоке для фикса бага - FF3.6 Показывает лишний аутлайн + лишняя позиция табуляции при переходе с первого датавью-
           this._container.prepend(this._getResizerCode());
           var footer = this._container.find('.ws-browser-footer');
           if(footer.length){
              footer.get(0).innerHTML += this._createFooterArea();
           }
  
           if(body){
              this._container.find('.ws-browser-container .ws-browser').replaceWith($(body));
           }
           TableView.superclass._createContainer.apply(this, arguments);
  
           this._browserContainer.find('colgroup').append(colgroup);
           if(containerStyles) {
              this._rootElement.css(containerStyles);
           }
           if (this._options.display.showHead) {
              this._head.append(this._prepareHeadTemplate(this._prepareHeadStruct()));
              this._headContainer.find('colgroup').append(colgroup);
           }
           if(typeof(this._options.display.headRender) === 'function') {
              this._options.display.headRender.apply(this, [this._head]);
           }
           this._initHeadVariables();
           this._bindHeaderEvents();
        },
        _createBodyContainer: function(borderClass, showHead){
           return false;
        },
        _getBrowserDataContainerStyles: function() {
           //В браузере с относительным позиционированием (в новых сетках)
           //при растягивании блок данных должен быть абсолютным,
           //чтоб не распирать родительские блоки и сжиматься при уменьшении размеров контейнера
           if (this._isRelativePosition() && this._horizontalAlignment === 'Stretch') {
              return {
                 position: 'absolute',
                 top: 0,
                 left: 0,
                 right: 0
              };
           }
        },
  
        _getResizerCode: function() {
           //В браузере с относительным позиционированием (в новых сетках)
           //при гориз. растягивании и автовысоте нужен ресайзер,
           //поскольку блок данных с абсолютной вёрсткой (см. _getBrowserDataContainerStyles) не влияет на высоту контейнера.
           var result = '';
           if (this._isRelativePosition() && this._horizontalAlignment === 'Stretch' &&
              this._isHeightGrowable())
           {
              result = '<div class="ws-browser-resizer" style="position: relative"></div>';
           }
           return result;
        },
  
        _updateDataBlockSize: function(isExternalChange){
           if (!(isExternalChange && this._horizontalAlignment !== 'Stretch')) {
              this._setWidth(this._userColumnWidth);
           }
           this._updateSizeVariables();
        },
        /**
         * Обработчик ресайза окна
         */
        _onResizeHandler: function(){
           TableView.superclass._onResizeHandler.apply(this, arguments);
           if(this.getActiveRow() !== false) {
              this._setSelection(this.getActiveRow(), true);
           }
        },
        _updateSelection: function(){
           //В случае, если useHoverRowAsActive = true, _updateSelection пытается сфокусироваться на первой строке,
           //что в свою очередь сбивает скрол. Поэтому когда релоад был вызван редактированием по месту, не скролимся никуда.
           if (!this._options.useHoverRowAsActive || !this._allowEditAtThePlace) {
              TableView.superclass._updateSelection.apply(this, arguments);
           }
           if (this._options.useHoverRowAsActive && this.getActiveRow() !== false) {
              this._useKeyboard = false;
           }
        },
        /**
         * Дейтсвия перед отрисовкой браузера. Если вернём false, то отрисовка остановится
         * @returns {Boolean}
         */
        _onBeforeRenderActions: function(){
           var notifyResult;
           try {
              this._inBeforeRenderActionCnt++;
              notifyResult = this._notify('onBeforeRender', this.getColumns());
           } finally {
              this._inBeforeRenderActionCnt--;
           }
  
           if(notifyResult === false){
              this._hideLoadingIndicator();
              return false;
           }
           else if(notifyResult instanceof Object){
              this._options.display.columns = notifyResult;
              this._columnMap = [];
              this._refreshHead();
           }
           if(!this._currentRecordSet.getRecordCount() && this._hasRowOptions){
              //аккуратно скроем опции записи и больше ничего не будем трогать
              this._hideRowOptions();
           }
           return true;
        },
        _initActionsFlags: function(){
           TableView.superclass._initActionsFlags.apply(this, arguments);
           this._actions['clearSorting'] = this._options.display.useSorting && $.proxy(this.clearSorting, this);
        },
        /**
         *
         * Находит строчку с указанным ключом
         * @param {String} rowkey Первичный ключ записи
         */
        findRow: function(rowkey){
           var row =  this._rowsMap[rowkey] ? this._rowsMap[rowkey] : this._body.find('.ws-browser-table-row[rowkey="' + rowkey + '"]');
           if(!row || !row.length)
              Env.IoC.resolve('ILogger').error("Browser", "Cannot find row with rowkey " + rowkey);
           return row;
        },
        /**
         *
         * Признак наличия указанной строки в табличном представлении.
         * @param {String} rowkey Идентификатор записи.
         * @return {Boolean} Возможные значения:
         * <ol>
         *    <li>true - указанная строка в табличном представлении есть;</li>
         *    <li>false - отсутствует.</li>
         * </ol>
         */
        haveRow: function(rowkey){
           return !!this._rowsMap[rowkey];
        },
        /**
         * Возвращает индекс колонки, которая имеет определённое название
         * @param {String} name Название колонки
         * @param {String} searchProp Поле, по которому ищем название колонки (field или title)
         * @return {Number}
         * @protected
         */
        _columnIndex: function(name, searchProp){
           searchProp = searchProp || 'title';
           for(var i in this._columnMap){
              if(this._columnMap.hasOwnProperty(i) && this._columnMap[i][searchProp] === name)
                 return i;
           }
           return -1;
        },
        /**
         *
         * Находит ячейку с указанным ключом записи и названием колонки
         * @param {String} rowkey Первичный ключ записи
         * @param {String} columnName Название колонки
         * @return {jQuery}
         */
        findCell: function(rowkey, columnName){
           return this._findCell(rowkey, columnName, 'field');
        },
        /**
         * Находит ячейку с указанным ключом записи и названием колонки
         * @param {String} rowkey Первичный ключ записи
         * @param {String} columnName Название колонки
         * @param {String} searchProp Поле, по которому ищем название колоник (field или title)
         * @return {jQuery}
         */
        _findCell: function(rowkey, columnName, searchProp){
           var cellIndex = this._columnIndex(columnName, searchProp);
           if(cellIndex >= 0){
              if (this._needShowSelectionCheckbox()) {
                 cellIndex++;
              }
              return this.findRow(rowkey).children('.ws-browser-cell:eq(' + cellIndex + ')');
           }
           Env.IoC.resolve('ILogger').error("Browser", "Cannot find column with name " + columnName);
           return $();
        },
        /**
         * Обработчик нажатия на заголовок браузера
         * @param {jQuery} event
         */
        _headCellClick: function(event){
           var $target = $(this),
              columnId = $target.attr('columnId'),
              self = event.data.self,
              field = self._columnMap[columnId].field,
              sortType = 0, //0 - по возрастанию, 1 - по убыванию, 2 - нету
              found = false,   //Найдена ли колонка в массиве
              sortable = $target.find('.ws-browser-sortable'),
              classNames = ['asc', 'desc', 'none'];
  
           for(var i = self._sortingStack.length - 1; i >= 0; --i){
              if(!self._sortingStack[i]){
                 continue;
              }
              if(self._sortingStack[i]['field'] == field){
                 found = true;
                 sortType = ++self._sortingStack[i]['type'];
                 if(sortType == 2)
                    delete self._sortingStack[i];
                 break;
              }
           }
           if(!found)
              self._sortingStack.push({'field': field, 'type': 0});
  
           sortable.removeClass(classNames[(sortType + 2) % 3]);
           sortable.addClass(classNames[sortType]);
  
           if(self._currentRecordSet && self._currentRecordSet instanceof dTransportRecordSet){
              var sorting = [];
              for(var c = 0, len = self._sortingStack.length; c < len; ++c){
                 if(!self._sortingStack[c])
                    continue;
                 sorting.push([self._sortingStack[c]['field'], self._sortingStack[c]['type'] > 0]);
              }
              self._currentRecordSet.setPage(0, true);
              self._currentRecordSet.setSorting(sorting, self.getQuery(), self.isHierarchyMode());
           }
        },
        /**
         * Устанавливает нужную ширину таблиц,
         * необходимо переопределить в плагинах
         * @param {String} tablesWidth ширина таблиц
         * @private
         */
        _setTablesWidth: function (tablesWidth) {
        },
        /**
         * Изменение ширин столбцов
         *
         * @param {Array} [newWidths] массив новых ширин столбцов
         * если newW[i] = 'byValue', то столбец выравнивается по содержимому
         */
        _setWidth: function(newWidths){
           var self = this,
              maxIndx = -1,
              maxVal = 0,
              sum = 0,
              rootWidth = 0,
              nullCols = [],
              cols = [],
              nonPercentCols = [],
              nonFixedCols = [],
              tablesWidth,
              haveSelectionCheckbox = this._needShowSelectionCheckbox(),
              selectionCheckboxWidthAdd = haveSelectionCheckbox ? Env.constants.Browser.selectionCheckboxWidth : 0,
              userColW = (this._userColumnWidth && this._userColumnWidth.length > 0) ? this._userColumnWidth : undefined,
              newW = (newWidths && newWidths.length > 0) ?  newWidths : undefined;
  
           if (newW !== undefined) {
              this._userColumnWidth = newW.slice();
           }
           else if (userColW) {
              newW = userColW;
           }
  
           if(newW){
              newW.forEach(function(width, i) {
                 if (newW[i] == "byValue") {
                    newW[i] = this._columnMinWidthArray[i];
                 }
              }, this);
           }
  
           if (this._columnMap.length === 0)
              return;
  
           function getMinWidth(colIdx) {
              var type = self._columnMap[colIdx].type,
                 minWidth = self._columnMap[colIdx].minWidth,
                 defColWidth = Env.constants.Browser.defColWidth,
                 defMinWidth = type in defColWidth ? defColWidth[type] : defColWidth['another'];
  
              return parseInt(minWidth !== null ? minWidth : defMinWidth, 10);
           }
  
           if(!this._haveAutoWidth()){
              //Вычитаем 1 для лечения глюка IE, который иногда думает, что ему не хватает места в
              // родительском блоке _rootElement, и добавляет прокрутку
              rootWidth = Math.max(0, self._rootElement.width() - (this._getVerticalScrollShowed() ? this._scrollWidth : 0) -
              (Env.constants.browser.isIE ? 1 : 0) - selectionCheckboxWidthAdd);
  
              tablesWidth = Math.max(rootWidth + selectionCheckboxWidthAdd, this._options.minWidth) + 'px';
  
              if (this._head) {
                 this._head.closest('table').width(tablesWidth);
              }
  
              this._data.width(tablesWidth);
  
              this._setTablesWidth(tablesWidth);
  
              if(newW){
                 newW.forEach(function(width, idx) {
                    this._headColumns.eq(idx).attr('width', width);
                    this._bodyColumns.eq(idx).attr('width', width);
                 }, this);
              }
              else{
                 newW = [];
                 if(this._bodyColumns){ // TODO: проверка на существование колонок шапки, если их нет, то событие onResize сработало раньше, чем построился браузер
                 // Считаем ширины колонок шапки, переводим % в px
                    this._bodyColumns.each(function(i){
                       var existsInMap = self._columnMap && self._columnMap[i],
                          w = (existsInMap && self._columnMap[i].width) || 0,
                          isFixed = existsInMap ? self._columnMap[i].fixedSize : true;
  
                       cols.push($(this));
                       // IE8 и WebKit не отдают нам вычисленную ширину колонки. Считаем сами.
                       if(typeof(w) != 'number'){
                          if(w.indexOf('%') > 0)
                             w = Math.floor((rootWidth * parseInt(w, 10)) / 100);
                          else if(!existsInMap || !isFixed){
                             nonPercentCols.push(i);
                          }
                       }
                       else if(existsInMap && !isFixed){
                          nonPercentCols.push(i);
                       }
                       if(existsInMap && !isFixed){
                          nonFixedCols.push(i);
                       }
  
                       w = parseInt(w, 10);
                       if (!w){
                          if(existsInMap && isFixed){
                             throw new Error('Column with fixed size option must have width');
                          }
                          nullCols.push(i);
                       }
  
                       //Ширина не должна быть меньше минимальной
                       w = Math.max(w, isFixed ? Env.constants.Browser.minColumnWidth : getMinWidth(i));
                       newW[i] = w;
                       sum += w;
  
                       // Ищем самую жирную колонку. За ее счет потом будем исправлять ошибку вычилений/округлений
                       if(w > maxVal) {
                          maxVal = w;
                          maxIndx = i;
                       }
                    });
                 }
  
                 if(sum > rootWidth && nonFixedCols.length){
                    var diff = sum - rootWidth,
                       freeSpace = 0;
  
                    var enumerateNonFixed = function(callback) {
                       nonFixedCols.forEach(function(nonFixedCol) {
                          if (nonFixedCol in newW) {
                             var minWidth = getMinWidth(nonFixedCol);
                             callback(nonFixedCol, minWidth, minWidth - newW[nonFixedCol]);
                          }
                       });
                    };
  
                    enumerateNonFixed(function(nonFixedCol, minWidth, minWidthDiff) {
                       if(minWidthDiff >= 0)
                          diff += minWidthDiff;
                       else
                          freeSpace += (-minWidthDiff);
                    });
  
                    enumerateNonFixed(function(nonFixedCol, minWidth, minWidthDiff) {
                       var oldWidth = newW[nonFixedCol],
                          newWidth = (minWidthDiff >= 0) ? minWidth :
                             Math.max(oldWidth + diff * (minWidthDiff / freeSpace), minWidth); //Нельзя уменьшать ниже minWidth
  
                       sum += (newWidth - oldWidth);//корректируем общую сумму
                       newW[nonFixedCol] = newWidth;
                    });
                 }
  
                 //раздача лишнего места
                 var giveBonus = function(cols) {
                    var bonusW = cols.length > 0 ? Math.floor((rootWidth - sum) / cols.length) : 0;
                    if (bonusW > 0) {
                       cols.forEach(function(col) { newW[col] += bonusW; });
                       sum += bonusW * cols.length;
                    }
                 };
                 giveBonus(nullCols.length ? nullCols : nonPercentCols);
  
                 // Подправим ошибку вычислений за счет самой жирной колонки
                 if(rootWidth > sum) {
                    newW[maxIndx] += rootWidth - sum;
                 }
  
                 // Сделаем ресайз всех колонок. И в шапке и в теле таблицы
                 if(this._bodyColumns){
                    cols.forEach(function(col, idx) {
                       col.attr('width', newW[idx]);
                       this._headColumns.eq(idx).attr('width', newW[idx]);
                    }, this);
                 }
  
                 if(this._options.display.allowHorizontalScroll && this._emptyDataScroller) {
                    this._emptyDataScroller.width(sum);
                 }
              }
           } else {
              // autoSize
              this._data.removeClass('ws-table-fixed').width('auto').css({'table-layout': 'auto'});
              this._head.parent().removeClass('ws-table-fixed').width('1px').css({'table-layout': 'auto'});
              this._browserContainer.width('auto');
              if(!newW){
                 newW = this._columnMap.map(function(col) { return col.width; });
              }
              var td_data = this._data.find('.ws-browser-table-row:eq(0) .ws-browser-cell'),
                 td_header = this._head.find('.ws-browser-head-row:eq(0) .ws-browser-header-cell'),
                 dfContainer = this._footer;
  
              newW.forEach(function(_, i) {
                 self._bodyColumns.eq(i).attr('width', '');
                 self._headColumns.eq(i).attr('width', '');
              }, this);
  
              var totalWidth = 0, isEmptyTable = this._currentRecordSet && this._currentRecordSet.isEmptyTable();
              // фиксированным колонки оставляем установленный им размер, остальным ставим авто-размер.
              newW.forEach(function(_, i) {
                 var value, isFixed = this._columnMap[i] && this._columnMap[i].fixedSize;
                 if (isFixed || isEmptyTable) {
                    value = isFixed ? newW[i] : (newW[i] || getMinWidth(i));
                 }
                 else{
                    value = Math.max(td_data.eq(i).outerWidth(), td_header.eq(i).outerWidth(), getMinWidth(i));
                    if (this._columnMap[i]) {
                       this._columnMap[i].width = value;
                    }
                 }
                 totalWidth += value;
              }, this);
  
              // установка "авто-ширин"
              this._columnMap.forEach(function(col, i) {
                 this._headColumns.eq(i).attr('width', col.width);
                 this._bodyColumns.eq(i).attr('width', col.width);
              }, this);
  
              var realMinWidth = this._options.minWidth,
                 flagMinSize = false;
              if(this._getVerticalScrollShowed()){
                 realMinWidth -= this._scrollWidth;
              }
              if(totalWidth < realMinWidth){
                 flagMinSize = true;
                 totalWidth = realMinWidth;
              }
              var headWidth = totalWidth;
              if(this._getVerticalScrollShowed()){
                 headWidth += this._scrollWidth;
              }
              this._data.width(totalWidth);
              this._browserContainer.width(headWidth);
              this._head.parent().width(headWidth);
              dfContainer.width(headWidth);
              this._container.width('auto');
              this._rootElement.width(headWidth);
              if(flagMinSize){
                 // переустановка ширин столбцов
                 this._columnMap.forEach(function(col, i) {
                    if(!col.fixedSize){
                       this._bodyColumns.eq(i).attr('width', '');
                    }
                 }, this);
  
                 this._columnMap.forEach(function(col, i) {
                    var width = td_data.eq(i).outerWidth();
                    if(!col.fixedSize){
                       col.width = width;
                    }
                    this._bodyColumns.eq(i).attr('width', width);
                    this._headColumns.eq(i).attr('width', width);
                 }, this);
  
                 this._head.parent().width(headWidth);
              }
              this._data.addClass('ws-table-fixed').css({'table-layout': 'fixed'});
              this._head.parent().addClass('ws-table-fixed').css({'table-layout': 'fixed'});
           }
        },
        _getBodyContainer: function() {
           return $('<tbody class="ws-browser-body"/>');
        },
        _drawBodyCycle: function(){
           var trs, tableBody;
  
           this._count = 0;
           this._rowsMap = {}; //Забываем ссылки на старые строки
           tableBody = this._createRecordsTemplate(this._currentRecordSet.getRecords());
  
           /*
            http://stackoverflow.com/questions/5805956/internet-explorer-9-not-rendering-table-cells-properly
            баг ие9 и jQuery, если вставлять много чистого html текста
            */
           if (Env.constants.browser.isIE9) {
              tableBody = this._removeWhiteSpacesForTableElems(tableBody);
           }
           // var nowCount = this._body.children().length;
           this._body.remove();
           this._body = this._getBodyContainer();
           this._body.html(tableBody);
           //сформируем объект со ссылками на строки
           trs = this._body.find('> .ws-browser-table-row');
           for(var i = 0, len = trs.length; i < len; i++) {
              this._rowsMap[trs[i].getAttribute('rowkey')] = trs.eq(i);
           }
           this._applyRowRender(this._currentRecordSet, this._body);
           if (this._options.display.allowHorizontalScroll)
              this._emptyDataScroller.toggleClass('ws-hidden', !!this._count);
  
           this._data.append(this._body);
           return tableBody;
        },
        _createRecordsTemplate: function(records){
           var record,
              odd,
              key,
              colDef = {},
              classFromType,
              data = '',
              title = '',
              visibleId = 0,
              dataObject = {
                 columns : [],
                 data: [],
                 keys: [],
                 classes: [],
                 title: [],
                 checkbox: this._needShowSelectionCheckbox()
              },
              /* Определяем, указан ли шаблон для строки */
              rowTemplate = this._options.display.rowTemplate ?
                 typeof(this._options.display.rowTemplate) === 'function' ?
                    this._options.display.rowTemplate :
                    Utils.RequireHelper.defined(this._options.display.rowTemplate) ?
                       requirejs(this._options.display.rowTemplate) :
                       undefined :
                 undefined,
              useWidthByValue = false, //В одной из колонок используется ширина по содержимому
              i, l = this._columnMap.length;
           for(i = 0; i < l; i++){
              colDef = this._columnMap[i];
              classFromType = "";
              if(colDef.textAlign === 'auto' && Env.constants.Browser.type2ClassMap[colDef.type]) {
                 classFromType = " ws-browser-type-" + Env.constants.Browser.type2ClassMap[colDef.type];
              }
              dataObject.columns.push({
                 textAlign: colDef.textAlign,
                 fieldName: colDef.field,
                 textVerticalAlign: colDef.textVerticalAlign,
                 type: colDef.type,
                 className: colDef.className + classFromType,
                 render: colDef.render ? true : false,
                 highlight: colDef.highlight ? true : false,
                 cellTemplate: !rowTemplate && colDef.cellTemplate && Utils.RequireHelper.defined(colDef.cellTemplate) ?
                    requirejs(colDef.cellTemplate) : false
              });
           }
  
           var minColumnWidth = {};
           this._columnMinWidthArray = null;
  
           for (var iRec = 0, len = records.length; iRec < len; iRec++) {
              record = records[iRec];
              odd = this._options.display.hasZebra ? (visibleId % 2) : true;
              key = record.getKey();
              if(typeof key == 'string') {
                 key = key.replace(/'/g, "&#039;");
              }
              dataObject.keys.push(key);
              dataObject.classes[visibleId] = '' + (odd ? '' : ' rE');
              if(this._selected[key] !== undefined){
                 dataObject.classes[visibleId] += " ws-browser-selected";
              }
              dataObject.data[visibleId] = [];
              for(i = 0; i < l; i++){
                 colDef = this._columnMap[i];
                 data = this._renderTD(colDef, record);
  
                 if (colDef.width === 'byValue') {
                    useWidthByValue = true;
                    var dataWidth = getTextWidth(data);
                    if (minColumnWidth[colDef.title] !== undefined){
                       if (minColumnWidth[colDef.title] < dataWidth) {
                          minColumnWidth[colDef.title] = dataWidth;
                       }
                    }
                    else {
                       minColumnWidth[colDef.title] = dataWidth;
                    }
                 }
  
                 if(this._options.display.cutLongRows){
                    dataObject.title[visibleId] = dataObject.title[visibleId] || [];
                    title = data;
                    if(typeof title == 'string') {
                       title = data.replace(new RegExp("<(?=\/*(?:))[^>]+>", "g"), " ")
                          .replace( /\s\s+/g, ' ' )
                          .replace(/'/g, "&#039;").trim();
                    }
                    dataObject.title[visibleId].push(title);
                 }
                 dataObject.data[visibleId] = dataObject.data[visibleId] || [];
                 dataObject.data[visibleId].push(data);
              }
              visibleId++;
           }
           if(!objIsEmpty(this._options.display.rowTemplateOptions) && this._options.display.rowTemplate) {
              cMerge( dataObject, this._options.display.rowTemplateOptions);
           }
           this._count += visibleId;
           if (useWidthByValue) {
              this._columnMinWidthArray = $.map(minColumnWidth, function(value,key) {
                 var keyWidth = getTextWidth(key);
                 value += Env.constants.TableView.tableRowPadding * 2;
                 if (value < keyWidth){
                    value = keyWidth;
                 }
                 return [value];
              });
           }
           return rowTemplate ? rowTemplate(dataObject, this) : dotTplFnForRow(dataObject, this);
        },
        /**
         * Убирает пробелы между всеми табличными элементами(table, caption, colgroup, col, tbody, thead, tfoot, tr, td, th)
         * @param {String} html
         * @returns {String}
         */
        _removeWhiteSpacesForTableElems: function(html) {
           return html.replace(/>\s+(?=<\/?(t|c)[hardfob])/gm,'>');
        },
        _applyRowRender: function(recordSet, container){
           //поддерживаем пользовательские функции рендеринга
           var rowRender = typeof(this._options.display.rowRender) == 'function' ? this._options.display.rowRender : false,
              record;
           if(rowRender){
              var collection = container.find('[rowkey]'),
                 tr;
              for(var j = 0, cnt = collection.length; j < cnt; j++){
                 tr = $(collection[j]);
                 record = recordSet.getRecordByPrimaryKey(tr.attr('rowkey'));
                 if(rowRender)
                    rowRender.apply(this, [record, tr]);
              }
           }
        },
        /**
          *
          * В основе метода лежит стандартный механизм делегирования событий JavaScript.
          * Метод позволяет прикрепить функцию обработчик к одному или нескольким событиям для всех элементов,
          * соответствующих определённому селектору, указанному в первом параметре метода.
          * Метод реализуется как для существующих, так и для будущих элементов.
          * @param selector Селектор для поиска элементов, на которые будет установлен заданный обработчик событий.
          * @param event Обрабатываемое событие.
          * @param handler Функция обработчик.
          * @param [data] Данные, передаваемые обработчику событий.
          * @example
          * <pre>
          *     //в одной из колонок браузера навешан css класс ('.show-full-log-on-click')
          *     var browser = this.getChildControlByName("СписокЛогов");
          *     //селектором является css класс, обрабатываем клик
          *     browser.delegateUserEvent('.show-full-log-on-click', 'click', function() {
          *        //функция обработчик
          *        var self = $(this);
          *        helpers.showFloatArea({
          *           target: self.getContainer(),
          *           animation: 'fade',
          *           template: 'Шаблон просмотр полного сообщения лога',
          *           handlers: {
          *              onAfterShow: function() {
          *                 //у элемента с этим селектором есть атрибут ('full-val') с логом
          *                 this.getChildControlByName("ПолныйТекст").setContent(self.attr('full-val'));
          *              }
          *           }
          *        });
          *     });
          * </pre>
          */
        delegateUserEvent: function(selector, event, handler, data) {
           this._useDelegateUserEvent(true,selector, event, handler, data);
        },
        /**
         *
         * Удаление обработчика событий
         * @param selector
         * @param event
         * @param handler
         */
        undelegateUserEvent: function(selector, event, handler) {
           this._useDelegateUserEvent(false,selector, event, handler);
        },
        /**
         * Установка\удаление обработчика событий
         * @param {Boolean} setEvent - определяет устанавливаем обработчик события или удаляем его
         * @param selector
         * @param event
         * @param handler
         * @param data
         */
        _useDelegateUserEvent: function (setEvent,selector, event, handler, data){
           if(typeof selector !== 'string' || selector === '')
              throw new TypeError("delegateUserEvent: Selector must be a non-empty string");
           if(typeof handler != 'function')
              throw new TypeError("delegateUserEvent: Handler must be a function");
           if(typeof event != 'string' || event === '')
              throw new TypeError("delegateUserEvent: Event name must be a non-empty string");
  
           var self = this,
              collection = this._body.parent();
           if (setEvent) {
  
              this._delegatedEventsCount[selector] = 1;
  
              if (Object.keys(this._delegatedEventsCount).length > MAX_DELEGATED_SELECTORS) {
                 Env.IoC.resolve('ILogger').error(
                    'TableView ' + this.getName(),
                    'Потенциальная проблема производительности! ' +
                    'На данном табличном браузере делегирована обработка слишком большого числа различных селекторов!');
              }
  
              if (event === 'click') {
                 handler._rowSelect = function (e) {
                    // перед пользовательским кликом нужно вызвать обработчик выбора зиписи в таблице
                    self._rowSelect.apply(this, arguments);
                    // чтобы обработчик не вызывался 2-й раз в собственном delegate
                    e._rowSelect = true;
                    return handler.apply(this, arguments);
                 };
              }
              collection.delegate(selector, event, data, handler._rowSelect ? handler._rowSelect : handler);
           }
           else {
              collection.undelegate(selector, event, handler._rowSelect ? handler._rowSelect : handler);
           }
        },
        /**
         * Применяет все рендеры к данным выводимым в столбце
         * @param {Object} colDef определение типа колонки
         * @param {Deprecated/Record} record запись по которой отрисовывается строка
         * @return {String|jQuery} отрендеренные данные
         */
        _renderTD: function(colDef, record){
           var data = record.hasColumn(colDef.field) ? record.get(colDef.field) : "";
           return this._prepareColumnData(colDef, data, record);
        },
  
        _jQueryToString: function (jq) {
           var res = [];
           for (var i = 0, l = jq.length; i < l; i++) {
              res.push(jq.get(i).outerHTML);
           }
           return res.join('');
        },
  
        _prepareColumnData: function(colDef, fieldValue, record){
           var data = null,
              dataLength = 0;
           if(typeof(colDef.render) === 'function'){
              data = colDef.render.apply(this, [ record, colDef.field, colDef ]);
              if(data instanceof Object && "jquery" in data){
                 data = this._jQueryToString(data);
              }
              //Да, сначала из jQuery в String, а потом все равно вырежем script
              if (typeof(data) === 'string') {
                 while(dataLength !== data.length) {
                    dataLength = data.length;
                    data = escapeTagsFromStr(data, ['script']);
                 }
              }
              if(data === null){ // пустое значение должно выводиться пустым, а не "null"
                 data = '';
              }
           }
           //Рисуем значение в столбце по-умолчанию если не задана функция отрисовки, либо задана, но из нее вернули undefined
           if(!colDef.render || data === undefined) {
              if((data = fieldValue)!== null){
                 if(typeof(data) === 'string' && !colDef.render) {
                    data = escapeHtml(data);
                 }
                 if (colDef.formatValue) {
                    data = stringFormat(record, colDef.formatValue);  //Альтернативный формат времени
                 }
                 else{
                    switch (colDef.type){
                       case 'Перечисляемое':
                          data = defaultRenders.enumType(data);
                          break;
                       case "Деньги":
                          data = defaultRenders.money(data);
                          break;
                       case "timestamp":
                       case "timestamptz":
                       case "interval":
                       case "date":
                       case "Дата":
                       case "Дата и время":
                       case "Время":
                          data = defaultRenders.timestamp(data, colDef.type); // формат времени
                          break;
                       case "oid":
                       case "int2":
                       case "int4":
                       case "int8":
                       case "Число целое":
                          data = defaultRenders.integer(data); // формат числа
                          break;
                       case "boolean":
                       case "bool":
                       case "Логическое":
                          data = defaultRenders.logic(data, true);  //формат чекбокса возвратит span для true
                          break;
                       case "Флаги":
                          data = defaultRenders.flags(data);
                          break;
                       case "":
                          break;
                       default:
                          break;
                    }
                 }
                 if(colDef.highlight){
                    data = this._highlightData(data);
                 }
              } else {
                 data = "";//Чтобы в title не проставился undefined
              }
           }
           return data;
        },
        _prepareHeadStruct: function() {
           var max = 0, // количество столбцов
              head = [],
              i, j = -1,
              cs, rs,
              columnsCount = 0,
              csIndex = 0;
           for (i in this._columnMap){
              if(this._columnMap.hasOwnProperty(i)){
                 head[++j] = [];
                 columnsCount++;
                 var curr = this._columnMap[i].title,
                    count = 0;
                 do{//Заполняем массив заголовков. Записываем, учитывая иерархию все имена столбцов
                    /^([^\.]*)(\.?(.*)$|$)/.exec(curr);
                    if(RegExp.$3){
                       if(RegExp.$3.substr(0,1) === " " && (RegExp.$1.charAt(RegExp.$1.length - 1) !== '\\')){
                          var columnName = RegExp.$1 + ". ";
                          /^([^\.]*)(\.?(.*)$|$)/.exec(RegExp.$3);
                          columnName += RegExp.$1;
                          head[j].push({ title: columnName });
                          curr = RegExp.$3;
                       } else {
                          head[j].push({ title: RegExp.$1 });
                          curr = RegExp.$3;
                       }
                    }
                    else{
                       head[j].push({ title: RegExp.$1 + RegExp.$2 });
                       curr=null;
                    }
                 } while (curr);
                 /*Возвращаем экранированные точки по принципу замены последнего '\\' в кусочке head[j]*/
                 count = 0;
                 while (count <= head[j].length - 1){
                    var currTitle = head[j][count].title;
                    if (currTitle !== ''){
                       head[j][count].title = head[j][count].title.replace(/\\\\\\./g,'\\.');//Ставим \.
                       if (head[j][count].title === '\\\\.'){
                          head[j][count].title = '\.';//ставим сразу .
                       }
                       else{
                          if (currTitle.charAt(currTitle.length - 1) === '\\' && //Соединяем так как это не разделитель . а просто .
                                currTitle.charAt(currTitle.length - 2) === '\\'){
                             head[j][count].title = head[j][count].title.replace(/\\\\$/,'.');
                             head[j][count].title += head[j][count + 1].title;
                             head[j].splice(count + 1, 1);
                             count--;
                          }
                          else if (count === head[j].length -1 )
                             head[j][count].title = head[j][count].title.replace('\\\\.','\.');
                       }
                    }
                    count++;
                 }
                 //Внимание! нет проверки на последний \\ в массиве head[j]
                 max = head[j].length > max ? head[j].length : max; //Считаем макс. кол-во колспанов/ровспанов или типа того
              }
           }
  
           for (var n = 0; n < max; n++){
              j = -1;
              for (i in this._columnMap){
                 if(this._columnMap.hasOwnProperty(i)){
                    var colDef = this._columnMap[i];
                    ++j;
                    var currentHead = head[j][n],
                       prevTitle = j !== 0 && head[j - 1] && head[j - 1][n] ? head[j - 1][n].title : undefined;
                    if (currentHead !== undefined && (j === 0 || (currentHead.title != prevTitle) )){
                       var nextTitle = head[j][n].title;
                       for (cs = j; cs < columnsCount && (head[cs] !== undefined) && nextTitle == currentHead.title; cs++) {
                          nextTitle = head[cs+1] && head[cs+1][n] ? head[cs+1][n].title : undefined;
                       }
                       cs--;
                       for (rs = n + 1; rs < max && head[j][rs] === undefined; rs++) {/*empty*/}
                       rs--;
  
                       if (cs > j)
                          csIndex += cs - j + 1;
                       else
                          csIndex++;
  
                       var colName = "";
                       for(var k = 0; k <= n; k++){
                          colName += (k === 0 ? "" : "." ) + head[j][k].title;
                       }
  
                       currentHead.csIndex = csIndex;
                       currentHead.columnId = i;
                       currentHead.rs = rs;
                       currentHead.cs = cs;
                       currentHead.colspan = cs - j + 1;
                       currentHead.rowspan = rs - n + 1;
                       currentHead.colName = colName;
                       if(typeof(this._columnMap[i].captionRender) == 'function'){
                          currentHead.content = this._columnMap[i].captionRender.apply( this, [ colName ] );
                       } else if(typeof(this._options.display.headColumnRender) == 'function'){
                          currentHead.content = this._options.display.headColumnRender.apply( this, [ colName ] );
                       }
                       currentHead.content = currentHead.content || currentHead.title;
                       currentHead.filterVis = colDef.title;
                       currentHead.hasFilter = false;
                       if(this._initialFilter) {
                          var filterName = colDef.filterName || colDef.title;
                          if(this._initialFilter[filterName]) {
                             currentHead.hasFilter = true;
                             if(colDef.visualFilterFunction)
                                currentHead.filterVis = colDef.visualFilterFunction.call(this, this._initialFilter);
                             else
                                currentHead.filterVis = this._initialFilter[filterName];
                          }
                       }
                    }
                 }
              }
           }
           return [ head, max ];
        },
        _prepareHeadTemplate: function(headData) {
  
           if(!headData || headData.length != 2){
              return '';
           }
  
           var max = headData[1];
           var head = headData[0];
           var j;
           var result = [];
           var hasColSpan = false,
              showSelectionCheckbox = this._needShowSelectionCheckbox(),
              useDrawingLines = this._options.display.useDrawingLines;
  
           for (var n = 0; n < max; n++){
              result.push("<tr class='ws-browser-head-row ws-browser-head-", (n === 0 ? 'top' : 'bottom'), "'>");
              j = -1;
              if (showSelectionCheckbox) {
                 var useCheckbox = (n + 1 === max);
                 result.push(['<td class="',
                    (useCheckbox ? 'ws-browser-checkbox-holder' : 'ws-browser-checkbox-emptyCell'),
                    (useDrawingLines ? ' ws-browser-checkbox-border' : ''),
                    '"', (useCheckbox ? 'title="' + rk("Отметить всю страницу") + '"' : '') ,'>',
                    (useCheckbox ? '<span class="ws-browser-checkbox"></span>' : ''),
                    '</td>'].join(''));
              }
              for (var i in this._columnMap){
                 if(this._columnMap.hasOwnProperty(i)){
                    ++j;
                    var currentHead = head[j][n];
                    if (currentHead !== undefined && (j === 0 || currentHead.title != (head[j - 1][n] && head[j - 1][n].title))){
                       var
                          rs = currentHead.rs,
                          cs = currentHead.cs,
                          csIndex = currentHead.csIndex,
                          columnId = currentHead.columnId,
                          contentText = escapeHtml(currentHead.content);
                       if(currentHead.colspan > 1){
                          hasColSpan = true;
                       }
                       result.push([
                          "<td columnId='", columnId, "' csIndex='", csIndex, "' class='" , [
                             'ws-browser-header-cell',
                             ( useDrawingLines ? 'ws-header-cell-bottom-border' : ''),
                             ( j === 0 && useDrawingLines && !showSelectionCheckbox ? 'ws-header-cell-left-border' : ''),
                             ( this._columnMap[j] ? 'ws-browser-valign-' + this._columnMap[j].titleVerticalAlign  : '' ) ,
                             ( this._options.display.useSorting && rs + 1 == max && this._columnMap[i].isSortable ? 'ws-browser-head-hover' : '') ,
                             ( (cs > j && (currentHead.title !== this._columnMap[i].title)) ?
                                contentText.length !== 0 ?
                                   'ws-browser-auto ws-browser-head-border-' + (n ? 'top' : 'bottom') : ""
                                : 'ws-browser-' + this._columnMap[i].titleAlign),
                             ( cs == j && this._columnMap[i].type == "text" ? 'ws-browser-text' : ''),
                             ( this._options.display.useSeparating === true && j < (head.length - 1) ? 'ws-browser-separatable' : '' ),
                             ( n > 0 ? 'ws-browser-header-bottom-cell' : '' )
                          ].join(' ') , "' ",
                          ( cs > j ? " colspan='" + currentHead.colspan + "' " : ''),
                          ( rs > n ? " rowspan='" + currentHead.rowspan + "' " : ''), ">" ,
                          "<div>" ,
                          (this._options.display.useSorting && this._columnMap[i].isSortable === true ? "<span class='ws-browser-sortable none'></span>" : ''),
                          (currentHead.content.jquery ?
                             this._jQueryToString(currentHead.content) :
                             (!this._columnMap[j].filterDialog ?
                             // Не указан диалог фильтра
                                contentText.replace(/\n/mg, '<br/>') :
                             // Указан диалог фильтра
                                [
                                   "<a href='javascript:void(0)' class='ws-browser-head-link ws-browser-filter-trigger' hasFilter='true' id='", i, "'>",
                                   (currentHead.filterVis.jquery ?
                                      this._jQueryToString(currentHead.filterVis) :
                                      currentHead.filterVis
                                   ),
                                   "</a>",
                                   "<span class='ws-browser-filter ws-browser-filter-trigger' style='",
                                   (currentHead.hasFilter ? 'display: block;' : ''),
                                   "' hasFilter='false'></span>"
                                ].join('')
                             )
                          ),
                          "</div>" ,
                          "</td>"
                       ].join(''));
                    }
                 }
              }
              result.push("<td class='ws-browser-header-cell-scroll-placeholder' width='", this._scrollWidth, "' style = 'display: none'>&nbsp;</td>");
              result.push("</tr>");
           }
           if(Env.constants.browser.isIE && hasColSpan){
              var temp = [];
              temp.push('<tr class="ws-browser-ie-head-row-colspan-fix">');
  
              //Столбцов должно быть ровно столько же, сколько в реальной шапке
              if (showSelectionCheckbox)
                 temp.push('<td></td>');
  
              this._columnMap.forEach(function() {
                 temp.push("<td></td>");
              });
  
              temp.push('<td class="ws-browser-header-cell-scroll-placeholder" style = "display: none"></td>');
  
              temp.push('</tr>');
              result = temp.concat(result);
           }
           this._headerContentFlags['headerRows'] = !!result.length;
           return result.join('');
        },
  
        _renderColgroups: function(noCache){
           if(this._colgroupCache && !noCache)
              return this._colgroupCache;
  
           this._colgroupCache = this._columnMap.reduce(function(resMemo, colMap) {
              var width;
              if ((colMap.fixedSize || this._haveAutoWidth()) && (width = colMap.width))
                 resMemo.push('<col class="ws-browser-col" width="', width, '"/>');
              else
                 resMemo.push('<col class="ws-browser-col"/>');
              return resMemo;
           }.bind(this), [this._needShowSelectionCheckbox() ? '<col width="24"/>' : ""], this).join('');
  
           return this._colgroupCache;
        },
        _renderHead: function() {
           var borderClass = this._options.display.useDrawingLines ? 'ws-browser-border' : '',
              rows = this._prepareHeadTemplate(this._prepareHeadStruct()),
              res = [
                 '<table cellspacing="0" class="ws-table-fixed ws-browser-head '+borderClass+'">' ,
                 '<colgroup>' ,
                 this._renderColgroups(),
                 '</colgroup>' ,
                 '<thead>',
                 rows,
                 '</thead>' ,
                 '</table>'
              ];
  
           return res.join('');
        },
        /**
         *
         * Перерисовать табличное представление без запроса к БД.
         * @param [forceRefreshHead]  - перерисовать шапку обязательно
         */
        refresh: function(forceRefreshHead){
           this._runInBatchUpdate('TableView.refresh', function() {
              if(this._dReady.isReady() && this._currentRecordSet){
                 this._rowSelectionSetted = false;
                 if(this._onBeforeRenderActions()){
                    //если из onBeforeRender вернули свои колонки или обязательная перерисовка
                    if(forceRefreshHead || !this._columnMap || this._columnMap.length === 0){
                       this._refreshHead();
                    }
                    // Пересчитать col ширины и записать в bodyColumns
                    // мы ищем так, чтобы точно найти колонки только для тела таблицы. иначе можно найти много чего... например, colgroup от сетки в шаблоне
                    //А еще мы должны находить только первое соответствие ибо в шаблон редактирования по месту положили браузер и все сломалось.
                    var colgroup = this._data.find('colgroup:first');
                    colgroup.empty().append(this._renderColgroups());
                    this._bodyColumns = colgroup.children(".ws-browser-col");
                    this._drawBody();
  
                    if(!this._refreshResults())
                       this._setHeight();
                 }
              }
           });
        },
        _refreshResults: function(){
           return false;
        },
        /**
         * Формирует шапку таблицы и выводит ее
         */
        _refreshHead: function(){
           if(this._columnMap.length === 0){
              //если у нас изменились колонки, то нужно их пересчитать и обновить colgroup
              //чтобы colgroup посчитался заново, потрём то, то закэшированно
              this._colgroupCache = undefined;
              this._mapColumns();
              //TODO придумать механизм пересчета колгрупп в одном месте для всех сразу.
              this._data.find('colgroup').empty().append(this._renderColgroups());
           }
           //если у нас нет шапки, то не будем перерисовывать заголовок
           if(!this._options.display.showHead)
              return;
           var head = this._renderHead(),
              isSelectedAllCheckbox = this._selectAllCheckboxRow.hasClass('ws-browser-selected');
  
           this._headContainer.find('.ws-browser-head-scroller').empty().append(head);
           this._head =  this._headContainer.find('.ws-browser-head');
           if(typeof(this._options.display.headRender) === 'function') {
              this._options.display.headRender.apply(this, [this._head]);
           }
           this._initHeadVariables();
           this._bindHeaderEvents();
           this.setCheckboxState(isSelectedAllCheckbox);
        },
        /**
         * Задает маппинг колонок браузера, сливая в один массив все реально отображаемые колонки, с полным набором их параметров.
         */
        _mapColumns: function(){
           var configColumns = this._options.display.columns,
              recivedColumns = this._currentRecordSet && this._currentRecordSet.getColumns(),
              columns = configColumns ? configColumns : recivedColumns,
              num = 0;
           if(columns){
              for(var i in columns){
                 if(columns.hasOwnProperty(i)){
                    var cur = columns[i],
                       title = cur.title ? cur.title : '',
                       field = cur.field ? cur.field : title,
                       fieldType = (recivedColumns && recivedColumns[field]) ? recivedColumns[field].type : (cur.type ? cur.type : null);
                    this._columnMap[num] = {
                       title: title,
                       extendedTooltip: cur.extendedTooltip ? cur.extendedTooltip : false,
                       isSortable: cur.field !== undefined && (cur.isSortable !== undefined ? cur.isSortable : false),
                       field: field,
                       render: cur.render ? cur.render : null,
                       highlight: cur.highlight ? cur.highlight : false,
                       type: fieldType,
                       fixedSize: cur.fixedSize ? cur.fixedSize : false,
                       textAlign: cur.textAlign ? cur.textAlign : 'auto',
                       textVerticalAlign: cur.textVerticalAlign ? cur.textVerticalAlign : 'top',
                       //TODO отказаться от verticalAlign
                       titleVerticalAlign: cur.titleVerticalAlign ? cur.titleVerticalAlign : cur.verticalAlign ?
                          cur.verticalAlign : null,
                       className: cur.className ? cur.className : '',
                       //TODO отказаться от captionAlign
                       titleAlign: cur.captionAlign ? cur.captionAlign : cur.titleAlign ? cur.titleAlign : 'auto',
                       captionRender: cur.captionRender ? cur.captionRender : null,
                       formatValue: cur.formatValue ? cur.formatValue : null,
                       filterDialog: cur.filterDialog ? cur.filterDialog : null,
                       filterName: cur.filterName ? cur.filterName : title,
                       visualFilterFunction: cur.visualFilterFunction ? cur.visualFilterFunction : null,
                       minWidth: cur.minWidth || null,
                       index: num,
                       cellTemplate: cur.cellTemplate ? cur.cellTemplate : false
                    };
                    this._columnMap[num].width = cur.width ? parseInt(cur.width, 10) : (this._getDefWidth(this._columnMap[num].type));
                    num++;
                 }
              }
           }
        },
        /**
         *
         */
        getColumnMap: function(){
           return this._columnMap;
        },
        /**
         *
         */
        getColumnsTitle: function(fieldOnly){
           var titles = [],
              columns = this._options.display.columns;
           for(var i = 0; i < columns.length; i++){
              if (fieldOnly){
                 if (columns[i].hasOwnProperty('field')){
                    titles.push(columns[i].title);
                 }
              } else {
                 titles.push(columns[i].title);
              }
           }
           return titles;
        },
        /**
         * По типу столбца возвращает дефолтное значение ширины для него, или null в случае отсутствия такого типа
         * @param {String} columnType  тип колонки
         * @return {Number} ширина колонки или null в случае отсутствия ее или дефолтного значения
         */
        _getDefWidth : function(columnType){
           if(columnType && Env.constants.Browser.defColWidth[columnType])
              return Env.constants.Browser.defColWidth[columnType];
           return null;
        },
        _initHeadVariables : function(){
           this._head = this._rootElement.find('thead');//head может огказаться старым
           this._headColumns = this._head.parent().find('> colgroup col.ws-browser-col');
           this._bodyColumns = this._body.parent().find('> colgroup col.ws-browser-col');
           this._resultsColumns = this._rootElement.find('.ws-browser-foot.results').find('col.ws-browser-col');
           this._scrollGapPlaceholder = this._head.find('td.ws-browser-header-cell-scroll-placeholder');
           //почему-то для шапки не срабатывают псевдоселекторы :first-child и :last-child, поэтому добавим классы
           //классы нужны для отступов
           this._head.find('.ws-browser-header-cell:last').addClass('ws-browser-last-column');
           this._head.find('.ws-browser-header-cell:first').addClass('ws-browser-first-column');
           this._initColumnsWidth();
           if(this._options.display.resizable)
              this._initResizeEvents();
        },
        /**
         * инициализация resize для шапки
         */
        _initColumnsWidth: function(){
           var k = 0,
              self = this;
           self._resizingColumn = self._head.find('th:first');
           self._idCols = [];
  
           for(var i in self._columnMap){
              if(self._columnMap.hasOwnProperty(i)){
                 self._idCols[k] = i;
                 k++;
              }
           }
  
           k = 0;
           self._body.find('.ws-browser-cell').eq(0).children('.ws-browser-cell').each(function(){
              $(this).attr("id", self._idCols[k]);
              k++;
           });
        },
        /**
         * Выделяет строку с переданным элементом флагом отметки записи и синей полоской, как активную
         * @param {Event} event событие клика по элементу
         */
        _selectAndSetActiveElement: function(event){
           var row = $(event.target).closest(this._rowSelector);
           this._selectActiveElement(row);
           if(event.button === 0) {
              this.setActiveElement(row, false, false, true);
           }
        },
        /**
         * Добавляет событие движения мыши для отрисовки опций строки
         */
        _initEvents: function(){
           var self = this,
              bodyParent = this._body.parent();
  
           bodyParent.on('click', '.ws-browser-checkbox-holder', function(event){
              //не отлавливаем клик по невидимому чекбоксу
              var checkbox = $(this).find('.ws-browser-checkbox');
              if(checkbox.hasClass('ws-invisible')){
                 return;
              }
              //Внутри обработчика может быть всякий сложный код, изменяющий размеры браузера, и оповещающий об этом.
              //Завернём этот обработчик в пакет, чтобы все пересчёты авторазмеров склеились в один, и запустились после обработчика.
              self._runInBatchUpdate('ws-browser-checkbox-holder click', function() {
                 if(self.isEnabled()){
                    self._selectAndSetActiveElement(event);
                 }
              });
  
              event.stopImmediatePropagation();
              return false;
           });
           TableView.superclass._initEvents.apply(this, arguments);
  
           if(this._options.useSelection) {
              if (this._options.useHoverRowAsActive) {
                 bodyParent.on('mouseenter', '[rowkey]', function () {
                    if (!self._useKeyboard) {
                       self.setActiveElement.apply(self, [$(this), false, true, false]);
                    }
                 });
                 $(this._data).bind('mouseleave', $.proxy(self._activeHoverHideSelection, self));
              }
           }
  
           var browserContainer = this._rootElement.find('.ws-browser-container'),
              headScroller = this._headContainer.find('.ws-browser-head-scroller');
           browserContainer.bind('scroll', function(){
              var scrollLeft = browserContainer.scrollLeft();
              headScroller.scrollLeft(scrollLeft);
              self._onScrollActions(scrollLeft);
           });
        },
        /**
         * Обработчик скролла в теле браузера
         * @param {Number} scrollLeft прокрутка содержимого по-горизонтали
         */
        _onScrollActions: function(scrollLeft){
        },
        _unselectRow: function(key) {
           TableView.superclass._unselectRow.apply(this, arguments);
           var selectAllRow = this._head.find('.ws-browser-selected');
           if (selectAllRow.length !== 0) {
              selectAllRow.removeClass('ws-browser-selected');
              selectAllRow.find('.ws-browser-checkbox-holder').attr('title', rk('Отметить всю страницу'));
           }
        },
        _selectRow: function(key) {
           var isSelected = TableView.superclass._selectRow.apply(this, arguments);
  
           this.setCheckboxState(isSelected);
           return isSelected;
        },
        /**
         * Проверять ли на наличие пейджинга при отображении опций записи на смену порядкового номера
         * @returns {boolean}
         * @private
         */
        _checkOnPaging: function () {
           return this.getPagingMode() !== '';
        },
        /**
         *
         * Выделяет или снимает выделение цветом чётных строк в зависимости от указанного значения {@link hasZebra}.
         * @param {Boolean} hasZebra
         * @example
         * <pre>
         *    tableView.zebraBody(true);
         * </pre>
         * @see hasZebra
         */
        zebraBody: function(hasZebra) {
           var rows = this._body.find('.ws-browser-table-row');
           hasZebra = !!hasZebra;
           rows.removeClass('rE');
           this._data.toggleClass('ws-browser-hasZebra', hasZebra);
           this._options.display.hasZebra = hasZebra;
           if(hasZebra){
              rows.filter(':even').addClass("rE");
           }
           //Включаем зебровые стили для блока данных, зависимые от данных и шапки
           this._updateHeaderContentStyles();
           this._updateSelection();
        },
        /**
         *
         * Выбор колонок, в которых будет подсвечиваться текст (text).
         * @param {Array} сolumns Имена колонок, которым включаем\отключаем подсветку.
         * @param {Boolean} instant Включение\отключение подсветки
         * @example
         * <pre>
         *    tableView.setColumnHighlight(['Цена','Производитель'],true)
         *    tableView.setColumnHighlight(['Цена'])
         * </pre>
         */
        setColumnHighlight: function(columns, instant){
           for (var j=0;j<columns.length;j++)
           {
              var col = columns[j].replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  
              var index = -1;
              if (instant !== true){
                 instant = false;
              }
  
              for (var i=0;i<this._columnMap.length;i++){
                 if (this._columnMap[i].title == col){
                    index = this._columnMap[i].index;
                    break;
                 }
              }
  
              if (index != -1){
                 $('[coldefindex = ' + index + ']').find('.ws-browser-div-cut').toggleClass("ws-browser-text-highlight",instant);
              }
           }
        },
        /**
         *
         * Устанавливает новый набор и конфигурацию отображаемых колонок.
         * Если необходимо, чтобы данные были отображены так, как придут в наборе данных, то в качестве значения параметра передаётся пустой массив.
         * В этом случае отображение изменится при следующей отрисовке данных.
         * Обратите внимание, что опция title является обязательной, и если заголовок в колонке пустой, то нужно передать его как ''.
         * @param {Array} columns Массив с параметрами колонок, аналогичен параметру из конфига
         * @example
         * <pre>
         *    var columns = tableView.getColumns(),
         *        newColumns = [];
         *    for(var i = 0, l = columns.length; i < l; i++){
         *       if(columns[i].title !== "Примечание")
         *          newColumns.push(columns[i]);
         *    }
         *    newColumns.push({
         *       title: 'ФИО',
         *       field: 'РП.ФИО'
         *    });
         *    tableView.setColumns(newColumns);
         * </pre>
         * @see columns
         * @see getColumns
         * @see toggleColumn
         */
        setColumns: function(columns){
           var self = this;
           //Экранируем // отваленные при присвоении строк
           for(var i = 0, l = columns.length; i < l; i++){
              //Добавляем проверку на дурака
              columns[i].title = columns[i].title || '';
              columns[i].title = columns[i].title.replace(/\\\.|\\/g, function(result){
                 if(result.length == 2) return '.'; else return result + result;
              }); // Замена \ на \\
           }
           function dReadyCallback() {
              self._options.display.columns = columns;
              self._columnMap = [];
              self._colgroupCache = undefined;
              self._mapColumns();
              self.refresh(true);
           }
           if(this._dReady.isReady()){
              dReadyCallback();
           }else {
              this._dReady.addCallback(function () {
                 dReadyCallback();
              });
           }
        },
        /**
         *
         * Получить текущее описание колонок табличного браузера.
         * @returns {Array} Текущее описание колонок табличного представления.
         * @example
         * <pre>
         *    var columns = tableView.getColumns(),
         *        newColumns = [];
         *    for(var i = 0, l = columns.length; i < l; i++){
         *       if(columns[i].title !== "Примечание")
         *          newColumns.push(columns[i]);
         *    }
         *    newColumns.push({
         *       title: 'ФИО',
         *       field: 'РП.ФИО'
         *    });
         *    tableView.setColumns(newColumns);
         * </pre>
         * @see toggleColumn
         * @see setColumns
         * @see columns
         */
        getColumns: function(){
           return this._options.display.columns || (this._currentRecordSet && this._currentRecordSet.getColumns());
        },
        /**
          *
          * Получить колонку по имени.
          * @param name
          * @returns {*}
          */
        getColumnByName: function(name){
           return this._getColumnByAttr("title",name);
        },
        /**
         * Получить колонку по атрибуту
         * @param attrName имя атрибута
         * @param attrValue значение атрибута
         * @returns {Object}
         */
        _getColumnByAttr: function (attrName,attrValue){
           var columns, column;
           if(attrValue){
              columns = this.getColumns();
              if(typeof columns !== "undefined"){
                 if(columns instanceof Array){
                    for(var i = 0, l = columns.length; i < l; i++){
                       if(columns[i][attrName] === attrValue){
                          column = columns[i];
                          break;
                       }
                    }
                 }else
                    column = columns[attrValue];
              }
           }
           return column;
        },
        /**
         *
         * Признак видимости колонки.
         * @param {String} name Имя колонки
         * @returns {Boolean} Возвращает признак видима ли колонка
         */
        isVisibleColumn : function(name){
           var column = this._getColumnByAttr("field",name);
           return !!column;
  
        },
        /**
         *
         * Возвращает массив полей, имееющихся на бизнес-логике для данного представления.
         * @param {Boolean} withTitles возвращать поля вместе с указанным названием.
         * @returns {Array} массив полей.
         */
        getFields: function (withTitles) {
           var fields = [],
              columns = this.getColumns();
           for (var i = 0; i < columns.length; i++) {
              if (columns[i].field) {
                 fields.push(withTitles ? { 'title': columns[i].title, 'field': columns[i].field } : columns[i].field);
              }
           }
           return fields;
        },
        /**
         * Изменяет статус включенности элемента
         * Т.е. возможность взаимодейтсвия с ним пользователя
         *
         * <pre>
         *    if(control.isEnabled())
         *       control.setEnabled(false);
         * </pre>
         * @param {Boolean} enable статус "включенности" элемента управления
         */
        _setEnabled: function(enable){
           TableView.superclass._setEnabled.apply(this, arguments);
        },
        /**
         *
         * Устанавливает обработчик отрисовки строки
         * @param {Function} render Функция-обработчик. В качестве аргументов получит запись {Deprecated/Record} и строку {jQuery}, которая ещё не прикреплена к дом-дереву (!)
         * @example
         * <pre>
         *    tableView.setRowRender(function(record, row){
         *       row.addClass("my-table-row");
         *    });
         * </pre>
         */
        setRowRender: function(render){
           this._options.display.rowRender = render;
        },
        /**
         *
         * Установить функцию рендеринга для колонки
         * Метод определяет функцию, которая для каждой записи табличного представления изменит отображаемое значение в заданной колонке.
         * Изменения, вносимые функцией, не влияют на значения записей базы данных. Метод переопределяет ранее установоленную функцию рендеринга.
         * @param {String} columnName Название колонки, для значений которой применяется функция рендеринга.
         * @param {Function} render Функция рендеринга.
         * Первый аргумент - запись табличного представления.
         * Второй аргумент - значение опции columnName.
         * @example
         * Преобразовать в гиперссылки адреса веб-сайтов производителей товаров.
         * <pre>
         *    browser.setColumnRender('Сайт', function(record, field) {
         *       var url = record.get(field),
         *           prod;
         *       //если адрес сайта есть и соответствует необходимым условиям, то продолжаем выполнение преобразования
         *       if (url && url.match(/(?:[a-z]+:\/\/|www\.).+?\.\S*(?:[\?&a-z0-9\.=])/).length>0) {
         *           //формируем необходимый формат гиперссылки
         *           prod = '( <a href="' + ( url.match(/[a-z]+:\/\//) ? url : 'http://' + url ) + '" target="_blank">' + url + '</a> )';
         *       }
         *       return prod;
         *    });
         * </pre>
         */
        setColumnRender: function(columnName, render){
           for(var i = 0, len = this._options.display.columns.length; i < len; ++i){
              var column = this._options.display.columns[i];
              if(column.title === columnName){
                 column.render = render;
                 break;
              }
           }
        },
        /**
         * Разрушает экземпляр класса
         *
         * <pre>
         *    control.destroy();
         * </pre>
         */
        destroy: function(){
           $(this._data).unbind('mouseleave', $.proxy(debounce.call(this._activeHoverHideSelection, 15), this));
           $('[rowkey]', this._body.parent()[0]).off('mouseenter');
           this._unbindHeaderEvents();
           TableView.superclass.destroy.apply(this, arguments);
        },
        /**
         *
         * Установить состояние режима табличного представления "только для чтения".
         * @param {Boolean} status Возможные значения:
         * <ol>
         *    <li>true - режим "только для чтения" включён;</li>
         *    <li>false - выключен.</li>
         * </ol>
         * @example
         * <pre>
         *    tableView.setReadOnly(true);
         * </pre>
         */
        setReadOnly: function(status){
           TableView.superclass.setReadOnly.apply(this, arguments);
        },
        /**
         * Обработчик удаления записи
         * @param {Array} records Удалённые записи
         * @protected
         */
        _updateAfterRecordsDelete: function(records){
           TableView.superclass._updateAfterRecordsDelete.apply(this, arguments);
           if( !this._options.reloadAfterChange ){
              for(var i = 0; i < records.length; ++i){
                 var key = records[i].getKey();
                 if( this.haveRow(key) ){
                    this.findRow(key).remove();
                    this._rowsMap[key] = undefined;
                 }
              }
              this.setActiveRow(this._activeElement);
           }
        },
        /**
         *
         * Скрывает/показывает указанную колонку.
         * Принимает признак что нужно сделать - скрыть или показать колонку
         * Note! Может и должен работать только с колонками, которые были заданы (!)изначально (в джине). Нельзя
         * сделать setColumns а потом их добавлять или удалять из отображения этим методом.
         * @param {String} columnField имя поля, к которому привязана колонка, которую нужно скрыть
         * @param {String|Object} columnField имя поля или набор полей, к которому(ым) привязана колонка, которую нужно скрыть или показать
         * @param {Boolean} toggle скрыть или показать колонку
         * @example
         * <pre>
         *    dataView.toggleColumn({
         *       "Документ.Приоритет": false,
         *       "Документ.Порядок": true
         *    });
         * </pre>
         * @see columns
         * @see setColumns
         * @see getColumns
         */
        toggleColumn : function (columnField, toggle){
           var result = [],
              columns = this._initialColumns,
              num = 0,
              isObject = toggle === undefined && columnField && typeof columnField === 'object';
  
           for (var i in  columns){
              if (columns.hasOwnProperty(i)){
                 var cur = columns[i];
                 if ((isObject && cur.field in columnField) || (cur.field === columnField)) {
                    cur.show = isObject ? columnField[cur.field] : toggle;
                 }
                 if (cur.show !== false){
                    result[num] = cur;
                    num++;
                 }
              }
           }
  
           this.setColumns(result);
        }
     });
     return TableView;
  
  });
  