Начнем с того, к каким файлам применимо это решение, то есть какие файлы обычно используются в разработке:
Содержимое веб-контролов можно загружать по требованию (при нажатии кнопки), а можно загружать сразу при открытии карточки. От второго варианта разработчики часто отказываются из-за того, что это замедляет открытие карточки, особенно если веб-контролов на форме много. Чтобы минимизировать время загрузки веб-контролов, необходимо файлы, используемые в веб-контроле, кэшировать на клиенте. Ну и выполнять отладку веб-контролов тоже хотелось бы быстро и удобно.
В этой статье я покажу как может выглядеть решение, реализующее:
В решении будут использоваться функции из библиотеки 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, но решение должно работать на всех версиях системы.
Авторизуйтесь, чтобы написать комментарий