Собственная начальная страница с избранным, историей и сообщениями
Сначала была мысль. И мысль была о том, что пустая начальная страница — это плохо. В разные периоды жизни программы появлялись разные бизнес-процессы, ни один из которых так и не прижился. Различные РМК в нашей сфере деятельности не требуются, а рабочие места сборщиков нужны только 5% пользователей программы. В итоге, как природа не терпит пустоты, так и я не смог дальше мириться с целой вкладкой нефункционального ничего.
Было принято решение вывести то, что может пригодиться каждому, а любому пользователю самому виднее, что ему нужно! Поэтому, будем показывать его же избранное, но этого будет недостаточно. Начальная страница большая, а избранного у пользователя может быть 5-6 отчётов и любимое списание с кассы себе же на премию. Кстати, если пользователь никогда не обращал внимания на звёздочки, то мы заботливо нарисуем ему инструкцию.
Вспоминаем о том, сколько было слёз у сотрудников, когда они только что вышли из документа и не помнят его номер. Или же не приведи Господь, мы поменяли год, и номер тоже поменялся. Документ теперь утерян навеки и надо его искать всем отделом по обрывкам воспоминаний о комментарии и сумме «точно больше тысячи, но меньше ста». Все пользователи знают о волшебной кнопочке с часиками, но никто не вспоминает о ней без звонка в ИТ отдел, а туда звонить страшно! Значит, выведем историю последних действий пользователя.
И если уж про кнопку с часиками у бухгалтера есть шанс вспомнить, то о том, что сообщения с «колокольчика» можно пересмотреть в специальной обработочке где-то в сервисе, в какой-то там подсистеме, уже нет никакой надежды. А значит последние наши сообщения:
- контрагент попал в новый сегмент;
- прошла переоценка интересного нам контракта;
- КТО-ТО поменял дату документа назад в будущее;
выведем сюда же.
План намечен, но забегая вперёд, сперва покажу итоговый результат, а затем будем разбираться, как и что в этом результате работает.
ИЗБРАННОЕ
Начнём по порядку, а именно с "Избранного". Задачу делим на "получить избранное" и закономерно это "избранное отобразить". Как и многое в нашей жизни, избранное хранится в хранилище системных настроек, по ключу Общее/ИзбранноеРаботыПользователя. Что легко найти на сайте ИТС, просто поискав по слову "Избранное", там же полезно будет почитать, что еще можно найти в хранилище системных настроек.
Избранное = ХранилищеСистемныхНастроек.Загрузить("Общее/ИзбранноеРаботыПользователя",,, ПользовательИБ.Наименование);
ПользовательИБ – само собой, это текущий пользователь с параметров сеанса, но по коду и в самой обработке я оставил себе возможность подглядеть в чужое избранное и сообщения. Вы же можете не указывать четвёртый параметр вовсе. Сделал я так, чтобы упростить себе жизнь в общении с пользователями об этом рабочем месте. Смотреть чужую историю возможности нет, а жаль. Опять же, для того, чтобы узнать «кто», «где» и «когда» у нас есть отдельный регистр.
В самом избранном хранятся элементы типа "ИзбранноеРаботыПользователя", в нём же есть поля "Важное" (иконка пина, которая фиксирует элемент вверху списка), "НавигационнаяСсылка" и "Представление". Казалось бы, это всё что нам нужно, чтобы показать пользователю ссылку и куда его отправить по клику. НО, если пользователь представление своего избранного не поменял вручную, а в избранном это сделать можно, то там будет пусто. Лично у меня ровно 0 пользователей из 20 просмотренных делали представления своему избранному. Да что лукавить, и я не пользовался представлениями в "Избранном". И все же если показать пользователю вот это e1cib/data/Справочник.ВариантыОтчетов?ref=80c71866dab567cd11e7e5f3c7a20af7, он точно сюда не кликнет, только если не доказать ему лично,что именно этот текст его любимый отчёт. Нам точно нужно показывать надпись на языке бухгалтера, а не на языке навигационных ссылок.
Значит вспоминаем про ПолучитьПредставленияНавигационныхСсылок(), которая принимает массив ссылок и возвращает массив их представлений вместе с изначальным URI.
Получаем следующий код:
Избранное = ХранилищеСистемныхНастроек.Загрузить("Общее/ИзбранноеРаботыПользователя",,, ПользовательИБ);
Если ЗначениеЗаполнено(Избранное) Тогда
СсылкиИзбранное = Новый Массив;
Для каждого СтрИзбранное Из Избранное Цикл
СсылкиИзбранное.Добавить(СтрИзбранное.НавигационнаяСсылка);
КонецЦикла;
ПредставленияНавигационныхСсылок = ПолучитьПредставленияНавигационныхСсылок(СсылкиИзбранное);
КонецЕсли;
Вторая часть задачи — это вывести полученное на экран. Мы же заранее не знаем, сколько у человека избранного, а значит накидать надписей, а затем поменять им заголовки не можем. Очень плохое решение — это сделать 100 надписей, а лишние сделать невидимыми. Но плохому мы сегодня постараемся не учиться, будем динамически рисовать элементы формы и привязывать к ним обработчики событий.
Чтобы нарисовать элемент формы в виде ссылки нам потребуется следующий код:
Идентификатор = "Ссылка_" + СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(Идентификатор, Тип("ДекорацияФормы"), Элементы.ГруппаИзбранное);
НовыйЭлемент.Вид = ВидДекорацииФормы.Надпись;
НовыйЭлемент.Заголовок = СтрПредставлениеНавигационнойСсылки.Текст;
НовыйЭлемент.Гиперссылка = Истина;
Тут Идентификатор нам нужен не только для уникальности, но и для того, чтобы связать элемент и его навигационную ссылку. Конечно, можно было бы прямо в идентификатор добавить навигационную ссылку, но пришлось бы экранировать символы, которые нельзя использовать в наименовании. А потом еще и придётся парсить это в обратную сторону, чтобы оживить ссылку и по ней можно было перейти. Калории на такое тратить нам жаль, поэтому нас спасёт соответствие!
НавигационныеСсылкиСервер = Новый Соответствие;
А после рисования элемента добавим
НавигационныеСсылкиСервер.Вставить(Идентификатор, СтрПредставлениеНавигационнойСсылки.НавигационнаяСсылка);
Чтобы потом с клиента обращаться к этому соответствию, добавим реквизит формы "НавигационныеСсылкиСервер" с произвольным типом. И... Получаем ошибку! Мы забыли, что нельзя на клиенте хранить соответствие, но мы то хитрые и помним, что фиксированное то можно.
Меняем на форме имя соответствия на "НавигационныеСсылки", а в конце кода добавляем:
НавигационныеСсылки = Новый ФиксированноеСоответствие(НавигационныеСсылкиСервер);
Ошибок нет, осталось начать это использовать.
Добавляем на клиент обработчик события, где мы получаем ссылку из соответствия по имени элемента и просто переходим по ней.
&НаКлиенте
Процедура ПерейтиПоСсылке(Элемент, СтандартнаяОбработка)
НавигационнаяСсылка = НавигационныеСсылки.Получить(Элемент.Имя);
Если ЗначениеЗаполнено(НавигационнаяСсылка) Тогда
ПерейтиПоНавигационнойСсылке(НавигационнаяСсылка);
КонецЕсли;
КонецПроцедуры
А в код рисования формы добавляем строчку:
НовыйЭлемент.УстановитьДействие("Нажатие", "ПерейтиПоСсылке");
Таким образом, у нас каждый элемент, по своему имени, сможет отправить пользователя по своей ссылочке. УРА! Мы и получили и отобразили избранное, еще и пользователь может перейти по ссылке!
Итоговый код получается следующим:
Избранное = ХранилищеСистемныхНастроек.Загрузить("Общее/ИзбранноеРаботыПользователя",,, ПользовательИБ);
Если ЗначениеЗаполнено(Избранное) Тогда
СсылкиИзбранное = Новый Массив;
Для каждого СтрИзбранное Из Избранное Цикл
СсылкиИзбранное.Добавить(СтрИзбранное.НавигационнаяСсылка);
КонецЦикла;
ПредставленияНавигационныхСсылок = ПолучитьПредставленияНавигационныхСсылок(СсылкиИзбранное);
Для каждого СтрПредставлениеНавигационнойСсылки Из ПредставленияНавигационныхСсылок Цикл
Идентификатор = "Ссылка_" + СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(Идентификатор, Тип("ДекорацияФормы"), Элементы.ГруппаИзбранное);
НовыйЭлемент.Вид = ВидДекорацииФормы.Надпись;
НовыйЭлемент.Заголовок = СтрПредставлениеНавигационнойСсылки.Текст;
НовыйЭлемент.Гиперссылка = Истина;
НовыйЭлемент.УстановитьДействие("Нажатие", "ПерейтиПоСсылке");
НавигационныеСсылкиСервер.Вставить(Идентификатор, СтрПредставлениеНавигационнойСсылки.НавигационнаяСсылка);
КонецЦикла;
КонецЕсли;
ВПЕРЁД ЗА ИСТОРИЕЙ ПОЛЬЗОВАТЕЛЯ!
И меня, как программиста, предал мой самый верный помощник. Гугл был явно настроен против меня, добиться от него чего-то кроме истории изменений документа как платформенной, так и средствами БСП, было очень мучительно. Но после долгих увещеваний "поисковая система номер один" сжалилась и показала мне журнал регистрации. На использование журнала я был готов также, как и на использование истории изменений объекта. Искать то надо последние действия конкретного пользователя и искать хотелось бы быстро, а ЖР вообще не про это. Тем более, если пользователь просто заглянет, не записывая, в документ, то и записей не будет ни в «тут», ни в «там». История открываемых списков, обработок и отчётов тем более там не появится.
Но не хочется верить в невозможность этой затеи , потому что история показывается по системной кнопочке с часиками. Значит, как-то можно её откопать, я уже готов был копать и разбирать файлы пользовательского кэша, нужно было только направление. Однако, синтаксис-помощник оказался вернее западных технологий. В нём я по ключевым словам довольно быстро нашёл менеджер "ИсторияРаботыПользователя". Что лишний раз доказывает, что всё-таки лучше сначала искать в синтаксис-помощнике и только потом в сети.
Кстати, там же можно программно Добавить(URI) любую навигационную ссылку в историю пользователю, что даёт простор воображению, но нас пока это не интересует.
История = ИсторияРаботыПользователя.Получить();
В ответ функция возвращает массив навигационных ссылок с датами. Радостно получаем представления и выводим тем же кодом что и «Избранное». В принципе, оно работает, но с двумя НО:
- Мы потеряли дату-время события, что не очень хорошо
- При получении представлений они еще и яростно перемешались, что совсем не хорошо
В отличие от избранного нам важно сохранить изначальный порядок и дополнительную информацию по каждой ссылке от менеджера, а значит слегка меняем код:
История = ИсторияРаботыПользователя.Получить();
Если ЗначениеЗаполнено(История) Тогда
СсылкиИстория = Новый Массив;
Для каждого СтрИстория Из История Цикл
СсылкиИстория.Добавить(СтрИстория.НавигационнаяСсылка);
КонецЦикла;
ПредставленияНавигационныхСсылок = ПолучитьПредставленияНавигационныхСсылок(СсылкиИстория);
Для каждого СтрИстория Из История Цикл
Для каждого СтрПредставлениеНавигационнойСсылки Из ПредставленияНавигационныхСсылок Цикл
Если СтрИстория.НавигационнаяСсылка = СтрПредставлениеНавигационнойСсылки.НавигационнаяСсылка Тогда
Идентификатор = "Ссылка_" + СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(Идентификатор, Тип("ДекорацияФормы"), Элементы.ГруппаИстория);
НовыйЭлемент.Вид = ВидДекорацииФормы.Надпись;
НовыйЭлемент.Заголовок = "" + Формат(СтрИстория.Дата, "ДФ='dd.MM HH:mm'; ДЛФ=DT") + " " + СтрПредставлениеНавигационнойСсылки.Текст;
НовыйЭлемент.Гиперссылка = Истина;
НовыйЭлемент.УстановитьДействие("Нажатие", "ПерейтиПоСсылке");
НавигационныеСсылкиСервер.Вставить(Идентификатор, СтрПредставлениеНавигационнойСсылки.НавигационнаяСсылка);
Прервать;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЕсли;
Получилось более громоздко чем для "Избранного", но результат выводится красиво и по порядку.
СООБЩЕНИЯ
Остались лишь "Сообщения" и тут уже начинаются особенности нашей конфигурации. Для вывода «колокольчика» мы используем периодический регистр сведений СообщенияПользователям. В него мы закидываем сообщения и отправляем пользователю через клиентские обработчики оповещения, но сегодня не об этом. Сегодня о том, как это сообщение показать в нашей формочке.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| СообщенияПользователям.ТекстСообщения КАК ТекстСообщения,
| СообщенияПользователям.ОбъектЦель КАК ОбъектЦель,
| СообщенияПользователям.Период КАК Период,
| СообщенияПользователям.Пояснение КАК Пояснение
|ИЗ
| РегистрСведений.СообщенияПользователям КАК СообщенияПользователям
|ГДЕ
| СообщенияПользователям.Пользователь = &Пользователь
|
|УПОРЯДОЧИТЬ ПО
| СообщенияПользователям.Период УБЫВ";
Запрос.УстановитьПараметр("Пользователь", ?(ЗначениеЗаполнено(Пользователь), Пользователь, ПараметрыСеанса.ТекущийПользователь));
АГА! Периодический регистр сведений, а срез последних не используется, вот тут ты и попался, Носырев!
Согласен, так делать плохо, спорить не буду, просто покажу замер:
Видно, что со срезом всё равно было медленнее, даже если не поверить, что данные кэшировались после первого запроса. Как я понимаю, так вышло из-за количества записей в регистре и сортировки по убыванию заведомо индексируемого поля, в таких случаях построитель запроса может понять вас совсем не так, как бы вам хотелось. Лучше явно указать ему искать сразу по записям. Буду рад более научным объяснениям в комментариях. Опять же, если у вас есть подобные регистры сообщений, журналов событий и тому подобных, можете поэкспериментировать с ними.
Таким образом получаем следующий код:
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 100
| СообщенияПользователям.ТекстСообщения КАК ТекстСообщения,
| СообщенияПользователям.ОбъектЦель КАК ОбъектЦель,
| СообщенияПользователям.Период КАК Период,
| СообщенияПользователям.Пояснение КАК Пояснение
|ИЗ
| РегистрСведений.СообщенияПользователям КАК СообщенияПользователям
|ГДЕ
| СообщенияПользователям.Пользователь = &Пользователь
|
|УПОРЯДОЧИТЬ ПО
| СообщенияПользователям.Период УБЫВ";
Запрос.УстановитьПараметр("Пользователь", ?(ЗначениеЗаполнено(Пользователь), Пользователь, ПараметрыСеанса.ТекущийПользователь));
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
Идентификатор = "Ссылка_" + СтрЗаменить(Новый УникальныйИдентификатор(), "-", "");
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(Идентификатор, Тип("ДекорацияФормы"), Элементы.ГруппаСообщения);
НовыйЭлемент.Вид = ВидДекорацииФормы.Надпись;
НовыйЭлемент.Заголовок = "" + Формат(ВыборкаДетальныеЗаписи.Период, "ДФ='dd.MM HH:mm'; ДЛФ=DT") + " " + ВыборкаДетальныеЗаписи.ТекстСообщения;
НовыйЭлемент.Гиперссылка = Истина;
НовыйЭлемент.УстановитьДействие("Нажатие", "ПерейтиПоСсылке");
НавигСсылка = "";
Если ЗначениеЗаполнено(ВыборкаДетальныеЗаписи.ОбъектЦель) Тогда
Если ТипЗнч(ВыборкаДетальныеЗаписи.ОбъектЦель) = Тип("Строка") Тогда
НавигСсылка = ВыборкаДетальныеЗаписи.ОбъектЦель;
Иначе
НавигСсылка = ПолучитьНавигационнуюСсылку(ВыборкаДетальныеЗаписи.ОбъектЦель);
КонецЕсли;
КонецЕсли;
НовыйЭлемент = ЭтаФорма.Элементы.Добавить(Идентификатор + "_Сообщение", Тип("ДекорацияФормы"), Элементы.ГруппаСообщения);
НовыйЭлемент.Вид = ВидДекорацииФормы.Надпись;
НовыйЭлемент.Заголовок = "" + ВыборкаДетальныеЗаписи.Пояснение;
НавигационныеСсылкиСервер.Вставить(Идентификатор, НавигСсылка);
КонецЦикла;
Основные отличия здесь это:
- Мы ходим уже по выборке, а не массивам
- В выборке может быть как ссылка на объект, так и сразу навигационная ссылка
- Под заголовком темы сообщения выводим некликабельный текст с пояснением
Итак, с этой формой мы закончили, но пока рисовали сообщения появилась идея дать пользователям возможность отправлять друг другу навигационные ссылки. Делов то – сделать формочку, которая будет делать запись в регистр сведений сообщений.
ОТПРАВКА СООБЩЕНИЙ
Рисуем формочку:
Где сверху видим список пользователей, обязательно с автосохранением и с галочками, чтобы пользователь накинул туда свои топ 10 адресатов и просто переставлял галочки в нужные моменты. УДОБНО ЖЕ! Ссылочку будем забирать из буфера обмена и при открытии, и при нажатии кнопочки. Как "положить ссылочку в буфер обмена" рисуем инструкцию. Если вашим пользователям она не требуется, то я их заведомо люблю и уважаю. Своих тоже и уважаю, и люблю, но им инструкция нужна.
Инструкции, которые были на предыдущей форме, приводить не буду, кому интересно тот сам в них заглянет . Особенно я рад инструкции по панели открытых, всех не обзвонить и не показать, а пользователи сидят и мучаются.
Осталось только посмотреть на то, как это работает. Сперва следует понять, как получить ссылку из буфера обмена.
Мы получаем данные из буфера обмена, создавая COM объект гипертекстового файла. Я видел, что в новых версиях НАКОНЕЦ-ТО можно будет работать с буфером напрямую из 1С, и даже с файлами и изображениями, но я всё еще живу в средних веках , поэтому делаем так. Если можно оптимальней на версиях 8.3 ниже 20ой, очень прошу подскажите.
&НаКлиенте
Процедура ЗаполнитьИзБО(Команда)
ОбъектHTML = Новый COMОбъект("htmlfile");
ДанныеБО = ОбъектHTML.ParentWindow.ClipboardData.Getdata("Text");
ОтправляемаяНавигационнаяСсылка = "";
Сообщение = "";
Если ЗначениеЗаполнено(ДанныеБО) Тогда
Попытка
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(ДанныеБО);
Представление = ПолучитьПредставленияНавигационныхСсылок(МассивСсылок)[0];
Если ЗначениеЗаполнено(Представление.Текст) Тогда
ОтправляемаяНавигационнаяСсылка = ДанныеБО;
Сообщение = "Добрый день, посмотрите, пожалуйста
|" + Представление.Текст;
КонецЕсли;
Исключение
КонецПопытки
КонецЕсли;
КонецПроцедуры
Попытка Исключение здесь на самом деле не нужны. Когда писал код, думал, что при получении представления из невалидной ссылки будет падать ошибка, а она и не падает, просто возвращается пустая строка , поэтому обработчик исключений здесь получается уже какой-то параноидальный. Но убирать всё равно не хочу, потому что ПолучитьПредставленияНавигационныхСсылок — для меня чёрный ящик, а я явно забираю после него из массива нулевой элемент. А вдруг обновим платформу и не будет пустая строка возвращаться, а будет пустой массив. Надо оно мне через пять лет вспоминать, что тут происходило? Вот и вам не надо.
При открытии добавляем:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ЗаполнитьИзБО(Неопределено);
КонецПроцедуры
Ведь это так приятно, когда открываешь форму, а она уже сама заполнилась. Положительный UE получается, а это всегда плюс нам как разработчикам.
Ну и сама отправка это просто запись в регистр сведений:
&НаСервере
Процедура ОтправитьНаСервере()
УстановитьПривилегированныйРежим(Истина);
Для каждого СтрПолучатели Из Получатели Цикл
Если СтрПолучатели.Пометка Тогда
МЗ = РегистрыСведений.СообщенияПользователям.СоздатьМенеджерЗаписи();
МЗ.Период = ТекущаяДата();
МЗ.Пользователь = СтрПолучатели.Значение;
МЗ.ТекстСообщения = "Сообщение от " + ПараметрыСеанса.ТекущийПользователь;
МЗ.Пояснение = Сообщение;
МЗ.ОбъектЦель = ОтправляемаяНавигационнаяСсылка;
МЗ.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура Отправить(Команда)
ОтправитьНаСервере();
ЭтаФорма.Закрыть();
КонецПроцедуры
В итоге мы получаем прекрасную форму с избранным, историей, сообщениями и возможностью отправлять друг другу навигационные ссылки.
Во вложении я оставлю как полную обработку, так и версию с выключенной работой с сообщениями, чтобы была возможность запускать без сообщений, но и без подтачиваний под себя. Оба файла тестировались на версии 1С:Предприятие 8.3 (8.3.15.1656)
Огромное спасибо всем, кто не пожалел своего времени и дочитал до конца!
UPD 22.08.2023
В обработке добавлена возможность открывать чужие пользовательские варианты отчётов отправленные механизмом сообщений. Актуально для БСП ниже третьей версии подробнее в моей статье. ОСТОРОЖНО файл обработки Избранное_IS.epf в этой статье и файл Избранное_ОткрытиеЧужихВариантовОтчета.epf в новой статье одинаковые, если скачали здесь, там качать не стоит и наоборот!