Задача: необходимо реализовать возможность переключения реестра в разделе. То есть, находясь в разделе "Контрагенты" переключать реестры с физическими и юридическими лицами, причём у каждого реестра свои определенные колонки.
![]()
Решение состоит из 2х частей:
- Добавить кнопки переключения между представлениями
- Собственно сделать страницы представлений

Добавление кнопок переключения реестров в раздел
Подготовлены спрайты кнопок:
![]()
![]()
![]()
Размер 30*60. Смысл в том, чтобы при наведении мыши фон кнопки съезжал на 30 пикселей, и как бы подсвечивался. Создан модуль, назвать, например SmrCustomButtonsCSS и определить LESS:
.blue-button-wrapper {
width: 30px;
height: 30px;
padding: 0px;
margin: 0px 5px 0px 5px;
}
.blue-button-image {
display: inline;
background-position: 0px 0px;
width: 30px;
height: 30px;
padding: 0px;
margin: 0px;
}
.blue-button-image:hover {
display: inline;
background-position: 0px -30px;
}
.blue-button-pressed {
display: inline;
background-position: 0px -30px;
width: 30px;
height: 30px;
padding: 0px;
margin: 0px;
}Создана замещающая страница "AccountSectionV2":
define("AccountSectionV2", ["ConfigurationConstants", "css!SmrCustomButtonsCSS"],
function(ConfigurationConstants) {
return {
entitySchemaName: "Account",
attributes: { },
methods: {
showLegalView: function(){
this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/LegalAccountSectionV2" });
},
showIndividualView: function(){
this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/IndividualAccountSectionV2" });
},
showAllView: function(){
this.sandbox. publish( "PushHistoryState", { hash : "SectionModuleV2/AccountSectionV2" });
},
isSwitchButtonsContainerVisible: function() {
var isCardVisible = this.get("IsCardVisible");
return !isCardVisible;
}
},
diff: /**SCHEMA_DIFF*/[
{
"operation": "insert",
"name": "SwitchViewButtonsContainer",
"parentName": "ActionButtonsContainer",
"propertyName": "items",
"values": {
"itemType": Terrasoft.ViewItemType.CONTAINER,
"visible": {
"bindTo": "isSwitchButtonsContainerVisible"
},
"items": []
}
},
{
"operation": "insert",
"parentName": "SwitchViewButtonsContainer",
"propertyName": "items",
"name": "SwitchToAllDataView",
"values": {
"layout": {
"column": 23,
"row": 0,
"colSpan": 1,
"rowSpan": 1
},
"itemType": Terrasoft.ViewItemType.BUTTON,
"style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
//"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
pressed: {bindTo: "LegalGridActive"},
"imageConfig": {
"source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
"params": {
"schemaName": "SmrCustomButtonsCSS",
"resourceItemName": "GridDataViewIconBlue"
}
},
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-pressed"
},
"click": {"bindTo": "showAllView"}
}
},
{
"operation": "insert",
"parentName": "SwitchViewButtonsContainer",
"propertyName": "items",
"name": "SwitchToPersonalDataView",
"values": {
"layout": {
"column": 23,
"row": 0,
"colSpan": 1,
"rowSpan": 1
},
"itemType": Terrasoft.ViewItemType.BUTTON,
"style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
//"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
pressed: {bindTo: "LegalGridActive"},
"imageConfig": {
"source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
"params": {
"schemaName": "SmrCustomButtonsCSS",
"resourceItemName": "GridPersonalDataViewIconBlue"
}
},
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image"
},
"click": {"bindTo": "showIndividualView"}
}
},
{
"operation": "insert",
"parentName": "SwitchViewButtonsContainer",
"propertyName": "items",
"name": "SwitchToLegalDataView",
"values": {
"layout": {
"column": 23,
"row": 0,
"colSpan": 1,
"rowSpan": 1
},
"itemType": Terrasoft.ViewItemType.BUTTON,
"style": Terrasoft.controls.ButtonEnums.style.TRANSPARENT,
//"iconAlign": Terrasoft.controls.ButtonEnums.iconAlign.RIGHT,
iconAlign: Terrasoft.controls.ButtonEnums.iconAlign.TOP,
pressed: {bindTo: "LegalGridActive"},
"imageConfig": {
"source": Terrasoft.ImageSources.SOURCE_CODE_SCHEMA,
"params": {
"schemaName": "SmrCustomButtonsCSS",
"resourceItemName": "GridLegalDataViewIconBlue"
}
},
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image",
pressedClass: "blue-button-pressed"
},
"click": {"bindTo": "showLegalView"}
}
}
]/**SCHEMA_DIFF*/
};
}
);Страницы разделов
Созданы 2 страницы разделов. Код для реестра физических лиц:
define("IndividualAccountSectionV2", ["ConfigurationConstants", "css!SmrCustomButtonsCSS"],
function(ConfigurationConstants) {
return {
entitySchemaName: "Account",
methods: {
/**
* Инициализирует фильтры экземпляра запроса
* @protected
* @overridden
* @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
*/
initQueryFilters: function (esq) {
this.callParent(arguments);
esq.filters.removeByKey("ClientType");
esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "80FD3F74-0AF4-4F8A-9F0B-F626E4F05147"));
},
/**
* Возвращает представления раздела по умолчанию.
* Реестр, Аналитика не создаётся
* @overridden
* @return {Object} Представления раздела по умолчанию
*/
getDefaultDataViews: function() {
var gridDataView = {
name: this.get("GridDataViewName"),
caption: this.getDefaultGridDataViewCaption(),
icon: this.getDefaultGridDataViewIcon()
};
var analyticsDataView = {
};
return {
"GridDataView": gridDataView,
"AnalyticsDataView": analyticsDataView
};
},
/**
* Получает пункты меню кнопки "Вид"
* @overridden
* @virtual
* @return {Terrasoft.BaseViewModelCollection} Возвращает пункты меню кнопки "Вид"
*/
getViewOptions: function() {
var viewOptions = this.Ext.create("Terrasoft.BaseViewModelCollection");
viewOptions.addItem(this.getButtonMenuItem({
"Caption": {"bindTo": "Resources.Strings.SortMenuCaption"},
"Items": this.get("SortColumns")
}));
viewOptions.addItem(this.getButtonMenuItem({
"Caption": {"bindTo": "Resources.Strings.OpenGridSettingsCaption"},
"Click": {"bindTo": "openGridSettings"}
}));
return viewOptions;
}
},
diff: /**SCHEMA_DIFF*/[
{
"operation": "merge",
"name": "SwitchToAllDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image"
}
}
},
{
"operation": "merge",
"name": "SwitchToPersonalDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-pressed"
}
}
},
{
"operation": "merge",
"name": "SwitchToLegalDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image"
}
}
}
]/**SCHEMA_DIFF*/
};
}
);Код страницы для реестра юридических лиц:
define("LegalAccountSectionV2", ["RightUtilities", "ConfigurationConstants", "css!SmrCustomButtonsCSS"],
function (RightUtilities, ConfigurationConstants) {
return {
entitySchemaName: "Account",
attributes: {},
messages: {},
methods: {
/**
* Инициализирует фильтры экземпляра запроса
* @protected
* @overridden
* @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
*/
initQueryFilters: function (esq) {
this.callParent(arguments);
esq.filters.removeByKey("ClientType");
esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "DD4E6E34-21D3-4F09-A417-6FC4116876B5"));
},
/**
* Возвращает представления раздела по умолчанию.
* Реестр, Аналитика не создаётся
* @overridden
* @return {Object} Представления раздела по умолчанию
*/
getDefaultDataViews: function() {
var gridDataView = {
name: this.get("GridDataViewName"),
caption: this.getDefaultGridDataViewCaption(),
icon: this.getDefaultGridDataViewIcon()
};
var analyticsDataView = {
};
return {
"GridDataView": gridDataView,
"AnalyticsDataView": analyticsDataView
};
},
/**
* Получает пункты меню кнопки "Вид"
* @overridden
* @virtual
* @return {Terrasoft.BaseViewModelCollection} Возвращает пункты меню кнопки "Вид"
*/
getViewOptions: function() {
var viewOptions = this.Ext.create("Terrasoft.BaseViewModelCollection");
viewOptions.addItem(this.getButtonMenuItem({
"Caption": {"bindTo": "Resources.Strings.SortMenuCaption"},
"Items": this.get("SortColumns")
}));
viewOptions.addItem(this.getButtonMenuItem({
"Caption": {"bindTo": "Resources.Strings.OpenGridSettingsCaption"},
"Click": {"bindTo": "openGridSettings"}
}));
return viewOptions;
}
},
diff: /**SCHEMA_DIFF*/[
{
"operation": "merge",
"name": "SwitchToAllDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image"
}
}
},
{
"operation": "merge",
"name": "SwitchToPersonalDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image"
}
}
},
{
"operation": "merge",
"name": "SwitchToLegalDataView",
"values": {
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-pressed"
}
}
}
]/**SCHEMA_DIFF*/
};
});Что нужно отметить в этих страницах:
/**
* Инициализирует фильтры экземпляра запроса
* @protected
* @overridden
* @param {Terrasoft.EntitySchemaQuery} esq Запрос, в котором будут инициированы фильтры
*/
initQueryFilters: function (esq) {
this.callParent(arguments);
esq.filters.removeByKey("ClientType");
esq.filters.add("ClientType", this.Terrasoft.createColumnFilterWithParameter(
this.Terrasoft.ComparisonType.EQUAL, "SmrClientType.Id", "DD4E6E34-21D3-4F09-A417-6FC4116876B5"));
}Мы переопределяем метод для установки фильтрации записей (например, только физические лица).
Также, переопределяем методы getDefaultDataViews и getViewOptions, чтобы скрыть функции, которые не работают в новых реестрах (почему - не было времени разбираться).
Проблемы
В представлениях (дополнительных реестрах) не работают некоторые функции, а именно аналитика и некоторые действия из меню "Вид".
P.S.: Очень ждем возможности использовать такую возможность базовыми средствами, кейсов много - это и деление на сотрудники/контакты и физ/юр лица. В ситуациях, когда набор колонок одинаковый, конечно рекомендуется пользоваться стандартными динамическими группами.
Кстати, а кейс ведения физических и юридических лиц в Контрагентах давно стал практикой?
Это практика при условиях, что:
- Клиенту нужно смотреть суммарную разнообразную аналитику по всем клиентам физики+юрики, создаваемую через стандартные средства (в представлении "Аналитика")
- Клиента бесит, когда ему каждый раз заходя в раздел Контакты, нужно отфильтровывать своих сотрудников и сотрудников подрядчиков, чтобы поработать с клиентами:smile:
Александр, золотой вы человек :twisted:
Как раз ломал голову как бы не убить кучу времени и реализовать представления с переключением, а тут пример живой.
Рад помочь, для этого и нужно комьюнити:smile:. Если есть еще вопросы в стиле "В 3ке это делалось в 5 кликов аналитиком, а как это сделать это в 7ке?" - спрашивайте, наверняка найдем проектное решения в закромах, пусть и не всегда очевидное:lol:
"Александр Свистунов" написал:спрашивайте, наверняка найдем проектное решения в закромах
Я ж воспользуюсь :)
Case: отфильтровать в разделе Счета все счета, к которым прикреплен продукт = "ххх". То есть фильтр по вхождению некой информации в деталь - аналогично в любом разделе. Так как штатно доступны только фильтры по детали с агрегатной функцией, то видимо надо как-то добавлять свой фильтр?
В 3х это делалось через модификацию sq_, в 5х вроде как вообще работало штатно через обратные связи.
Раздел итоги элемент список не предлагать :) там делается посредством списка на основании Продукт в счете - пробовал, работает.
Александр, если я правильно понял ваш Кейс, то это делается через подобный фильтр
"Толмачев Дмитрий Юрьевич" написал:делается через подобный фильтр
Вот только сейчас сумел поменять count на exists - после того как выбрал хоть какую-то колонку при фильтрации по связанной таблице.
Именно это и было нужно, спасибо
Ну вот как надо было догадаться сначала выбрать произвольное поле типа Количество, чтобы попасть обратно в настройки фильтра... Идем и ищем (с)
Чтобы темы не плодить... товарищи, как бы попроще скрыть из меню Edit, Copy и особенно Delete для детали, основанной на вьюшке?
Деталь самодельная, родитель BaseGridDetailV2.
Александр,
Для решения вашей задачи достаточно переопределить функцию addRecordOperationsMenuItems на созданной вами странице реестра
[javascript]
methods: {
/**
* @overridden
*/
addRecordOperationsMenuItems: Ext.emptyFn
//...
}
[/javascript]
"Толмачев Дмитрий Юрьевич" написал:Ext.emptyFn
Спасибо, попробую так.
Я ее нашел, только наивно переопределял как
[javascript]
addRecordOperationsMenuItems: function() {
}
[/javascript]
То есть пытался заполнить "ничем"...
А я где-то такую функцию нашёл
[javascript]
getAddRecordButtonVisible: function() {
return false;
},
[/javascript]
Добрый день!
Александр, спасибо за предоставленной пример, остался один непонятный для меня момент, какие действия нужно выполнить, что бы новая страница раздела (например, IndividualAccountSectionV2) стала доступна по URL "http://localhost/0/Nui/ViewModule.aspx#SectionModuleV2/IndividualAccoun…"?
Сергей,
Если навскидку, то достаточно чтобы в качестве родительской страницы использовалась страница "Базовая схема раздела".
В данном случае, насколько я помню, у страниц LegalAccountSectionV2 и IndividualAccountSectionV2 в качестве родительской использовалась "AccountSectionV2", т.к. по сути это дочерние ветки.
Александр, большое спасибо за то что делитесь отличным материалом!
В процессе его изучения и использования появилось несколько вопросов. Буду очень благодарен за любой совет или помощь.
1. Правильно ли я понимаю, что через свойства Diff:
[javascript]
pressed: {bindTo: "LegalGridActive"},
[/javascript]
и
[javascript]
classes: {
wrapperClass: "blue-button-wrapper",
imageClass: "blue-button-image",
pressedClass: "blue-button-pressed"
},
[/javascript]
Вы указываете, будет ли элемент содержать класс "blue-button-pressed" ?
Если да, то что в этом случае должна возвращать функция "LegalGridActive" - true или false?
У меня на версии 7.7 не получилось сделать так, чтобы к елементу "прицепился" мой класс указанный в pressedClass (как у Вашем примере: pressedClass: "blue-button-pressed"). Вместо него появился "дефолтный" класс - t-btn-pressed.
вот код DIFF:
[javascript]
{
"operation": "insert",
"parentName": "SignButtonsContainer",
"propertyName": "items",
"name": "SignBtnVip",
"values": {
"itemType": Terrasoft.ViewItemType.BUTTON,
"caption": {"bindTo": "sign_Vip"},
"hint": {"bindTo": "Resources.Strings.sign_Vip"},
"imageConfig" : {"bindTo": "Resources.Images.sign_vip_Image"},
//"click": {"bindTo": "onVipButtonClick"},
pressed : {bindTo : "VipButtonPressed"},
classes : {
wrapperClass : "sign-btn",
imageClass : "sign-img",
pressedClass : "sign-img-pressed"
},
"visible": true
}
},
[/javascript]
В итоге вместо ожидаемого css-класса "sign-img-pressed" - появляется "t-btn-pressed". Не могу понять что я сделал не так...
2. Можно ли какимто образом отключить для моего елемента генерацию стандрартных css-классов?
(для элемента Terrasoft.ViewItemType.BUTTON сгенерировались - t-btn-wrapper, t-btn-no-text-padding, t-btn-style-default )
3. Глобальный вопрос - не могли бы Вы подсказать, какой подход можно применить для того, чтобы можно было динамически (например по кнопочке) добавлять или удалять указанные кастомные css-классы для элемента?
В целом, очень хорошо что в 7-ке есть возможность подключать свои css-стили, это очень удобно, но вот генерация самих классов для элемента происходит немного не очевидно... :smile: