Введение
С появлением управляемого приложения и веб-клиента некоторые задачи, которые решались в обычном приложением за несколько строчек, существенно усложнились. Казалось бы такая простая и часто используемая операция, как интерактивный выбор файла, которая в обычном приложении решалась в одном методе, теперь требует создания цепочки из нескольких процедур, которые вызываются одна за другой через ОписаниеОповещения. Вот как это было с использованием модальных вызовов:
Процедура КаталогНачалоВыбора(Элемент, СтандартнаяОбработка)
ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
ДиалогВыбораФайла.Каталог = ЭтотОбъект.Каталог;
ДиалогВыбораФайла.Заголовок = "Выберите каталог";
Если НЕ ДиалогВыбораФайла.Выбрать() Тогда
Возврат;
КонецЕсли;
ЭтотОбъект.Каталог = ДиалогВыбораФайла.Каталог;
КонецПроцедуры
Но как эту задачу решить на веб-клиенте? В поисковике не удалось найти готового универсального решения. Поискал в типовых, но там все разбросано по модулям и как-то запутано. Посмотрел пример в консоли СКД с диска ИТС для управляемых форм. Там тоже надо вычленять по кусочкам все шаги. Кроме того там применяется одна фича, которая позволяет открывать диалог выбора файла без установленного расширения работы с файлами. Но в общем случае эта хитрость не подойдет, так как она работает только для существующих файлов, насколько я понял. Поэтому написал процедуры, которые можно просто скопировать и использовать в будущем, не тратя время на то, чтобы вспоминать, как это работает.
Диалог выбора файла
Сразу приведу код, который получился после упрощения.
&НаКлиенте
Процедура КаталогНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
ДиалогВыбораФайла = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);
ДиалогВыбораФайла.Каталог = Объект.Каталог;
ДиалогВыбораФайла.Заголовок = "Выберите каталог";
ПараметрыСценария = Новый Структура;
Оповещение = Новый ОписаниеОповещения("КаталогНачалоВыбораЗавершение", ЭтаФорма);
ПараметрыСценария.Вставить("Оповещение_ПослеЗакрытияДиалогаВыбораФайла", Оповещение);
ПараметрыСценария.Вставить("ДиалогВыбораФайла", ДиалогВыбораФайла);
ВыполнитьСценарийВыбораФайла(, ПараметрыСценария);
КонецПроцедуры
&НаКлиенте
Процедура КаталогНачалоВыбораЗавершение(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат;
КонецЕсли;
Объект.Каталог = Результат[0];
КонецПроцедуры
Я специально привел ранее вариант для обычных форм, чтобы проще было сравнивать. Код разбился на две примитивные процедуры и практически не изменился за счет метода ВыполнитьСценарийВыбораФайла(). Этот метод скрывает все рутинные асинхронные действия:
- подключает расширение по работе с файлами
- если расширение не подключено, то задает пользователю вопрос по его подключению
- начинает установку расширения, если пользователь подтвердит это действие
- открывает диалог выбора файла
- передает результаты выбора файла назад в прикладную процедуру КаталогНачалоВыбораЗавершение()
Далее приведу код этой служебной процедуры, написанной по принципу, из этой статьи.
Исходный код
&НаКлиенте
Процедура ВыполнитьСценарийВыбораФайла(Результат = Неопределено, ПараметрыСценария = Неопределено) Экспорт
НачатьОтсчетШагов(ПараметрыСценария);
Оповещение = Новый ОписаниеОповещения("ВыполнитьСценарийВыбораФайла", ЭтаФорма, ПараметрыСценария);
// подключить расширение работы с файлами
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
НачатьПодключениеРасширенияРаботыСФайлами(Оповещение);
Возврат;
ИначеЕсли ЭтоРезультатВыполненногоШага(ПараметрыСценария) Тогда
// если расширение подключилось, перейти к следующему шагу
Если Результат = Истина Тогда
// если расширение не подключилось, задать вопрос об его установке
ИначеЕсли Результат = Ложь Тогда
ТекстСообщения = "Расширение для работы с файлами не установлено. Установить?";
ПоказатьВопрос(Оповещение, ТекстСообщения, РежимДиалогаВопрос.ДаНет);
Возврат;
// пользователь отказался от установки расширения по работе с файлами
ИначеЕсли Результат <> КодВозвратаДиалога.Да Тогда
Возврат;
// пользователь подтвердил установку расширения по работе с файлами
ИначеЕсли Результат = КодВозвратаДиалога.Да Тогда
НачатьУстановкуРасширенияРаботыСФайлами();
Возврат;
КонецЕсли;
КонецЕсли;
// открыть диалог выбора файла
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
// переадресовываем результат на прикладную процедуру
ПараметрыСценария.ДиалогВыбораФайла.Показать(ПараметрыСценария.Оповещение_ПослеЗакрытияДиалогаВыбораФайла);
Возврат;
КонецЕсли;
КонецПроцедуры
То есть вместо того, чтобы писать несколько процедур с труднопонимаемыми названиями в стиле ЗагрузитьФайлКонсолиПослеПодключенияРасширенияПослеПомещенияФайла(), вся последовательность действий описана в одной. Кроме того, можно заметить, что она универсальная и не содержит зависимостей от контекста формы. То есть ее можно перенести в другую форму простым копированием
Загрузка файлов с клиента на сервер
Постановка задачи: пусть требуется метод, который умеет помещать на сервер выбранный на клиенте файл. Если выбранный файл является каталогом, то необходимо поместить на сервер все файлы, содержащиеся в этом каталоге (подчиненные каталоги игнорировать).
Последовательность асинхронных действий:
- определить является ли файл каталогом
- если не является, то добавить его в массив помещаемых файлов
- если является, то найти все файлы этого каталога
- каждый из найденных файлов проверить является ли он каталогом. Если не является то добавить в массив помещаемых файлов (каждая итерация цикла обхода файлов является асинхронной, потому что вместо синхронного Файл.ЭтоФайл() необходимо использовать его аналог Файл.НачатьПроверкуЭтоФайл())
- поместить файлы на сервер
- вернуть результат помещения в прикладную процедуру, которая будет выполнять обработку файлов по какому-то алгоритму
Данная задача снова решается за три простых шага
&НаКлиенте
Процедура ОбработатьФайлы(Команда)
ПараметрыСценария = Новый Структура;
Оповещение = Новый ОписаниеОповещения("ПослеПомещенияФайлаНаСервер", ЭтаФорма);
ПараметрыСценария.Вставить("Оповещение_ПослеПомещенияФайлаНаСервер", Оповещение);
ВыполнитьСценарийЗагрузкиФайлаНаСервер(Объект.Каталог, ПараметрыСценария);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПомещенияФайлаНаСервер(ПомещенныеФайлы, ДополнительныеПараметры) Экспорт
ОбработатьФайлНаСервере(ПомещенныеФайлы);
КонецПроцедуры
&НаСервере
Процедура ОбработатьФайлНаСервере(ПомещенныеФайлы)
// выполняем обработку результату по какому-нибудь алгоритму
Для Каждого ОписаниеФайла Из ПомещенныеФайлы Цикл
ИмяВременногоФайла = ПолучитьИмяВременногоФайла();
Данные = ПолучитьИзВременногоХранилища(ОписаниеФайла.Хранение);
Данные.Записать(ИмяВременногоФайла);
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать(ИмяВременногоФайла);
Объект.СодержимоеФайла = Текст.ПолучитьТекст();
Прервать;
КонецЦикла;
КонецПроцедуры
Все детали скрыты в процедуре ВыполнитьСценарийЗагрузкиФайлаНаСервер(), которая подходит как для файлов, так и для каталогов. При необходимости ее можно доработать, чтобы она покрывала больше случаев.
Исходный код
&НаКлиенте
Процедура ВыполнитьСценарийЗагрузкиФайлаНаСервер(Результат = Неопределено, ПараметрыСценария = Неопределено) Экспорт
НачатьОтсчетШагов(ПараметрыСценария);
Оповещение = Новый ОписаниеОповещения("ВыполнитьСценарийЗагрузкиФайлаНаСервер", ЭтаФорма, ПараметрыСценария);
// определим что на входе: файл или каталог
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
ПараметрыСценария.Вставить("ИсходныйПомещаемыйФайл", Результат);
Файл = Новый Файл(ПараметрыСценария.ИсходныйПомещаемыйФайл);
Файл.НачатьПроверкуЭтоФайл(Оповещение);
Возврат;
КонецЕсли;
// если передан каталог, то нужно найти все файлы, содеражщиеся в нем
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
Если Результат = Истина Тогда // если это не каталог, то переходим к следующему шагу
ПомещаемыеФайлы = Новый Массив;
ПомещаемыеФайлы.Добавить(Новый Файл(ПараметрыСценария.ИсходныйПомещаемыйФайл));
ВыполнитьОбработкуОповещения(Оповещение, ПомещаемыеФайлы);
Возврат;
Иначе
// выполняем поиск всех файлов, содержащихся в переданном каталоге
НачатьПоискФайлов(Оповещение, ПараметрыСценария.ИсходныйПомещаемыйФайл, "*.?*");
Возврат;
КонецЕсли;
КонецЕсли;
// проверяем, что найденные файлы не являются каталогами
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
ПараметрыСценария.Вставить("НайденныеФайлы", Результат);
ПараметрыСценария.Вставить("ПомещаемыеФайлы", Новый Массив);
ПараметрыСценария.Вставить("ИндексПроверяемогоФайла", 0);
Результат[ПараметрыСценария.ИндексПроверяемогоФайла].НачатьПроверкуЭтоФайл(Оповещение);
Возврат;
// асинхронная проверка каждого из найденных файлов
ИначеЕсли ЭтоРезультатВыполненногоШага(ПараметрыСценария) Тогда
Если Результат = Истина Тогда
Файл = ПараметрыСценария.НайденныеФайлы[ПараметрыСценария.ИндексПроверяемогоФайла];
ПараметрыСценария.ПомещаемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла(Файл.ПолноеИмя, ""));
КонецЕсли;
Если ПараметрыСценария.НайденныеФайлы.Количество() > ПараметрыСценария.ИндексПроверяемогоФайла+1 Тогда
ПараметрыСценария.ИндексПроверяемогоФайла = ПараметрыСценария.ИндексПроверяемогоФайла + 1;
Файл = ПараметрыСценария.НайденныеФайлы[ПараметрыСценария.ИндексПроверяемогоФайла];
Файл.НачатьПроверкуЭтоФайл(Оповещение);
Возврат;
КонецЕсли;
КонецЕсли;
// помещаем файлы на сервер
Если ЭтотШагЕщеНеВыполнялся(ПараметрыСценария) Тогда
ПомещаемыеФайлы = ПараметрыСценария.ПомещаемыеФайлы;
Если ПомещаемыеФайлы.Количество() Тогда
// передаем управление в прикладную процедуру
НачатьПомещениеФайлов(ПараметрыСценария.Оповещение_ПослеПомещенияФайлаНаСервер, ПомещаемыеФайлы, , Ложь);
КонецЕсли;
Возврат;
КонецЕсли;
КонецПроцедуры
Файл для скачивания
К публикации прилагается пример, который можно использовать как шаблон для описанных операций. В нем реализованы несколько наиболее востребованных случаев: выбор существующего файла, выбор имени файла для сохранения, выбор каталога и множественный выбор. Для полноты картины приведены аналогичные операции для обычных форм. Тестировались процедуры на платфроме 8.3, на конфигурацях начиная с режима совместимости 8.2.13