bpmn-js-rails 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Rakefile +6 -0
- data/app/assets/javascripts/bpmn-js/bpmn-modeler.production.min.js +199 -0
- data/app/assets/javascripts/bpmn-js/bpmn-viewer.production.min.js +22 -0
- data/app/assets/javascripts/dmn-js/dmn-modeler.production.min.js +194 -0
- data/app/assets/javascripts/dmn-js/dmn-viewer.production.min.js +16 -0
- data/app/assets/javascripts/form-js/form-editor.umd.js +78936 -0
- data/app/assets/javascripts/form-js/form-viewer.umd.js +54147 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/css/bpmn-codes.css +108 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/css/bpmn-embedded.css +166 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/css/bpmn.css +164 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/font/bpmn.eot +0 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/font/bpmn.svg +224 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/font/bpmn.ttf +0 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/font/bpmn.woff +0 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-font/font/bpmn.woff2 +0 -0
- data/app/assets/stylesheets/bpmn-js/bpmn-js.css +145 -0
- data/app/assets/stylesheets/bpmn-js/diagram-js.css +1011 -0
- data/app/assets/stylesheets/dmn-js/diagram-js.css +1011 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/css/dmn-codes.css +41 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/css/dmn-embedded.css +98 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/css/dmn.css +97 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/font/dmn.eot +0 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/font/dmn.svg +90 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/font/dmn.ttf +0 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/font/dmn.woff +0 -0
- data/app/assets/stylesheets/dmn-js/dmn-font/font/dmn.woff2 +0 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-boxed-expression-controls.css +130 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-boxed-expression.css +186 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-decision-table-controls.css +301 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-decision-table.css +580 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-drd.css +95 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-literal-expression.css +165 -0
- data/app/assets/stylesheets/dmn-js/dmn-js-shared.css +221 -0
- data/app/assets/stylesheets/form-js/form-js-editor.css +2342 -0
- data/app/assets/stylesheets/form-js/form-js.css +3694 -0
- data/app/helpers/bpmn_js_rails/view_helper.rb +291 -0
- data/app/javascript/bpmn_js_rails/controllers/bpmn_js_modeler_controller.js +85 -0
- data/app/javascript/bpmn_js_rails/controllers/bpmn_js_viewer_controller.js +46 -0
- data/app/javascript/bpmn_js_rails/controllers/dmn_js_modeler_controller.js +102 -0
- data/app/javascript/bpmn_js_rails/controllers/dmn_js_viewer_controller.js +45 -0
- data/app/javascript/bpmn_js_rails/controllers/form_js_editor_controller.js +80 -0
- data/app/javascript/bpmn_js_rails/controllers/form_js_viewer_controller.js +52 -0
- data/app/javascript/bpmn_js_rails/index.js +34 -0
- data/app/models/bpmn_js_rails/decision.rb +60 -0
- data/app/models/bpmn_js_rails/diagram.rb +51 -0
- data/app/models/bpmn_js_rails/form.rb +32 -0
- data/config/importmap.rb +7 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20260319000001_create_bpmn_js_rails_diagrams.rb +15 -0
- data/db/migrate/20260319000002_create_bpmn_js_rails_forms.rb +15 -0
- data/db/migrate/20260319000003_create_bpmn_js_rails_decisions.rb +15 -0
- data/lib/bpmn-js-rails.rb +1 -0
- data/lib/bpmn_js_rails/engine.rb +30 -0
- data/lib/bpmn_js_rails/version.rb +3 -0
- data/lib/bpmn_js_rails/version.rb.erb +3 -0
- data/lib/bpmn_js_rails.rb +5 -0
- data/lib/generators/bpmn_js_rails/install/install_generator.rb +58 -0
- data/lib/generators/bpmn_js_rails/install/templates/create_bpmn_js_rails_decisions.rb.erb +15 -0
- data/lib/generators/bpmn_js_rails/install/templates/create_bpmn_js_rails_diagrams.rb.erb +15 -0
- data/lib/generators/bpmn_js_rails/install/templates/create_bpmn_js_rails_forms.rb.erb +15 -0
- data/lib/tasks/form_js_rails_tasks.rake +4 -0
- metadata +116 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BpmnJsRails
|
|
4
|
+
module ViewHelper
|
|
5
|
+
# ── form-js assets ───────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
# Render <link> and <script> tags for form-js assets.
|
|
8
|
+
#
|
|
9
|
+
# <%= form_js_assets %> # viewer only
|
|
10
|
+
# <%= form_js_assets(:viewer, :editor) %> # both viewer and editor
|
|
11
|
+
#
|
|
12
|
+
def form_js_assets(*components)
|
|
13
|
+
components = [ :viewer ] if components.empty?
|
|
14
|
+
tags = []
|
|
15
|
+
|
|
16
|
+
tags << tag.link(
|
|
17
|
+
rel: "stylesheet",
|
|
18
|
+
href: "https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,600;1,400&display=swap"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
tags << stylesheet_link_tag("form-js/form-js", "data-turbo-track": "reload")
|
|
22
|
+
|
|
23
|
+
if components.include?(:editor)
|
|
24
|
+
tags << stylesheet_link_tag("form-js/form-js-editor", "data-turbo-track": "reload")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if components.include?(:viewer)
|
|
28
|
+
tags << javascript_include_tag("form-js/form-viewer.umd", "data-turbo-track": "reload")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if components.include?(:editor)
|
|
32
|
+
tags << javascript_include_tag("form-js/form-editor.umd", "data-turbo-track": "reload")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
safe_join(tags, "\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Render a form-js viewer that displays/fills a form.
|
|
39
|
+
#
|
|
40
|
+
# <%= form_js_viewer(@form) %>
|
|
41
|
+
# <%= form_js_viewer(@form, data: { name: "John" }) %>
|
|
42
|
+
# <%= form_js_viewer({ type: "default", components: [...] }) %>
|
|
43
|
+
#
|
|
44
|
+
def form_js_viewer(form_or_schema, data: {}, **html_options)
|
|
45
|
+
schema = extract_schema(form_or_schema)
|
|
46
|
+
|
|
47
|
+
html_options[:class] = Array(html_options[:class]).push("form-js-viewer-container").join(" ")
|
|
48
|
+
html_options[:style] = [ html_options[:style], "min-height: 200px" ].compact.join("; ")
|
|
49
|
+
|
|
50
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
51
|
+
controller: "form-js-viewer",
|
|
52
|
+
form_js_viewer_schema_value: schema.to_json,
|
|
53
|
+
form_js_viewer_data_value: data.to_json
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
content_tag(:div, "", **html_options)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Render a form-js editor for designing forms.
|
|
60
|
+
#
|
|
61
|
+
# <%= form_js_editor(@form) %>
|
|
62
|
+
# <%= form_js_editor(@form, field_name: "form[schema]") %>
|
|
63
|
+
#
|
|
64
|
+
def form_js_editor(form_or_schema, field_name: nil, **html_options)
|
|
65
|
+
schema = extract_schema(form_or_schema)
|
|
66
|
+
|
|
67
|
+
html_options[:class] = Array(html_options[:class]).push("form-js-editor-container").join(" ")
|
|
68
|
+
|
|
69
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
70
|
+
controller: "form-js-editor",
|
|
71
|
+
form_js_editor_schema_value: schema.to_json
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
content_tag(:div, **html_options) do
|
|
75
|
+
children = []
|
|
76
|
+
children << content_tag(:div, "", style: "min-height: 500px", data: { form_js_editor_target: "container" })
|
|
77
|
+
|
|
78
|
+
if field_name
|
|
79
|
+
children << tag.input(
|
|
80
|
+
type: "hidden",
|
|
81
|
+
name: field_name,
|
|
82
|
+
value: schema.to_json,
|
|
83
|
+
data: { form_js_editor_target: "schemaField" }
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
safe_join(children)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# ── bpmn-js assets ───────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
# Render <link> and <script> tags for bpmn-js assets.
|
|
94
|
+
#
|
|
95
|
+
# <%= bpmn_js_assets %> # viewer only
|
|
96
|
+
# <%= bpmn_js_assets(:viewer, :modeler) %> # both viewer and modeler
|
|
97
|
+
#
|
|
98
|
+
def bpmn_js_assets(*components)
|
|
99
|
+
components = [ :viewer ] if components.empty?
|
|
100
|
+
tags = []
|
|
101
|
+
|
|
102
|
+
tags << stylesheet_link_tag("bpmn-js/diagram-js", "data-turbo-track": "reload")
|
|
103
|
+
tags << stylesheet_link_tag("bpmn-js/bpmn-js", "data-turbo-track": "reload")
|
|
104
|
+
tags << stylesheet_link_tag("bpmn-js/bpmn-font/css/bpmn-embedded", "data-turbo-track": "reload")
|
|
105
|
+
|
|
106
|
+
if components.include?(:viewer) && !components.include?(:modeler)
|
|
107
|
+
tags << javascript_include_tag("bpmn-js/bpmn-viewer.production.min", "data-turbo-track": "reload")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if components.include?(:modeler)
|
|
111
|
+
tags << javascript_include_tag("bpmn-js/bpmn-modeler.production.min", "data-turbo-track": "reload")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
safe_join(tags, "\n")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Render a bpmn-js viewer that displays a BPMN diagram (read-only).
|
|
118
|
+
#
|
|
119
|
+
# <%= bpmn_js_viewer(@diagram) %>
|
|
120
|
+
# <%= bpmn_js_viewer("<bpmn:definitions ...>...</bpmn:definitions>") %>
|
|
121
|
+
#
|
|
122
|
+
def bpmn_js_viewer(diagram_or_xml, **html_options)
|
|
123
|
+
xml = extract_xml(diagram_or_xml)
|
|
124
|
+
|
|
125
|
+
html_options[:class] = Array(html_options[:class]).push("bpmn-js-viewer-container").join(" ")
|
|
126
|
+
html_options[:style] = [ html_options[:style], "height: 500px" ].compact.join("; ")
|
|
127
|
+
|
|
128
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
129
|
+
controller: "bpmn-js-viewer",
|
|
130
|
+
bpmn_js_viewer_xml_value: xml
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
content_tag(:div, "", **html_options)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Render a bpmn-js modeler for designing BPMN diagrams.
|
|
137
|
+
#
|
|
138
|
+
# <%= bpmn_js_modeler(@diagram) %>
|
|
139
|
+
# <%= bpmn_js_modeler(@diagram, field_name: "diagram[xml]") %>
|
|
140
|
+
#
|
|
141
|
+
def bpmn_js_modeler(diagram_or_xml, field_name: nil, **html_options)
|
|
142
|
+
xml = extract_xml(diagram_or_xml)
|
|
143
|
+
|
|
144
|
+
html_options[:class] = Array(html_options[:class]).push("bpmn-js-modeler-container").join(" ")
|
|
145
|
+
|
|
146
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
147
|
+
controller: "bpmn-js-modeler",
|
|
148
|
+
bpmn_js_modeler_xml_value: xml
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
content_tag(:div, **html_options) do
|
|
152
|
+
children = []
|
|
153
|
+
children << content_tag(:div, "", style: "height: 500px", data: { bpmn_js_modeler_target: "container" })
|
|
154
|
+
|
|
155
|
+
if field_name
|
|
156
|
+
children << tag.input(
|
|
157
|
+
type: "hidden",
|
|
158
|
+
name: field_name,
|
|
159
|
+
value: xml,
|
|
160
|
+
data: { bpmn_js_modeler_target: "xmlField" }
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
safe_join(children)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# ── dmn-js assets ────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
# Render <link> and <script> tags for dmn-js assets.
|
|
171
|
+
#
|
|
172
|
+
# <%= dmn_js_assets %> # viewer only
|
|
173
|
+
# <%= dmn_js_assets(:viewer, :modeler) %> # both viewer and modeler
|
|
174
|
+
#
|
|
175
|
+
def dmn_js_assets(*components)
|
|
176
|
+
components = [ :viewer ] if components.empty?
|
|
177
|
+
tags = []
|
|
178
|
+
|
|
179
|
+
tags << stylesheet_link_tag("dmn-js/diagram-js", "data-turbo-track": "reload")
|
|
180
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-shared", "data-turbo-track": "reload")
|
|
181
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-drd", "data-turbo-track": "reload")
|
|
182
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-decision-table", "data-turbo-track": "reload")
|
|
183
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-literal-expression", "data-turbo-track": "reload")
|
|
184
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-boxed-expression", "data-turbo-track": "reload")
|
|
185
|
+
tags << stylesheet_link_tag("dmn-js/dmn-font/css/dmn-embedded", "data-turbo-track": "reload")
|
|
186
|
+
|
|
187
|
+
if components.include?(:modeler)
|
|
188
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-decision-table-controls", "data-turbo-track": "reload")
|
|
189
|
+
tags << stylesheet_link_tag("dmn-js/dmn-js-boxed-expression-controls", "data-turbo-track": "reload")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
if components.include?(:viewer) && !components.include?(:modeler)
|
|
193
|
+
tags << javascript_include_tag("dmn-js/dmn-viewer.production.min", "data-turbo-track": "reload")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if components.include?(:modeler)
|
|
197
|
+
tags << javascript_include_tag("dmn-js/dmn-modeler.production.min", "data-turbo-track": "reload")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
safe_join(tags, "\n")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Render a dmn-js viewer that displays a DMN decision (read-only).
|
|
204
|
+
#
|
|
205
|
+
# <%= dmn_js_viewer(@decision) %>
|
|
206
|
+
# <%= dmn_js_viewer("<definitions ...>...</definitions>") %>
|
|
207
|
+
#
|
|
208
|
+
def dmn_js_viewer(decision_or_xml, **html_options)
|
|
209
|
+
xml = extract_dmn_xml(decision_or_xml)
|
|
210
|
+
|
|
211
|
+
html_options[:class] = Array(html_options[:class]).push("dmn-js-viewer-container").join(" ")
|
|
212
|
+
html_options[:style] = [ html_options[:style], "height: 500px" ].compact.join("; ")
|
|
213
|
+
|
|
214
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
215
|
+
controller: "dmn-js-viewer",
|
|
216
|
+
dmn_js_viewer_xml_value: xml
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
content_tag(:div, "", **html_options)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Render a dmn-js modeler for designing DMN decisions.
|
|
223
|
+
#
|
|
224
|
+
# <%= dmn_js_modeler(@decision) %>
|
|
225
|
+
# <%= dmn_js_modeler(@decision, field_name: "decision[xml]") %>
|
|
226
|
+
#
|
|
227
|
+
def dmn_js_modeler(decision_or_xml, field_name: nil, **html_options)
|
|
228
|
+
xml = extract_dmn_xml(decision_or_xml)
|
|
229
|
+
|
|
230
|
+
html_options[:class] = Array(html_options[:class]).push("dmn-js-modeler-container").join(" ")
|
|
231
|
+
|
|
232
|
+
html_options[:data] = (html_options[:data] || {}).merge(
|
|
233
|
+
controller: "dmn-js-modeler",
|
|
234
|
+
dmn_js_modeler_xml_value: xml
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
content_tag(:div, **html_options) do
|
|
238
|
+
children = []
|
|
239
|
+
children << content_tag(:div, "", style: "height: 500px", data: { dmn_js_modeler_target: "container" })
|
|
240
|
+
|
|
241
|
+
if field_name
|
|
242
|
+
children << tag.input(
|
|
243
|
+
type: "hidden",
|
|
244
|
+
name: field_name,
|
|
245
|
+
value: xml,
|
|
246
|
+
data: { dmn_js_modeler_target: "xmlField" }
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
safe_join(children)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def extract_schema(form_or_schema)
|
|
257
|
+
case form_or_schema
|
|
258
|
+
when BpmnJsRails::Form
|
|
259
|
+
form_or_schema.schema || { "type" => "default", "components" => [] }
|
|
260
|
+
when Hash
|
|
261
|
+
form_or_schema
|
|
262
|
+
when String
|
|
263
|
+
JSON.parse(form_or_schema)
|
|
264
|
+
else
|
|
265
|
+
form_or_schema.try(:schema) || { "type" => "default", "components" => [] }
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def extract_xml(diagram_or_xml)
|
|
270
|
+
case diagram_or_xml
|
|
271
|
+
when BpmnJsRails::Diagram
|
|
272
|
+
diagram_or_xml.xml || BpmnJsRails::Diagram::DEFAULT_XML
|
|
273
|
+
when String
|
|
274
|
+
diagram_or_xml
|
|
275
|
+
else
|
|
276
|
+
diagram_or_xml.try(:xml) || BpmnJsRails::Diagram::DEFAULT_XML
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def extract_dmn_xml(decision_or_xml)
|
|
281
|
+
case decision_or_xml
|
|
282
|
+
when BpmnJsRails::Decision
|
|
283
|
+
decision_or_xml.xml || BpmnJsRails::Decision::DEFAULT_XML
|
|
284
|
+
when String
|
|
285
|
+
decision_or_xml
|
|
286
|
+
else
|
|
287
|
+
decision_or_xml.try(:xml) || BpmnJsRails::Decision::DEFAULT_XML
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="bpmn-js-modeler"
|
|
4
|
+
//
|
|
5
|
+
// Values:
|
|
6
|
+
// xml (String) - the BPMN 2.0 XML to load into the modeler
|
|
7
|
+
//
|
|
8
|
+
// Targets:
|
|
9
|
+
// container - the child div that bpmn-js renders into (falls back to this.element)
|
|
10
|
+
// xmlField - a hidden <input> that receives the serialised XML
|
|
11
|
+
// on every change and before form submission
|
|
12
|
+
//
|
|
13
|
+
// Lifecycle:
|
|
14
|
+
// connect: creates the BpmnJS modeler instance, imports XML, wires change + submit sync
|
|
15
|
+
// disconnect: destroys the modeler
|
|
16
|
+
//
|
|
17
|
+
// Events dispatched on the controller element:
|
|
18
|
+
// bpmn-js-modeler:ready - { detail: { modeler } }
|
|
19
|
+
// bpmn-js-modeler:changed - after XML sync
|
|
20
|
+
//
|
|
21
|
+
export default class extends Controller {
|
|
22
|
+
static values = {
|
|
23
|
+
xml: { type: String, default: "" }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static targets = ["container", "xmlField"]
|
|
27
|
+
|
|
28
|
+
async connect() {
|
|
29
|
+
if (typeof BpmnJS === "undefined") {
|
|
30
|
+
console.error("[bpmn-js-rails] BpmnJS global not found. Make sure bpmn-modeler.production.min.js is loaded.")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const renderTarget = this.hasContainerTarget ? this.containerTarget : this.element
|
|
35
|
+
|
|
36
|
+
this.modeler = new BpmnJS({ container: renderTarget })
|
|
37
|
+
|
|
38
|
+
if (this.xmlValue) {
|
|
39
|
+
try {
|
|
40
|
+
await this.modeler.importXML(this.xmlValue)
|
|
41
|
+
this.modeler.get("canvas").zoom("fit-viewport")
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error("[bpmn-js-rails] Failed to import XML:", err)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.modeler.on("commandStack.changed", () => {
|
|
48
|
+
this._syncXmlField()
|
|
49
|
+
this.dispatch("changed")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Also sync right before the enclosing <form> submits
|
|
53
|
+
this._form = this.element.closest("form")
|
|
54
|
+
if (this._form) {
|
|
55
|
+
this._boundSubmitHandler = () => this._syncXmlField()
|
|
56
|
+
this._form.addEventListener("submit", this._boundSubmitHandler)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.dispatch("ready", { detail: { modeler: this.modeler } })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
disconnect() {
|
|
63
|
+
if (this._form && this._boundSubmitHandler) {
|
|
64
|
+
this._form.removeEventListener("submit", this._boundSubmitHandler)
|
|
65
|
+
this._boundSubmitHandler = null
|
|
66
|
+
this._form = null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.modeler) {
|
|
70
|
+
this.modeler.destroy()
|
|
71
|
+
this.modeler = null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Serialise the current modeler XML into the hidden field target.
|
|
76
|
+
async _syncXmlField() {
|
|
77
|
+
if (!this.modeler || !this.hasXmlFieldTarget) return
|
|
78
|
+
try {
|
|
79
|
+
const { xml } = await this.modeler.saveXML({ format: true })
|
|
80
|
+
this.xmlFieldTarget.value = xml
|
|
81
|
+
} catch (_) {
|
|
82
|
+
// modeler may be mid-import; ignore
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="bpmn-js-viewer"
|
|
4
|
+
//
|
|
5
|
+
// Values:
|
|
6
|
+
// xml (String) - the BPMN 2.0 XML to display
|
|
7
|
+
//
|
|
8
|
+
// Lifecycle:
|
|
9
|
+
// connect: creates the BpmnJS viewer instance, imports XML
|
|
10
|
+
// disconnect: destroys it (cleans up DOM + listeners)
|
|
11
|
+
//
|
|
12
|
+
// Events dispatched on the controller element:
|
|
13
|
+
// bpmn-js-viewer:loaded - { detail: { warnings } }
|
|
14
|
+
// bpmn-js-viewer:error - { detail: { error } }
|
|
15
|
+
//
|
|
16
|
+
export default class extends Controller {
|
|
17
|
+
static values = {
|
|
18
|
+
xml: { type: String, default: "" }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async connect() {
|
|
22
|
+
if (typeof BpmnJS === "undefined") {
|
|
23
|
+
console.error("[bpmn-js-rails] BpmnJS global not found. Make sure bpmn-viewer.production.min.js or bpmn-modeler.production.min.js is loaded.")
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.viewer = new BpmnJS({ container: this.element })
|
|
28
|
+
|
|
29
|
+
if (this.xmlValue) {
|
|
30
|
+
try {
|
|
31
|
+
const result = await this.viewer.importXML(this.xmlValue)
|
|
32
|
+
this.viewer.get("canvas").zoom("fit-viewport")
|
|
33
|
+
this.dispatch("loaded", { detail: { warnings: result.warnings } })
|
|
34
|
+
} catch (err) {
|
|
35
|
+
this.dispatch("error", { detail: { error: err } })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
disconnect() {
|
|
41
|
+
if (this.viewer) {
|
|
42
|
+
this.viewer.destroy()
|
|
43
|
+
this.viewer = null
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="dmn-js-modeler"
|
|
4
|
+
//
|
|
5
|
+
// Values:
|
|
6
|
+
// xml (String) - the DMN 1.3 XML to load into the modeler
|
|
7
|
+
//
|
|
8
|
+
// Targets:
|
|
9
|
+
// container - the child div that dmn-js renders into (falls back to this.element)
|
|
10
|
+
// xmlField - a hidden <input> that receives the serialised XML
|
|
11
|
+
// on every change and before form submission
|
|
12
|
+
//
|
|
13
|
+
// Lifecycle:
|
|
14
|
+
// connect: creates the DmnJS modeler instance, imports XML, wires change + submit sync
|
|
15
|
+
// disconnect: destroys the modeler
|
|
16
|
+
//
|
|
17
|
+
// Events dispatched on the controller element:
|
|
18
|
+
// dmn-js-modeler:ready - { detail: { modeler } }
|
|
19
|
+
// dmn-js-modeler:changed - after XML sync
|
|
20
|
+
//
|
|
21
|
+
export default class extends Controller {
|
|
22
|
+
static values = {
|
|
23
|
+
xml: { type: String, default: "" }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static targets = ["container", "xmlField"]
|
|
27
|
+
|
|
28
|
+
async connect() {
|
|
29
|
+
if (typeof DmnJS === "undefined") {
|
|
30
|
+
console.error("[bpmn-js-rails] DmnJS global not found. Make sure dmn-modeler.production.min.js is loaded.")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const renderTarget = this.hasContainerTarget ? this.containerTarget : this.element
|
|
35
|
+
|
|
36
|
+
this.modeler = new DmnJS({
|
|
37
|
+
container: renderTarget,
|
|
38
|
+
keyboard: { bindTo: window }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (this.xmlValue) {
|
|
42
|
+
try {
|
|
43
|
+
await this.modeler.importXML(this.xmlValue)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error("[bpmn-js-rails] Failed to import DMN XML:", err)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Listen for changes on the active viewer's command stack
|
|
50
|
+
this.modeler.on("views.changed", ({ activeView }) => {
|
|
51
|
+
const activeViewer = this.modeler.getActiveViewer()
|
|
52
|
+
if (activeViewer && this._currentViewer !== activeViewer) {
|
|
53
|
+
this._currentViewer = activeViewer
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const eventBus = activeViewer.get("eventBus")
|
|
57
|
+
eventBus.on("commandStack.changed", () => {
|
|
58
|
+
this._syncXmlField()
|
|
59
|
+
this.dispatch("changed")
|
|
60
|
+
})
|
|
61
|
+
} catch (_) {
|
|
62
|
+
// some view types may not have a commandStack
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Also sync right before the enclosing <form> submits
|
|
68
|
+
this._form = this.element.closest("form")
|
|
69
|
+
if (this._form) {
|
|
70
|
+
this._boundSubmitHandler = () => this._syncXmlField()
|
|
71
|
+
this._form.addEventListener("submit", this._boundSubmitHandler)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.dispatch("ready", { detail: { modeler: this.modeler } })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnect() {
|
|
78
|
+
if (this._form && this._boundSubmitHandler) {
|
|
79
|
+
this._form.removeEventListener("submit", this._boundSubmitHandler)
|
|
80
|
+
this._boundSubmitHandler = null
|
|
81
|
+
this._form = null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this._currentViewer = null
|
|
85
|
+
|
|
86
|
+
if (this.modeler) {
|
|
87
|
+
this.modeler.destroy()
|
|
88
|
+
this.modeler = null
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Serialise the current modeler XML into the hidden field target.
|
|
93
|
+
async _syncXmlField() {
|
|
94
|
+
if (!this.modeler || !this.hasXmlFieldTarget) return
|
|
95
|
+
try {
|
|
96
|
+
const { xml } = await this.modeler.saveXML({ format: true })
|
|
97
|
+
this.xmlFieldTarget.value = xml
|
|
98
|
+
} catch (_) {
|
|
99
|
+
// modeler may be mid-import; ignore
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="dmn-js-viewer"
|
|
4
|
+
//
|
|
5
|
+
// Values:
|
|
6
|
+
// xml (String) - the DMN 1.3 XML to display
|
|
7
|
+
//
|
|
8
|
+
// Lifecycle:
|
|
9
|
+
// connect: creates the DmnJS viewer instance, imports XML
|
|
10
|
+
// disconnect: destroys it (cleans up DOM + listeners)
|
|
11
|
+
//
|
|
12
|
+
// Events dispatched on the controller element:
|
|
13
|
+
// dmn-js-viewer:loaded - { detail: { warnings } }
|
|
14
|
+
// dmn-js-viewer:error - { detail: { error } }
|
|
15
|
+
//
|
|
16
|
+
export default class extends Controller {
|
|
17
|
+
static values = {
|
|
18
|
+
xml: { type: String, default: "" }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async connect() {
|
|
22
|
+
if (typeof DmnJS === "undefined") {
|
|
23
|
+
console.error("[bpmn-js-rails] DmnJS global not found. Make sure dmn-viewer.production.min.js or dmn-modeler.production.min.js is loaded.")
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
this.viewer = new DmnJS({ container: this.element })
|
|
28
|
+
|
|
29
|
+
if (this.xmlValue) {
|
|
30
|
+
try {
|
|
31
|
+
const result = await this.viewer.importXML(this.xmlValue)
|
|
32
|
+
this.dispatch("loaded", { detail: { warnings: result.warnings } })
|
|
33
|
+
} catch (err) {
|
|
34
|
+
this.dispatch("error", { detail: { error: err } })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
disconnect() {
|
|
40
|
+
if (this.viewer) {
|
|
41
|
+
this.viewer.destroy()
|
|
42
|
+
this.viewer = null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
// Connects to data-controller="form-js-editor"
|
|
4
|
+
//
|
|
5
|
+
// Values:
|
|
6
|
+
// schema (Object) - the form-js JSON schema to load into the editor
|
|
7
|
+
//
|
|
8
|
+
// Targets:
|
|
9
|
+
// container - the child div that form-js renders into (falls back to this.element)
|
|
10
|
+
// schemaField - a hidden <input> that receives the serialised schema JSON
|
|
11
|
+
// on every change and before form submission
|
|
12
|
+
//
|
|
13
|
+
// Lifecycle:
|
|
14
|
+
// connect: creates the FormEditor instance, wires change + submit sync
|
|
15
|
+
// disconnect: destroys the editor
|
|
16
|
+
//
|
|
17
|
+
// Events dispatched on the controller element:
|
|
18
|
+
// form-js:editor:ready - { detail: { editor } }
|
|
19
|
+
// form-js:editor:changed - after schema sync
|
|
20
|
+
//
|
|
21
|
+
export default class extends Controller {
|
|
22
|
+
static values = {
|
|
23
|
+
schema: { type: Object, default: { type: "default", components: [] } }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static targets = ["container", "schemaField"]
|
|
27
|
+
|
|
28
|
+
connect() {
|
|
29
|
+
if (typeof FormEditor === "undefined") {
|
|
30
|
+
console.error("[bpmn-js-rails] FormEditor global not found. Make sure form-editor.umd.js is loaded.")
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const renderTarget = this.hasContainerTarget ? this.containerTarget : this.element
|
|
35
|
+
|
|
36
|
+
FormEditor.createFormEditor({
|
|
37
|
+
container: renderTarget,
|
|
38
|
+
schema: this.schemaValue
|
|
39
|
+
}).then((editor) => {
|
|
40
|
+
this.editor = editor
|
|
41
|
+
|
|
42
|
+
editor.on("changed", () => {
|
|
43
|
+
this._syncSchemaField()
|
|
44
|
+
this.dispatch("changed")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// Also sync right before the enclosing <form> submits
|
|
48
|
+
this._form = this.element.closest("form")
|
|
49
|
+
if (this._form) {
|
|
50
|
+
this._boundSubmitHandler = () => this._syncSchemaField()
|
|
51
|
+
this._form.addEventListener("submit", this._boundSubmitHandler)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.dispatch("ready", { detail: { editor } })
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
disconnect() {
|
|
59
|
+
if (this._form && this._boundSubmitHandler) {
|
|
60
|
+
this._form.removeEventListener("submit", this._boundSubmitHandler)
|
|
61
|
+
this._boundSubmitHandler = null
|
|
62
|
+
this._form = null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (this.editor) {
|
|
66
|
+
this.editor.destroy()
|
|
67
|
+
this.editor = null
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Serialise the current editor schema into the hidden field target.
|
|
72
|
+
_syncSchemaField() {
|
|
73
|
+
if (!this.editor || !this.hasSchemaFieldTarget) return
|
|
74
|
+
try {
|
|
75
|
+
this.schemaFieldTarget.value = JSON.stringify(this.editor.saveSchema())
|
|
76
|
+
} catch (_) {
|
|
77
|
+
// editor may be mid-import; ignore
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|