Smart Lookup Feature или Быстрый выбор значений в полях типа Справочник

Свершилось!
Данный материал посвящен всем, кто достаточно много времени проводит в работе с системой, и нацелен на максимально быструю и удобную работу с продуктом.
В данном материале речь пойдет о значениях в полях типа Справочник. Точнее, об удобном выборе значений в этих полях. Не секрет, что в процессе работы с системой часто бывает так, что Вам приходится выбирать в поле, например, Ответственный, часто повторяющиеся значения. Таких часто выбираемых значений обычно несколько, но каждый раз нажимать на значок лупы и выбирать нужное в окне выбора, наверное, не совсем оптимально. Особенно, если учесть, что выбирать приходится из большого списка, с использованием поиска и фильтров. Особенно часто это встречается при работе с Задачами и Запросами на изменение (но в общем случае это зависит от специфики работы предприятия).
Предлагаемая функциональность позволяет реализовать следующее: практически в любом LookupDataControl'е любой карточки редактирования системы рядом со значком лупы появится новая кнопка, по нажатию на которую (первый клик мышью) всплывает меню со списком недавно введенных значений в поле:

Выбрав мышью нужный пункт (второй клик) пользователь может осуществить ввод указанного значения в поле.
В итоге пользователь экономит как минимум 1 клик мышью, а также существенно ускоряется процесс заполнения поля и производительность системы - нет дополнительных запросов, порожденных окном выбора, нет самих окон выбора. В итоге получаем значительно более удобную работу с карточкой и экономию времени на рутинных, часто повторяющихся операциях (заполнение расписания, работа с запросами на изменение и т.д.).
Удобство от использования данной возможности уже оценили пользователи внутренней рабочей версии компании Террасофт.

Как же это работает и что нужно сделать, чтобы включить в своем проекте данную функцию? Обо всем по порядку.
Начнем с подходящей версии приложения. Данный механизм будет работать на версиях начиная с 3.2.0.х. Это связано с появившейся в 3.2.0 возможностью отображения рядом с большинством DataControl'ов набора кнопок с меню. Итак, по шагам.
1. Берем файл scr_SmartLookupUtilsForX25.rar, распаковываем, загружаем в TSAdmin файл scr_SmartLookupUtils.xml. Это основной скрипт с функциями Smart Lookup Feature.

2. В скрипте scr_BaseDBEditUtils добавляем скрипт scr_SmartLookupUtils в список Use Scripts.

3. В скрипт scr_BaseDBEdit добавляем следующие строки:

function SmartLookupOnDataChange(DataField) {
    ProcessSmartLookupOnDataChange(DataField, Self);
}

function SmartLookupSetValueOnExecute(ActionMenuItem) {
    SetSmartLookupValue(ActionMenuItem);
}

function wnd_BaseDBEditOnProfileDeserialize(Window, Node) {
    ReadSmartLookupDataFromProfile(Window, Node);
}

function wnd_BaseDBEditOnProfileSerialize(Window, Node) {
    ProcessSaveSmartLookupValuesToProfile(Window, Node);
}

function wnd_BaseDBEditOnShow(Window) {
    LoadSmartLookupValues(Window);
}

Это обработчики событий, используемых для работы функций.

4. В окне wnd_BaseDBEdit переключаемся на вкладку События и для событий OnProfileDeserialize, OnProfileSerialize, OnShow двойными щелчками добавляем обработчики событий (сам код обработчиков мы уже добавили в п.3). Сохраняем окно, открываем заново и убеждаемся, что все указанные обработчики успешно сохранены (это важно, и шансы здесь ошибиться есть).

5. Вносим изменения в скрипт scr_Utils. Добавляем новую функцию:

function GetIndexOfItemInArray(SearchValue, SearchArray) {
    for (var i = 0; i < SearchArray.length; i++) {
        if (SearchValue == SearchArray[i]) {
            return i;
        }
    }
    return -1;
}

Можно было бы предложить полностью скрипт scr_Utils, но есть большой риск, что в Ваших проектах в этом скрипте есть нужные Вам функции, и проще просто добавить в него новую, чем заниматься слиянием текста двух скриптов и поиском измененных и добавленных участков кода. Это особенно актуально, если Вы захотите добавить Smart Lookup Feature в проект версии ниже 3.3.0 (разработка велась именно на этой версии). По этой же причине пункты 2, 3, 4, 6 и 7 выглядят именно таким образом, а не предлагаются в виде готовых сервисов.

6. Теперь включаем данную функцию, например, в окне Задач. Для этого открываем окно wnd_TaskEdit, устанавливаем UseProfile = True, сохраняем окно.

7. Если бы в окне (в нашем случае wnd_TaskEdit) не было обработчика события OnDatasetDataChange для компонента dlData, на этом изменения и закончились бы (функции в состоянии подписаться на необходимые события самостоятельно, без участия разработчика). Однако, если данное событие обрабатывается (в нашем случае в скрипте scr_TaskEdit), придется внести небольшие коррективы в обработчик:

Последней строчкой в функции dlDataOnDatasetDataChange(DataField)  скрипта scr_TaskEdit необходимо вставить строку:

    ...
    SmartLookupOnDataChange(DataField);
}

8. Запускаем приложение. Открываем карточку задачи. Изменяем значение в поле, например, Ответственный. Закрываем карточку задачи. Закрываем приложение. Все это, видимо, нужно для корректной инициализации профиля карточки редактирования. Необходимость этого шага выявлена в ходе испытаний, но все еще под сомнением.

9. Запускаем приложение. Открываем карточку задачи. Изменяем значение в поле, например, Ответственный. Наблюдаем следующую картину (sorry за англоязычный скриншот, экспериментировал с применением функции на версиях, которые были под рукой):

На данный момент скрипты реализованы таким образом, что позволяют включить данную возможность в указанном списке окон редактирования для указанного списка элементов управления типа LookupDataControl. При желании пункты 6-9 можно проделать с другими окнами редактирования. В скрипте scr_SmartLookupUtils в массивах SmartLookupEnabledWindowsArray и SmartLookupEnabledControlNamesArray можно настроить перечень кодов доступных окон и имен компонентов в этих окнах соответственно. Либо поступить более рискованно и радикально, и переписать функции ReadSmartLookupDataFromProfile и ProcessSaveSmartLookupValuesToProfile таким образом, чтобы они работали с любым окном и любым LookupDataControl'ом. В первых альфа-версиях скрипта так и было, но решили все-таки ограничиться фиксированным списком окон и элементов управления.

Пару слов о том, как это работает. Необходимые списки значений сохраняются в профиле окна. Построение списков происходит при показе окна. При необходимости чтение из профиля выполняется принудительно (это актуально для окон, поднятых из программного кэша). Подписка на событие OnDatasetDataChange датасетлинка окна редактирования (или ручной вызов обработчика, как в примере с Задачами) нужна для анализа изменений значения в поле и пополнения списка. Список пополняется сверху вниз (свежие значения вверху, чтобы пользователю было ближе к ним мышь двигать). Первые разы, пока список не заполнен, все-таки придется использовать лупу для выбора значений, затем эти значения будут доступны в списке Smart Lookup. Сохранение списков производится в момент закрытия окна. Все необходимые компоненты (ActionMenu, ActionMenuItem) создаются динамически в режиме выполнения. Максимальное количество элементов в списке равно 7.

Надеюсь, вы найдете Smart Lookup Feature удобным и полезным механизмом.
Также надеюсь, что этот механизм будет включен в базовую версию продукта версии 3.3.1, по крайней мере все для этого готово.
Повторюсь, список поддерживаемых окон и компонентов можно очень легко расширить. Код специально написан универсальным образом, чтобы не зависеть от специфики конкретного окна редактирования.
Отзывы и комментарии приветствуются.

Нравится

Поделиться

21 комментарий

Дима, спасибо за отличную функциональность.

Спасибо!

--
Cogito, ergo sum

Просто и со вкусом, спасибо!
Только насчет 3.3.1, может этот функционал лучше в ядре реализовать. А в LookupDataControl и LookupControl добавить признаки вроде UseSmartLookup и SmartLookupCount.

"Underscore a.k.a. _" написал:Только насчет 3.3.1, может этот функционал лучше в ядре реализовать.

Не стоит этого делать. Создание функциональности в конфигурации добавляет гибкости решению.
Дима, удобная штука! Спасибо!

А в ядре, я так понимаю, скорости :) Ну, нет так нет.

Насчет скорости замечание справедливое. Замеры производительности показали, что данная функция при первом показе окна потребляет порядка 100-150 мс на чтение данных из профиля и построение списка пунктов меню. При поднятии карточки из программного кэша (т.е. при повторном открытии карточки) функция потребляет порядка 15 мс, т.е. практически мгновенно. При сохранении данных в кэш (которое выполняется, кстати, при каждом закрытии карточки), функция потребляет до 100 мс.

Дима, в целом, БОМБА!

"Underscore a.k.a. _" написал:Только насчет 3.3.1, может этот функционал лучше в ядре реализовать. А в LookupDataControl и LookupControl добавить признаки вроде UseSmartLookup и SmartLookupCount.

Может лучше SaveLookupHistory и LookupHistoryCount?

Ну названия я особо не выдумывал :) Второй вариант мне нравится, а вот в первом слово Save не смотрится. Он же не только Save но и Load потом :) Лучше Store.

Дмитрий, просьба прокомментировать дополнительно, в каких ситуациях может при внесении новой записи в список подставляется вместо нужного DisplayValue значение по-умолчанию:

   var DisplayValue = DataField.DisplayValue;
   if (DisplayValue == '') {
	var Dataset = LookupDataControl.DatasetLink.Dataset;
	// If it is DataChange during setting default       
        //values, then DisplayValues are empty
	// so we need to show something like default caption
	if (!Dataset.IsGettingDisplayValuesEnabled)	{
  	   DisplayValue = SmartLookupDefaultDisplayValue;
	}
   }

При заполнении поля "Инцидент" в задаче у менеджеров часто попадают в список именно "<Значение по умолчанию>", но происходит это далеко не всегда.
Комментарий есть ("If it is DataChange during setting default values, then DisplayValues are empty...") но хотелось бы лучше понять какие действия на практике приводят к смене атрибута IsGettingDisplayValuesEnabled для датасета

Добрый день, Александр!
Спасибо за Ваше замечание.
Базовая логика работы с карточкой такова, что перед установкой ряда значений по умолчанию у датасета выполняется метод DisableGettingDisplayValues(). Это нужно для того, чтобы быстрее выполнить присвоения в поля типа Справочник (устанавливаются только ID), а затем методом EnableGettingDisplayValues() выполнить чтение всех отображаемых значений в этих полях. Это чтение выполняется ядром, причем одним запросом. Разработчики продукта справедливо посчитали, что такой способ ускорит установку значений по умолчанию.
При использовании SmartLookup в этом случае отображаемого значения у DataField'а просто нет, и поэтому приходится выводить некоторый стандартный заголовок "Значение по умолчанию".

В итоге решили отказаться от использования SLF при установке значений по умолчанию вообще:

	var DisplayValue = DataField.DisplayValue;
	if (DisplayValue == '') {
		return;
		/* It is decided to turn off storing of SmartLookupValues during setting of default values
		var Dataset = LookupDataControl.DatasetLink.Dataset;
		// If it is DataChange during setting default values, then DisplayValues are empty
		// so we need to show something like default caption
		if (!Dataset.IsGettingDisplayValuesEnabled)	{
			DisplayValue = SmartLookupDefaultDisplayValue;
		}
		*/
	}

Дмитрий, спасибо за ответ!
Попробуем поступить аналогично))
Либо, я как понимаю, можно просто другим способом вытаскивать необходимое значение, ведь ID выбранной записи есть в любом случае (если не пустое, конечно), но это скажется на быстродействии.

Добрый день, товарищи.
Была сделана доработка (с согласия уважаемого автора) SmartLookup, сутью которой является поддержка данного механизма не только для LookupDataControl, но и для обычных LookupControl. Принцип работы механизма остался тот же. Инструкция по установке изменилась:

1. Берем файл scr_SmartLookupUtils.rar, распаковываем, загружаем в TSAdmin файл scr_SmartLookupUtils.xml. Это основной скрипт с функциями Smart Lookup Feature.

2. Если LookupControl или LookupDataControl находится в обычной карточке редактирования (основанной, на шаблоне wnd_BaseDBEdit):
2.1. В скрипте scr_BaseDBEditUtils добавляем скрипт scr_SmartLookupUtils в список Use Scripts.
2.2. В скрипт scr_BaseDBEdit добавляем следующие строки:

function SmartLookupOnDataChange(DataField) {
    ProcessSmartLookupOnDataChange(DataField, Self);
}
 
function SmartLookupDataSetValueOnExecute(ActionMenuItem) {
    SetSmartLookupDataValue(ActionMenuItem);
}
 
function SmartLookupOnChange(LookupControl) {
    ProcessSmartLookupOnChange(LookupControl);
}
 
function SmartLookupSetValueOnExecute(ActionMenuItem) {
    SetSmartLookupValue(ActionMenuItem);
}
 
function wnd_BaseDBEditOnProfileDeserialize(Window, Node) {
    ReadSmartLookupDataFromProfile(Window, Node);
}
 
function wnd_BaseDBEditOnProfileSerialize(Window, Node) {
    ProcessSaveSmartLookupValuesToProfile(Window, Node);
}
 
function wnd_BaseDBEditOnShow(Window) {
    LoadSmartLookupValues(Window);
}

Это обработчики событий, используемых для работы функций.
2.3. В окне wnd_BaseDBEdit переключаемся на вкладку События и для событий OnProfileDeserialize, OnProfileSerialize, OnShow двойными щелчками добавляем обработчики событий (сам код обработчиков мы уже добавили в п.2.2). Сохраняем окно, открываем заново и убеждаемся, что все указанные обработчики успешно сохранены (это важно, и шансы здесь ошибиться есть).

3. Если LookupControl или LookupDataControl находится в окне, которое не основано на шаблоне wnd_BaseDBEdit:
3.1. В скрипт окна добавляем скрипт scr_SmartLookupUtils в список Use Scripts.
3.2. В скрипт окна добавляем следующие обработчики функции:

function SmartLookupOnDataChange(DataField) {
    ProcessSmartLookupOnDataChange(DataField, Self);
}
 
function SmartLookupDataSetValueOnExecute(ActionMenuItem) {
    SetSmartLookupDataValue(ActionMenuItem);
}
 
function SmartLookupOnChange(LookupControl) {
    ProcessSmartLookupOnChange(LookupControl);
}
 
function SmartLookupSetValueOnExecute(ActionMenuItem) {
    SetSmartLookupValue(ActionMenuItem);
}

3.3. В скрипт окна добавляем обработчики следующих событий:

OnProfileDeserialize:

function <Название_окна>OnProfileDeserialize(Window, Node) {
    ReadSmartLookupDataFromProfile(Window, Node);
}

OnProfileSerialize:

function <Название_окна>OnProfileSerialize(Window, Node) {
    ProcessSaveSmartLookupValuesToProfile(Window, Node);
}

OnShow:

function <Название_окна>OnShow(Window) {
    LoadSmartLookupValues(Window);
}

где <Название_окна> - это название окна, которое содержит LookupControl.

4. Вносим изменения в скрипт scr_Utils. Добавляем новую функцию:

function GetIndexOfItemInArray(SearchValue, SearchArray) {
    for (var i = 0; i < SearchArray.length; i++) {
        if (SearchValue == SearchArray[i]) {
            return i;
        }
    }
    return -1;
}

Можно было бы предложить полностью скрипт scr_Utils, но есть большой риск, что в Ваших проектах в этом скрипте есть нужные Вам функции, и проще просто добавить в него новую, чем заниматься слиянием текста двух скриптов и поиском измененных и добавленных участков кода. Это особенно актуально, если Вы захотите добавить Smart Lookup Feature в проект версии ниже 3.3.0 (разработка велась именно на этой версии). По этой же причине пункт 2 выглядит именно таким образом, а не предлагаются в виде готовых сервисов.

5. Теперь включаем данную функцию, например, в окне Задач. Для этого открываем окно wnd_TaskEdit, устанавливаем UseProfile = True, сохраняем окно.

6. Если бы в окне (в нашем случае wnd_TaskEdit) не было обработчика события OnDatasetDataChange для компонента dlData, на этом изменения и закончились бы (функции в состоянии подписаться на необходимые события самостоятельно, без участия разработчика). Однако, если данное событие обрабатывается (в нашем случае в скрипте scr_TaskEdit), придется внести небольшие коррективы в обработчик:
Последней строчкой в функции dlDataOnDatasetDataChange(DataField) скрипта scr_TaskEdit необходимо вставить строку:

    ...
    SmartLookupOnDataChange(DataField);
}

Если в окне уже есть свой обработчик события OnChange для компонента LookupControl, то необходимо в этот обработчик вставить код:

    ...
    SmartLookupOnChange(LookupControl);
}

7. Находим сервис коллекции иконок il_ControlWarnings. Если в нем отсутствует иконка с кодом Wizard, то создаем элемент с таким именем, загружаем иконку из архива п. 1 и сохраняем сервис.

8. В скрипте scr_SmartLookupUtils в массив SmartLookupEnabledWindowsArray добавляем имена окон, в которых хотим иметь Lookup и LookupData контролы с поддержкой SmartLookup. В массив SmartLookupEnabledControlNamesArray добавляем соответствующие имена контролов. Например, если необходимо добавить поддержку SmartLookup для полей Основной контакт и Ответственный для карточки контрагента и Контакт и Контрагент для карточки задачи, необходимо заполнить массивы следующим образом:

var SmartLookupEnabledWindowsArray = 	['wnd_AccountEdit','wnd_TaskEdit'];
 
var SmartLookupEnabledControlNamesArray =
	// wnd_AccountEdit controls
	[['edtPrimaryContact', 'edtOwner'], 
	 // wnd_TaskEdit controls
	['edtContact', 'edtOwner']]

Артем, спасибо за ценное дополнение! Теперь в ряде мест в системе можно существенно повысить удобство и скорость работы. Это оценят те пользователи, для которых приложение на платформе Terrasoft - основной рабочий инструмент.

Спешу обрадовать всех поклонников данной ветки - готова версия Smart Lookup Feature v2.1!

Добавлено:

- Реализован пункт "Настройка..." в выпадающем меню, теперь список запоминаемых значений можно редактировать и сортировать. Это удобно, если запоминаемых значений достаточно много, и среди них есть уже неиспользуемые.

Исправлено:

- Устранена ошибка, приводящая к некорректному сохранению порядка запоминаемых значений

Инструкция по установке немного изменена и находится внутри приложенного архива.

Буду рад замечаниям и пожеланиям. Желаю приятного использования!

Спасибо, Дима! Отчизна тебе благодарна!

Дима, спасибо, многие уже оценили!

Добавлю, что данная функциональность уже включена в следующую версию 3.4.0!

Здравствуйте!
Наши клиенты высказали пожелание в реализации подобного функционала. Начала действовать по инструкции и поняла, что в этой (3.3.2.193) версии уже все как будто есть. То есть никаких изменений вносить не нужно.
Но при этом у нужного поля "волшебной палочки" не наблюдается. Что может быть не так?

Единственное, что меня смущает - это "Если в окне уже есть свой обработчик события OnChange для компонента LookupControl, то необходимо в этот обработчик вставить код:". Не совсем понимаю как это проверить, поскольку не вижу у LookUp'а события "OnChange".

_____
Проблема решена! Оказывается, волшебный значок появляется только после того, как будет выбрано хотя бы одно значение! :)

Дмитрий, обновите, пожалуйста, шапку актуальной версией SmartLookup.
Когда ваша паства может рассчитывать на новые версии?

божественная идея и реализация!
Спасибо !

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