mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-30 08:26:55 +03:00
feat(form-decompile,form-compile): унификация событий элементов на events-мапу (кластер Events DSL)
Несогласованность DSL: события ФОРМЫ описывались интуитивной мапой
events:{Событие:Обработчик}, а события ЭЛЕМЕНТА — двумя сущностями
on:[...] + handlers:{...}. Два способа для одного понятия путали модель.
Унифицировано на единую мапу events:{Событие:ИмяОбработчика} на форме И
элементах (как form-level). Декомпилятор эмитит только её, с явными именами
обработчиков (прозрачно, консистентно с form-level).
Компилятор (ps1+py):
- Emit-Events читает events-мапу (основной формат); значение null/"" →
имя по конвенции ИмяЭлемента+суффикс (прощающий fallback).
- legacy on/handlers по-прежнему принимаются ради совместимости (не эмитятся).
- choiceButton: проверка StartChoice через оба формата (Test-ElementEvent).
- events добавлен в whitelist ключей элемента.
Декомпилятор: Get-Events → упорядоченная мапа {Событие:Обработчик} в порядке
документа; убраны on/handlers и инверсия авто-имён.
spec/SKILL.md: events как единственный рекомендованный формат, on/handlers
помечены legacy. В SKILL.md только явные имена (null-сахар — деталь spec,
инструкцию не раздуваем).
Корпус acc_8.3.24: 190 элементов в 114/400 форм теряли Events до фикса (баг
on/handlers разобран отдельным коммитом). Раундтрип 2.17: Events ушли из топа
LOST, match 4→6, 0 compile-fail. Регресс ps+py 32/32, снэпшот events (добавлен
блок Events у поля с переименованным обработчиком) сертифицирован в 1С 8.3.24.
Follow-up: form-edit использует расширенный on с {event,callType} —
унификация отдельным решением (см. BACKLOG).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a38874280c
commit
4c2c72abce
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.31 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.32 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -1803,30 +1803,57 @@ $script:knownEvents = @{
|
||||
}
|
||||
$script:knownFormEvents = @("OnCreateAtServer","OnOpen","BeforeClose","OnClose","NotificationProcessing","ChoiceProcessing","OnReadAtServer","AfterWriteAtServer","BeforeWriteAtServer","AfterWrite","BeforeWrite","OnWriteAtServer","FillCheckProcessingAtServer","OnLoadDataFromSettingsAtServer","BeforeLoadDataFromSettingsAtServer","OnSaveDataInSettingsAtServer","ExternalEvent","OnReopen","Opening")
|
||||
|
||||
# Собрать упорядоченный список событий элемента (имя, обработчик) из DSL.
|
||||
# Основной формат: $el.events = { Событие: ИмяОбработчика } (null/"" → авто-имя по конвенции).
|
||||
# Legacy (принимается ради совместимости): $el.on (массив) + $el.handlers (переопределение имён).
|
||||
function Get-EventPairs {
|
||||
param($el, [string]$elementName)
|
||||
$pairs = New-Object System.Collections.ArrayList
|
||||
if ($el.events) {
|
||||
foreach ($p in $el.events.PSObject.Properties) {
|
||||
$h = "$($p.Value)"
|
||||
if ([string]::IsNullOrEmpty($h)) { $h = Get-HandlerName -elementName $elementName -eventName $p.Name }
|
||||
[void]$pairs.Add([pscustomobject]@{ name = $p.Name; handler = $h })
|
||||
}
|
||||
} elseif ($el.on) {
|
||||
foreach ($evt in $el.on) {
|
||||
$evtName = "$evt"
|
||||
$h = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" } else { Get-HandlerName -elementName $elementName -eventName $evtName }
|
||||
[void]$pairs.Add([pscustomobject]@{ name = $evtName; handler = $h })
|
||||
}
|
||||
}
|
||||
return $pairs
|
||||
}
|
||||
|
||||
# Проверить, подключено ли событие к элементу (в любом из форматов).
|
||||
function Test-ElementEvent {
|
||||
param($el, [string]$eventName)
|
||||
if ($el.events) {
|
||||
foreach ($p in $el.events.PSObject.Properties) { if ($p.Name -eq $eventName) { return $true } }
|
||||
}
|
||||
if ($el.on -contains $eventName) { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Emit-Events {
|
||||
param($el, [string]$elementName, [string]$indent, [string]$typeKey)
|
||||
|
||||
if (-not $el.on) { return }
|
||||
$pairs = Get-EventPairs -el $el -elementName $elementName
|
||||
if ($pairs.Count -eq 0) { return }
|
||||
|
||||
# Validate event names
|
||||
if ($typeKey -and $script:knownEvents.ContainsKey($typeKey)) {
|
||||
$allowed = $script:knownEvents[$typeKey]
|
||||
foreach ($evt in $el.on) {
|
||||
if ($allowed.Count -gt 0 -and $allowed -notcontains "$evt") {
|
||||
Write-Host "[WARN] Unknown event '$evt' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
||||
foreach ($pr in $pairs) {
|
||||
if ($allowed.Count -gt 0 -and $allowed -notcontains "$($pr.name)") {
|
||||
Write-Host "[WARN] Unknown event '$($pr.name)' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
X "$indent<Events>"
|
||||
foreach ($evt in $el.on) {
|
||||
$evtName = "$evt"
|
||||
$handler = if ($el.handlers -and $el.handlers.$evtName) {
|
||||
"$($el.handlers.$evtName)"
|
||||
} else {
|
||||
Get-HandlerName -elementName $elementName -eventName $evtName
|
||||
}
|
||||
X "$indent`t<Event name=`"$evtName`">$handler</Event>"
|
||||
foreach ($pr in $pairs) {
|
||||
X "$indent`t<Event name=`"$($pr.name)`">$($pr.handler)</Event>"
|
||||
}
|
||||
X "$indent</Events>"
|
||||
}
|
||||
@@ -1932,8 +1959,8 @@ function Emit-Element {
|
||||
"name"=1;"path"=1;"title"=1
|
||||
# visibility & state
|
||||
"visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1;"userVisible"=1
|
||||
# events
|
||||
"on"=1;"handlers"=1
|
||||
# events ("events" — основной формат; on/handlers — legacy, принимаются ради совместимости)
|
||||
"events"=1;"on"=1;"handlers"=1
|
||||
# layout
|
||||
"titleLocation"=1;"representation"=1;"width"=1;"height"=1
|
||||
"horizontalStretch"=1;"verticalStretch"=1;"autoMaxWidth"=1;"autoMaxHeight"=1
|
||||
@@ -2218,7 +2245,7 @@ function Emit-Input {
|
||||
if ($el.multiLine -eq $true) { X "$inner<MultiLine>true</MultiLine>" }
|
||||
if ($el.passwordMode -eq $true) { X "$inner<PasswordMode>true</PasswordMode>" }
|
||||
if ($el.choiceButton -eq $false) { X "$inner<ChoiceButton>false</ChoiceButton>" }
|
||||
elseif ($el.choiceButton -eq $true -and ($el.on -contains 'StartChoice')) { X "$inner<ChoiceButton>true</ChoiceButton>" }
|
||||
elseif ($el.choiceButton -eq $true -and (Test-ElementEvent $el 'StartChoice')) { X "$inner<ChoiceButton>true</ChoiceButton>" }
|
||||
if ($el.clearButton -eq $true) { X "$inner<ClearButton>true</ClearButton>" }
|
||||
if ($el.spinButton -eq $true) { X "$inner<SpinButton>true</SpinButton>" }
|
||||
if ($el.dropListButton -eq $true) { X "$inner<DropListButton>true</DropListButton>" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.31 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.32 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -1356,7 +1356,7 @@ KNOWN_KEYS = {
|
||||
"radioButtonType", "choiceList", "columnsCount", "checkBoxType", "editMode",
|
||||
"name", "path", "title",
|
||||
"visible", "hidden", "enabled", "disabled", "readOnly", "userVisible",
|
||||
"on", "handlers",
|
||||
"events", "on", "handlers",
|
||||
"titleLocation", "representation", "width", "height",
|
||||
"horizontalStretch", "verticalStretch", "autoMaxWidth", "autoMaxHeight",
|
||||
"maxWidth", "maxHeight",
|
||||
@@ -1533,26 +1533,53 @@ def get_element_name(el, type_key):
|
||||
return str(el.get(type_key, ''))
|
||||
|
||||
|
||||
# Собрать упорядоченный список событий элемента (имя, обработчик) из DSL.
|
||||
# Основной формат: el['events'] = { Событие: ИмяОбработчика } (None/"" → авто-имя по конвенции).
|
||||
# Legacy (принимается ради совместимости): el['on'] (массив) + el['handlers'] (переопределение имён).
|
||||
def get_event_pairs(el, element_name):
|
||||
pairs = []
|
||||
events = el.get('events')
|
||||
if events:
|
||||
for ev_name, val in events.items():
|
||||
handler = '' if val is None else str(val)
|
||||
if not handler:
|
||||
handler = get_handler_name(element_name, ev_name)
|
||||
pairs.append((ev_name, handler))
|
||||
elif el.get('on'):
|
||||
handlers = el.get('handlers') or {}
|
||||
for evt in el['on']:
|
||||
evt_name = str(evt)
|
||||
if handlers.get(evt_name):
|
||||
handler = str(handlers[evt_name])
|
||||
else:
|
||||
handler = get_handler_name(element_name, evt_name)
|
||||
pairs.append((evt_name, handler))
|
||||
return pairs
|
||||
|
||||
|
||||
# Проверить, подключено ли событие к элементу (в любом из форматов).
|
||||
def test_element_event(el, event_name):
|
||||
events = el.get('events')
|
||||
if events and event_name in events:
|
||||
return True
|
||||
return event_name in (el.get('on') or [])
|
||||
|
||||
|
||||
def emit_events(lines, el, element_name, indent, type_key):
|
||||
if not el.get('on'):
|
||||
pairs = get_event_pairs(el, element_name)
|
||||
if not pairs:
|
||||
return
|
||||
|
||||
# Validate event names
|
||||
if type_key and type_key in KNOWN_EVENTS:
|
||||
allowed = KNOWN_EVENTS[type_key]
|
||||
for evt in el['on']:
|
||||
if allowed and str(evt) not in allowed:
|
||||
print(f"[WARN] Unknown event '{evt}' for {type_key} '{element_name}'. Known: {', '.join(allowed)}")
|
||||
for ev_name, _ in pairs:
|
||||
if allowed and str(ev_name) not in allowed:
|
||||
print(f"[WARN] Unknown event '{ev_name}' for {type_key} '{element_name}'. Known: {', '.join(allowed)}")
|
||||
|
||||
lines.append(f"{indent}<Events>")
|
||||
for evt in el['on']:
|
||||
evt_name = str(evt)
|
||||
handlers = el.get('handlers')
|
||||
if handlers and handlers.get(evt_name):
|
||||
handler = str(handlers[evt_name])
|
||||
else:
|
||||
handler = get_handler_name(element_name, evt_name)
|
||||
lines.append(f'{indent}\t<Event name="{evt_name}">{handler}</Event>')
|
||||
for ev_name, handler in pairs:
|
||||
lines.append(f'{indent}\t<Event name="{ev_name}">{handler}</Event>')
|
||||
lines.append(f"{indent}</Events>")
|
||||
|
||||
|
||||
@@ -2015,7 +2042,7 @@ def emit_input(lines, el, name, eid, indent):
|
||||
lines.append(f'{inner}<PasswordMode>true</PasswordMode>')
|
||||
if el.get('choiceButton') is False:
|
||||
lines.append(f'{inner}<ChoiceButton>false</ChoiceButton>')
|
||||
elif el.get('choiceButton') is True and 'StartChoice' in (el.get('on') or []):
|
||||
elif el.get('choiceButton') is True and test_element_event(el, 'StartChoice'):
|
||||
lines.append(f'{inner}<ChoiceButton>true</ChoiceButton>')
|
||||
if el.get('clearButton') is True:
|
||||
lines.append(f'{inner}<ClearButton>true</ClearButton>')
|
||||
|
||||
Reference in New Issue
Block a user