Начнем с того, к каким файлам применимо это решение, то есть какие файлы обычно используются в разработке:
Содержимое веб-контролов можно загружать по требованию (при нажатии кнопки), а можно загружать сразу при открытии карточки. От второго варианта разработчики часто отказываются из-за того, что это замедляет открытие карточки, особенно если веб-контролов на форме много. Чтобы минимизировать время загрузки веб-контролов, необходимо файлы, используемые в веб-контроле, кэшировать на клиенте. Ну и выполнять отладку веб-контролов тоже хотелось бы быстро и удобно.
В этой статье я покажу как может выглядеть решение, реализующее:
В решении будут использоваться функции из библиотеки UDL Александра Тишина.
Для хранения файлов в системе было решено использовать документы по следующим причинам:
Итак, делаем свою ТКД и выгрузку кэша в файловую систему.
Файлы будут кэшироваться в %temp%\<Код системы>\cache\. Такой путь выбран чтобы файлы кэша были дочерними для html-файлов, выгружаемых в папку %temp%.
Result = IfThen(RelativeToTemp; ''; GetTempFolder()) & Application.Connection.SystemInfo.Code & "\Cache\"
Путь в папке кэша – относительный путь файла в папке, которую возвращает функция UDL_GetCacheFolderPath(). Если не указано расширение файла, то оно берется из последней действующей версии документа. Для zip-архивов имя файла используется как имя папки, куда будет распаковываться содержимое архива. Путь может содержать подпапки, например, “js/lib/jquery”. Этот реквизит используется как уникальный идентификатор файла при переносе документов между системами.
При сохранении документа будем записывать дату изменения в константу. Константы кэшируются на клиенте, поэтому при проверке актуальности кэша запрос в БД производиться не будет.
SetConstant('LastCacheFilesModifiedDate'; FormatDate('yymd h:n:s'; Object.ISBEDocModifyDate))
Функцию UDL_UpdateCache() можно выполнять не при каждом показе карточки, а только в событии «Набор данных. Открытие».
Не нужно никому выдавать права на документы.
Проверка актуальности кэша – всего 1 чтение из файла.
Содержимое zip-архивов распаковывается в каталог.
Выгрузка кэша работает быстро – всего один SQL-запрос и запись изменившихся файлов на диск.
ActualDate = GetAssignedConst("LastCacheFilesModifiedDate") CacheFolderPath = UDL_GetCacheFolderPath() CacheDateFilePath = CacheFolderPath & "cache.dat" CacheDate = '' if not DirectoryExists(CacheFolderPath) ForceDirectories(CacheFolderPath) endif NeedUpdate = true if FileExists(CacheDateFilePath) and not Force CacheDate = ReadFile(CacheDateFilePath) if ActualDate == CacheDate NeedUpdate = false endif endif if NeedUpdate Query = CreateQuery() Query.CommandText = " select lastActiveVersion.Body as Body , (select AppExt from MBAnalit where Analit = lastActiveVersion.Editor) as Extension , rtrim(ltrim(SBEDoc.Note)) as Path from SBEDoc cross apply ( select top 1 SBEDocVer.VersionData , SBEDocVer.Editor from SBEDocVer where SBEDocVer.EDocID = SBEDoc.XRecID and SBEDocVer.LifeStage = 'Д' order by SBEDocVer.Number desc) as lastActiveVersion(Body, Editor) where SBEDoc.TypeID = (select TypeID from MBEDocType where kod like 'UDL_CachedFiles') " & IfThen(Assigned(CacheDate) and not Force; " and SBEDoc.ModifyDate > '" & CacheDate & "'"; '') & IfThen(Assigned(DocumentID); " and SBEDoc.XRecID = " & DocumentID; '') Query.Open foreach File in Query CachedFilePath = CacheFolderPath & File.Fields("Path").AsString if not Assigned(ExtractFileDriveDirNameExt(CachedFilePath; 'E')) CachedFilePath = CachedFilePath & "." & File.Fields("Extension").AsString endif DeleteFile(CachedFilePath) File.Fields("Body").SaveToFile(CachedFilePath) if File.Fields("Extension").AsString == 'ZIP' TargetFolder = ExtractFileDriveDirNameExt(CachedFilePath; 'D:\P\N\') DeleteFile(TargetFolder & "*.*") UDL_UNZIPArchive(CachedFilePath; TargetFolder) DeleteFile(CachedFilePath) endif endforeach WriteFile(CacheDateFilePath; ; ActualDate) endif Result = CacheFolderPath
Открывает файл из папки кэша. Если файл - zip-архив, то открывает каталог с распакованным содержимым в проводнике. Результат редактирования можно сразу наблюдать в веб-контроле, обновив страницу(F5).
UDL_UpdateCache(; Object.SYSREQ_ID) FilePath = UDL_GetCacheFolderPath() & Object.SYSREQ_EDOC_NOTE if not Assigned(ExtractFileDriveDirNameExt(FilePath; 'E')) FilePath = FilePath & "." & Object.Info.Editor.Extension endif Ext = ExtractFileDriveDirNameExt(FilePath; 'E') if Ext == 'zip' Folder = ExtractFileDriveDirNameExt(FilePath; 'D:\P\N\') ExecuteProcess('explorer "' & Folder & '"') else OpenFile(FilePath) endif
Берет из кэша файл и импортирует в документ. Для zip-архива каталог архива запаковывается.
FilePath = UDL_GetCacheFolderPath() & Object.SYSREQ_EDOC_NOTE if not Assigned(ExtractFileDriveDirNameExt(FilePath; 'E')) FilePath = FilePath & "." & Object.Info.Editor.Extension endif Ext = ExtractFileDriveDirNameExt(FilePath; 'E') if Ext == 'zip' Folder = ExtractFileDriveDirNameExt(FilePath; 'D:\P\N\') ZipContent = CreateStringList() FolderContent = CreateStringList() FolderContent.Delimiter = ";" FolderContent.DelimitedText = FindFile(Folder & "*") & ';' & FindFile(Folder & "*"; 'D') foreach Item in FolderContent if Assigned(Item) and Item <<>> '.' and Item <<>> '..' ZipContent.Add(Folder & Item) endif endforeach UDL_ZIPArchive(ZipContent; FilePath) endif ImportDialog = SystemDialogs.GetImportEDocumentVersionFromFileDialog(Object; -1; FilePath; Object.Info.Editor.Code; false; false) ImportDialog.Show(false) if Ext == 'zip' Sleep(1) DeleteFile(FilePath) endif
Принудительно обновляет кэш текущего документа. Отменяет все изменения текущего документа, произведенные в кэше.
UDL_UpdateCache(true; Object.SYSREQ_ID)
Для удобства переноса между системами разработаны сценарии, использующие свой формат пакета: экспортируемые файлы запаковываются в архив, при этом добавляется служебный файл PackageInfo.xml, хранящий реквизиты документа для каждого экспортируемого файла.
Search = Searches.CreateNew(ckEDocument) Search.SearchCriteria.Add(SYSREQ_EDOC_TYPE_PSEUDO_REQUISITE_CODE).Add("UDL_CachedFiles") Search.Show(ssmMultiSelect; false) SelectedContents = Search.SelectedContents TargetFolder = IncludeTrailingPathDelimiter(SelectFolderDialog()) PackageName = "CachedFiles" & FormatDate(' yymdhns'; Now()) ZIPFolder = TargetFolder & PackageName & "\" ForceDirectories(ZIPFolder) ZIPContents = CreateStringList() try PackageInfoXML = CreateObject("MSXML.DOMDocument") RootElement = PackageInfoXML.CreateElement("CachedFiles") PackageInfoXML.AppendChild(RootElement) foreach DocInfo in SelectedContents Doc = DocInfo.Document LastActiveVersion = UDL_GetLastActiveVersion(Doc) FileName = ClearFileName(Doc.SYSREQ_EDOC_NAME & FormatDate(' yymdhns'; Doc.ISBEDocModifyDate); 'Ж') & '.' & LastActiveVersion.Editor.Extension FullFilePath = ZIPFolder & FileName ZIPContents.Add(FullFilePath) LastActiveVersion.Export(FullFilePath) FileElement = PackageInfoXML.CreateElement("File") Attribute = PackageInfoXML.createAttribute("Name") Attribute.Value = Doc.Requisites(SYSREQ_EDOC_NAME).AsString FileElement.setAttributeNode(Attribute) Attribute = PackageInfoXML.createAttribute("CachePath") Attribute.Value = Doc.Requisites(SYSREQ_EDOC_NOTE).AsString FileElement.setAttributeNode(Attribute) Attribute = PackageInfoXML.createAttribute("DocumentNote") Attribute.Value = Doc.Requisites("LongString").AsString FileElement.setAttributeNode(Attribute) Attribute = PackageInfoXML.createAttribute("VersionNote") Attribute.Value = LastActiveVersion.Note FileElement.setAttributeNode(Attribute) FileElement.text = FileName RootElement.AppendChild(FileElement) LastActiveVersion = nil Doc = nil endforeach PackageInfoFilePath = ZIPFolder & "PackageInfo.xml" PackageInfoXML.Save(PackageInfoFilePath) ZIPContents.Add(PackageInfoFilePath) UDL_ZIPArchive(ZIPContents; TargetFolder & PackageName & '.zip') finally DeleteFile(ZIPFolder) endfinally ExecuteProcess('explorer "' & TargetFolder & '"')
При импорте берется первый подходящий вид документа. Предполагается, что для кэшируемых файлов создан отдельный вид документа, с разрешением на создание только у Администратора.
KindCode = SQL(" select top 1 MBAnalitSpr.Kod from MBAnalitSpr join MBAnValR2 on MBAnalitSpr.Analit = MBAnValR2.Analit where MBAnValR2.TipElDokT2 = (select Analit from MBAnalit where Prim like 'UDL_CachedFiles' and Vid = " & References.ТЭД.ID & ") and MBAnalitSpr.Vid = " & References.ВЭД.ID) if not Assigned(KindCode) Raise(CreateException(''; 'Нет доступных видов документов для типа карточки "UDL_CachedFiles"'; ecWarning)) endif OpenDialog = CreateOpenDialog() OpenDialog.Filter = 'Cache files package (*.zip)|*.zip' OpenDialog.FilterIndex = 1 OpenDialog.MultiSelect = false if OpenDialog.Execute PackageFilePath = OpenDialog.Result ZIPFolder = GetTempFolder() & ExtractFileDriveDirNameExt(PackageFilePath; 'N') & "\" DeleteFile(ZIPFolder) UDL_UNZIPArchive(PackageFilePath; ZIPFolder) try PackageInfoXML = CreateObject("MSXML.DOMDocument") PackageInfoXML.load(ZIPFolder & "PackageInfo.xml") Root = PackageInfoXML.documentElement Nodes = Root.childNodes Index = 0 while Index < Nodes.length Node = Nodes.item(Index) FilePath = ZIPFolder & Node.text DocName = Node.getAttribute('Name') CachePath = Node.getAttribute('CachePath') DocumentNote = Node.getAttribute('DocumentNote') VersionNote = Node.getAttribute('VersionNote') DocumentID = SQL(" select XRecID from SBEDoc where TypeID = (select TypeID from MBEDocType where kod like 'UDL_CachedFiles') and ltrim(rtrim(Note)) like '" & Trim(CachePath) & "'") if Assigned(DocumentID) Doc = EDocuments.GetObjectByID(DocumentID) Doc.ImportFromFile(-1; VersionNote; FilePath) else EditorCode = UDL_GetEditorCodeByFilePath(FilePath) Doc = EDocuments.CreateNewFromFile('UDL_CachedFiles'; KindCode; EditorCode; FilePath) endif Doc.Requisites(SYSREQ_EDOC_NAME).Value = DocName Doc.Requisites(SYSREQ_EDOC_NOTE).Value = CachePath Doc.Requisites("LongString").Value = DocumentNote Doc.Save Doc = nil Index = Index + 1 endwhile ShowMessage("Успешно импортировано " & Index & " файлов") finally DeleteFile(ZIPFolder) endfinally endif
В качестве примера возьмем отображение на форме карточки справочника веб-контрола с календарем. Весь веб-контрол заархивировали, структура файлов как у обложки. Архив занесен в систему с ТКД "Кэшируемые файлы", значение реквизита документа Путь в папке кэша = calendar
UpdateCache() CalendarControl = Object.Form.Controls.FindControl("WebCalendar") CalendarControl.Navigate(GetCacheFolderPath() & "calendar\index.html")
Если содержимое веб-контрола динамическое его формирование занимает приличное время, то советую не формировать HTML-файл в коде ISBL и выгружать его на диск для загрузки в веб-контроле, а из самой страницы в коде JS вызывать сценарий Directum, который вернет набор данных для отображения. Такой подход позволяет открывать карточку вообще без задержек, так как JS выполняется в отдельном потоке, параллельно с открытием карточки.
Пакет разработки (лицензия MIT): CacheFile.zip
Пакет собран на версии 5.2.2, но решение должно работать на всех версиях системы.
Авторизуйтесь, чтобы написать комментарий