7.7
Технические вопросы
7.x

Наследование стандартной страницы 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) Далее вот вообщем-то пример самого кода
[javascript]
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"]
});
});
[/javascript]
перчим солим, замещаем, дополняем по вкусу и
ну и вызываем его через LookupUtilites или же есть более простой способ через this.openLookup (что само собой является оберткой над вышеуказанным и "сахаром" по сути).
Уже в таком виде - Ваш "отнаследованный" lookupPage можно вызвать следующим образом
[javascript]
var config = {
entitySchemaName: "Lead",
columns: ["Id"],
multiSelect: false,
actionsButtonVisible: false,
lookupPageName: "MyCustomLookupPage"
};
this.openLookup(config, function(result){console.log(result)})
[/javascript]
Вот.

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

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

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

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

Кстати по какой-то причине не удается подключить к карточке страницы LookupUtilities
В конечном итоге в контексте карточки нет объекта "LookupUtilities"
[javascript]
define("MyCustomPage", ["LookupUtilities"],
function(LookupUtilities) {
...
[/javascript]
В конечном итоге в контексте карточки LookupUtilities - undefined

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

Там необходимо использовать V2
[javascript]
define("MyCustomPage", ["LookupUtilitiesV2"],
function(LookupUtilities) {
...
[/javascript]

Победа.

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

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

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

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

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

[javascript]
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"]
});
});
[/javascript]

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

3) Для того чтобы закастомить кнопки необходимо в нашем наследнике переопределить метод renderLookupView
в который произвести инъекцию своей логики
[javascript]
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);
}
});
});
[/javascript]
Вот какое-то такое получится окно

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

Илья,

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

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

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

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

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

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