Наследование стандартной страницы LookupPage

Может кто-нибудь сталкивался с подобной проблемой.
Использую LookupUtilities:

var config = {
        entitySchemaName: "UserReport",
        columns: ["Id", "Name", "Parameters", "ReportSchemaUId"],
        multiSelect: false,
        actionsButtonVisible: false,
        lookupPageName: "LookupPage"
};
LookupUtilities.Open(this.sandbox, config, this.onReportSelected, this, null, false, false);

Все красиво, и работает корректно. Открывается окно выбора ().
Но предположим, что мне необходимо добавить дополнительную кнопку, либо изменить страницу LookupPage.
Что я делаю - наследую LookupPage.
define("CustomLookupPage", [],
        function() {
                return Ext.define("Terrasoft.configuration.CustomLookupPage",
                        {
                                extend: "Terrasoft.LookupPage",
                                alternateClassName: "Terrasoft.CustomLookupPage"
                        }
                );
        }
);

Затем, меняю config.lookupPageName = "CustomLookupPage".

var config = {
        entitySchemaName: "UserReport",
        columns: ["Id", "Name", "Parameters", "ReportSchemaUId"],
        multiSelect: false,
        actionsButtonVisible: false,
        lookupPageName: "CustomLookupPage"
};
LookupUtilities.Open(this.sandbox, config, this.onReportSelected, this, null, false, false);

Но теперь, при открытии получаю ошибку (
).
Что-то попало в бесконечный цикл, вижу ошибку переполнения стэка.

Как такое лечится?

Нравится

14 комментариев

Добрый день, Евгений!

Не понятно, что могло вызвать такой странный результат. Я бы порекомендовал обратиться в support Террасофта, пускай посмотрят.

В итоге не смог разобраться, а официальный саппорт отклоняет подобные запросы по SLA, хотя, здесь явно ошибка в стандартной конфигурации. Вобщем - ничего нового...

Предлагаю поступить немного иначе.
Не наследоваться через зависимости (в карточку),
А унаследоваться от схемы справочника (LookupPageV2) (создать свою схему наследника, не замещающую),
там в схеме "играться" с "дифами" и замещенеим, так как мы это делаем при классическом замещении клиентских схем.
И уже своего наследника подключать в зависимости.
(Мне кажется это более каноничный подход)

PS:
У меня в ближайшем будущем есть аналогичная задача - скастомить окно на базе типового окна справочника, со своими кнопками и т.д.
Я планировал действовать так.
Прошу Вас отписаться если предложенный мною вариант окажется рабочим, чтобы душа была спокойна :)

"Севостьянов Илья Сергеевич" написал:

Спасибо за идею, на первой неделе мая попробую реализовать и отпишусь!

Вообщем Вы были изначально на правильном пути, но упустили несколько моментов, по сути Ваши начинания "в купе с моей идеей" и есть итоговое решение, т.к. наследование на уровне схем все равно оказалось необходимым условием (правда через зависимости, а не механизм наследования)
Разобраться помогла схема "MultiLookupModule" входящая в базовую поставку
1) Вы реализуете модуль, зависимости которого необходимо указать не только в исходном коде но и в меню структуры

2) Сам модуль не должен быть чьим либо наследником, всё необходимое идет в его зависимости, в т.ч. и LookupPage который мы идеологически наследуем, но архитектурно через наследование в схемах - это почему-то не работает (Если у казать "Выбор из справочника" как родителя - получаем очень много разного рода ошибок при вызове).
3) Далее вот вообщем-то пример самого кода

define("MyCustomLookupPage", ["LookupPage", "LookupPageViewGenerator", "LookupUtilities", "css!LookupPageCSS"],
	function(LookupPage, LookupPageViewGenerator) {
		return Ext.define("Terrasoft.configuration.KmGMSLookupPage", {
			alternateClassName: "Terrasoft.KmGMSLookupPage",
			extend: "Terrasoft.LookupPage",
			gridWrapClasses: ["my-custom-lookup-control"]
		});
	});

перчим солим, замещаем, дополняем по вкусу и
ну и вызываем его через LookupUtilites или же есть более простой способ через this.openLookup (что само собой является оберткой над вышеуказанным и "сахаром" по сути).
Уже в таком виде - Ваш "отнаследованный" lookupPage можно вызвать следующим образом

var config = {
        entitySchemaName: "Lead",
        columns: ["Id"],
        multiSelect: false,
        actionsButtonVisible: false,
        lookupPageName: "MyCustomLookupPage"
};
this.openLookup(config, function(result){console.log(result)})

Вот.

PS:
Были попытки отнаследовать через механизм наследования схем, насколько хватило фантазии и времени - но все безрезультатны.
Хотя очень хотелось бы вот так вот взять и в diff все свои вынести изменения.
Тем не менее описанный способ, предположительно - позволяет делать то что нужно...

"Севостьянов Илья Сергеевич" написал:

Как оказалось - решение было близко)
Спасибо большое, что поделились окончательной реализацией! Я то уже переключился на более приоритетные задачи, так как посчитал эту затею бесперспективной.

На самом деле - решение не окончательное... бой продолжается :)
Есть прям несколько моментов - которые весьма и весьма удивляют... например что кнопки по факту "захардкожены" в LookupPageViewGenerator и собственно не зависят от каких либо конфигурационных параметров вызова этого самого генератора, что приводит к необходимости наследовать и его, с переопределением некоторых функций, в свою очередь "копия" генератора подобным образом не работает полностью или частично, т.к. используя наследника в качестве зависимости - получаем ряд ошибок про "sunbox of undefined" из core.js (что практически убивает надежду "по быстрому разобраться") при попытке выбора записей из такого справочника и т.д.

Кстати по какой-то причине не удается подключить к карточке страницы LookupUtilities
В конечном итоге в контексте карточки нет объекта "LookupUtilities"

define("MyCustomPage", ["LookupUtilities"],
	function(LookupUtilities) {
...

В конечном итоге в контексте карточки LookupUtilities - undefined

Не поделитесь, как вы подключали LookupUtilities к странице ?
(Я уже и в dependecies структуры добавил его, так же безрезультатно)

Там необходимо использовать V2

define("MyCustomPage", ["LookupUtilitiesV2"],
        function(LookupUtilities) {
...

Победа.

Кастом окна справочника через наследование его базового Ext-класса возможен, и даже, как оказалось не сложен.

В основном наверное в таком случае требуется юзкейз по управлению панелью с кнопками такого окна - переименование, сокрытие, добавление своих, стилизация и т.д.
HowTo по порядку

1) Создаем пользовательский модуль например "MyCustomLookupWindow"
[color=red]Внимание![/color]

  • Модуль не должен являться чьим либо наследником
  • В структуре метаданных модуля, дублируем зависимости, которые объявим в самом листинге

  • схемы при создании необходимо выбирать по заголовкам (давняя боль), облегчаю задачу:
    LookupPage - Выбор из справочника ( NUI )
    LookupPageCSS - Стили модуля справочника ( NUI )
    LookupPageViewGenerator, LookupUtilities, MaskHelper - имеют такие же заголовки как и их имена

define("MyCustomLookupWindow", ["LookupPage", "LookupPageViewGenerator", "LookupUtilities", "css!LookupPageCSS"],
	function(LookupPage, LookupPageViewGenerator) {
		return Ext.define("Terrasoft.configuration.MyCustomLookupWindow", {
			alternateClassName: "Terrasoft.MyCustomLookupWindow",
			extend: "Terrasoft.LookupPage",
			gridWrapClasses: ["km-gms-lookup-control"]
		});
	});

2) Для вызова lookup-наследника используйте в вашей логике this.openLookup метод (присутствует в контексте любой карточки/схемы/детали), если же случилось так что его нет - то см.выше - надо будет подключить LookupUtilitesV2 в зависимости схемы, и использовать метод Open предоставляемый объектом зависимости. (Примеры использования см.в исходниках и в начале темы)

//Подготовим минимальный конфигурационный объект
var config = {
        //Для примера справочник "нацелен" на раздел "Лиды"
        entitySchemaName: "Lead",
        columns: ["Id"],
        multiSelect: true,
        actionsButtonVisible: true,
        //Вот сюда передаем имя модуля-наследника
        lookupPageName: "MyCustomLookupWindow"
};
//пример вызова
this..openLookup(
         //наш конфигурационный объект
         config, 
         //callback принимающий результат/результаты выбора в виде коллекции 
         function(selected){console.log(selected)},
         //контекст для коллбэка
         this
)

3) Для того чтобы закастомить кнопки необходимо в нашем наследнике переопределить метод renderLookupView
в который произвести инъекцию своей логики

define("MyCustomLookupWindow", ["LookupPage", "LookupPageViewGenerator", "LookupUtilities", "css!LookupPageCSS"],
	function(LookupPage, LookupPageViewGenerator) {
		return Ext.define("Terrasoft.configuration.MyCustomLookupWindow", {
			alternateClassName: "Terrasoft.MyCustomLookupWindow",
			extend: "Terrasoft.LookupPage",
			gridWrapClasses: ["km-gms-lookup-control"],
			//Переопределяем метод в котором мы можем управлять сформированной конфигурацией до рендеринга.
			renderLookupView: function(schema, profile) {
				var config = this.getLookupConfig(schema, profile);
				var topPanelConfig = LookupPageViewGenerator.generateFixed(config);
				//----------------------- инъекция логики (начало) ----------------------
				//переменная для хранения ссылки на массив объектов-конфигурайий кнопок	
				var buttonsConfig;
				//Получаем ссылку на аттрибут-массив конфигурационных объектов-кнопок
				//Используем Underscore.some с возможностью прерывания переборы по возврату от предиката "true"
				_.some(topPanelConfig.items, function(target) {
					//выделяем объект группы кнопок (Wrapper) по id контейнера
					if (target.id === "selectionControlsContainerLookupPage") {
						//в нем ищем подчиненные объекты являющиеся массивом
						_.some(target, function(target) {
								//согласно структуры конфигурационного объекта панели
								//"чистым" массивом является только объект с конфигами кнопок
							if (Array.isArray(target)) {
								//сохраняем ссылку на него в переменной для дальнейшего использования
								buttonsConfig = target;
								//Прерываем перебор
								return true;
							}
						});
						//Прерываем перебор
						return true;
					}
				});
				//Создаем новую кнопку, клонируя любую, первой как правило идет кнопка "Отмена"
				var newButton = Terrasoft.deepClone(buttonsConfig[0]);	
				//Используем Underscore.extend для объединения склонированного объекта
				//с анонимным объектом-разницы в котором опишем необходимые изменения
				_.extend(
					newButton,
					{
						caption: "Добавленная кнопка",
						click: {
							bindTo: "AddTender"
						},
						//Внимаение! свойство "tag" должено быть уникальным для каждой кнопки
						tag: "AddTender",
						//меняем стиль
						style: "green"
					}
				);
				//Добавляем конфигурационный объект кнрпки в массив конф.объектов
				buttonsConfig.push(newButton);				
				//Поиск конфигурационного объекта кнопки "Выбрать" в искомом массиве по caption
				_.some(buttonsConfig, function(target) {
					if (target.caption === "Выбрать") {
						//В найденном объекте меняем значение аттрибута caption на "Создать тендеры".
						target.caption = "Переименованная кнопка" ;
						return true;
					}
				});
				//Поиск конфигурационного объекта кнопки "Добавить" в искомом массиве по caption
				_.some(buttonsConfig, function(target) {
					if (target.caption === "Действия") {
						//В найденном объекте меняем значение аттрибута visible на "false"
						//тем самым скрывая кнопку-меню
						target.visible = false ;
						return true;
					}
				});
				//----------------------- инъекция логики (конец) ----------------------
				this.renderLookupControls(config, topPanelConfig);
			}
		});
	});

Вот какое-то такое получится окно

Классно и подробно - хоть в официальный SDK добавляй! :)

Илья,

хотел бы уточнить, если правильно понял, то , в результате выполнения данных действий, окно справочника поменяется вообще во всей системе?

Севостьянов Илья Сергеевич, спасибо за подробность, как раз искал подобный кейс

Севостьянов Илья Сергеевич,

Скажите пожалуйста а где вызвать метод 

this.openLookup, я никак не могу реализовать пример. 

Показать все комментарии