glib-web 7.0.0 → 7.0.1.beta1
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 +4 -4
- data/app/controllers/concerns/glib/json/libs.rb +0 -33
- data/app/helpers/glib/json_ui/view_builder/panels.rb +144 -8
- data/app/models/concerns/glib/snapshot_v2.rb +9 -38
- data/app/views/json_ui/garage/tables/bulk_edit.json.jbuilder +6 -6
- data/app/views/json_ui/garage/test_page/_header.json.jbuilder +1 -0
- data/app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder +244 -0
- data/app/views/json_ui/garage/test_page/panels_bulkEdit3.json.jbuilder +2 -2
- data/lib/glib/json_crawler/action_crawlers/forms_submit.rb +1 -1
- data/lib/glib/test_helpers.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a88117fbaf57f748a21abf1ea95fc18fdec09171c7b93e87ac8ef5e02850f93
|
|
4
|
+
data.tar.gz: d37adcb5e04c61400b5f073ada5a65d0cd5e5f39b62df49c3a28c93c93b06a41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a47b8e5826f8ac505d0e38b4530a9294140dcd8e5280178c636e2abe78d38d4c323a04d02e0915aec9bcaf73441fecb90d5d0f89650059e7cdc9a7658f737b56
|
|
7
|
+
data.tar.gz: 7c8ce8df263b855e5a09cd4e42d5e0bf039c0b24d9d08b1f6f6767e5d8857f4cd33d1f9b5a272f3a4c8b8aa16291ff26c57f4dfa350d8881d5b79129dd77f2be
|
|
@@ -96,18 +96,6 @@ module Glib::Json::Libs
|
|
|
96
96
|
|
|
97
97
|
json_ui_redirect_to url, status: status
|
|
98
98
|
end
|
|
99
|
-
# Any other request format (e.g. a browser tab opening a protected file link
|
|
100
|
-
# `/temp_blobs/:id/report.pdf`, where the filename sets the format) used to raise
|
|
101
|
-
# ActionController::UnknownFormat -> a bare 406 with an empty body. Mirror the html
|
|
102
|
-
# branch instead: whatever opened the URL (browser tab, iframe, img) is redirected
|
|
103
|
-
# to the target, which the browser follows natively.
|
|
104
|
-
#
|
|
105
|
-
# MUST stay the LAST block. `negotiate_mime` answers `Accept: */*` requests with
|
|
106
|
-
# the FIRST declared format, so an `any` block placed before html would capture
|
|
107
|
-
# ordinary browser page loads and leave request.format as `*/*` for the action.
|
|
108
|
-
format.any do
|
|
109
|
-
redirect_to url
|
|
110
|
-
end
|
|
111
99
|
end
|
|
112
100
|
end
|
|
113
101
|
|
|
@@ -154,17 +142,6 @@ module Glib::Json::Libs
|
|
|
154
142
|
redirect_to sign_in_url, **redirect_options
|
|
155
143
|
end
|
|
156
144
|
end
|
|
157
|
-
# Other non-HTML/JSON formats (a protected file link whose filename sets the
|
|
158
|
-
# request format, an XML export, ...) used to raise UnknownFormat -> a bare 406.
|
|
159
|
-
# Mirrors the csv branch: a clean gate status in test, a redirect in production.
|
|
160
|
-
# MUST stay the LAST block -- see glib_redirect_to.
|
|
161
|
-
format.any do
|
|
162
|
-
if Rails.env.test?
|
|
163
|
-
head :unauthorized
|
|
164
|
-
else
|
|
165
|
-
redirect_to sign_in_url, **redirect_options
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
145
|
end
|
|
169
146
|
end
|
|
170
147
|
|
|
@@ -208,16 +185,6 @@ module Glib::Json::Libs
|
|
|
208
185
|
redirect_to user_default_url
|
|
209
186
|
end
|
|
210
187
|
end
|
|
211
|
-
# Other non-HTML/JSON formats -- mirrors the csv branch (clean gate status in
|
|
212
|
-
# test, redirect in production); previously raised UnknownFormat -> a bare 406.
|
|
213
|
-
# MUST stay the LAST block -- see glib_redirect_to.
|
|
214
|
-
format.any do
|
|
215
|
-
if Rails.env.test?
|
|
216
|
-
head :forbidden
|
|
217
|
-
else
|
|
218
|
-
redirect_to user_default_url
|
|
219
|
-
end
|
|
220
|
-
end
|
|
221
188
|
end
|
|
222
189
|
end
|
|
223
190
|
|
|
@@ -493,23 +493,129 @@ class Glib::JsonUi::ViewBuilder
|
|
|
493
493
|
end
|
|
494
494
|
end
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
# viewCells/viewHeaders, plus the full parse/gate/dedup config so any
|
|
498
|
-
# consumer can compose specialized import behavior (it replaced the
|
|
499
|
-
# retired bulkEdit2 and bulkEditSg -- see the component's header comment
|
|
500
|
-
# for the full mapping).
|
|
501
|
-
class BulkEdit3 < View
|
|
496
|
+
class BulkEdit2 < View
|
|
502
497
|
hash :import, required: [:submitUrl, :paramName]
|
|
503
498
|
|
|
504
499
|
# Override
|
|
505
500
|
def created
|
|
506
|
-
#
|
|
501
|
+
# bulkEdit2 is a form PEER, not a form child: it answers the same
|
|
507
502
|
# forms_submit action a form does (forms/directSubmit) and submits its
|
|
508
503
|
# rows itself to `import.submitUrl`. Inside a form, its repeated
|
|
509
504
|
# per-row input names would collide with the form's FormData scans
|
|
510
505
|
# (duplicate-name warnings, dirty-baseline pollution) while adding
|
|
511
506
|
# nothing to the form's submission -- so, like a nested form, reject
|
|
512
507
|
# the arrangement at render time.
|
|
508
|
+
if page.current_form
|
|
509
|
+
raise 'panels_bulkEdit2 is not allowed inside a form; place it as a sibling and target it with forms_submit targetId'
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Maximum number of data rows an uploaded file may contain; files with
|
|
514
|
+
# more rows are rejected with an error dialog. No limit when unset.
|
|
515
|
+
int :maxRows
|
|
516
|
+
|
|
517
|
+
# string :statusViewIdPrefix
|
|
518
|
+
array :viewHeaders
|
|
519
|
+
|
|
520
|
+
# string :paramNameForFormData
|
|
521
|
+
string :paramNameForRowId
|
|
522
|
+
action :onCellChange
|
|
523
|
+
action :onRowsLoad
|
|
524
|
+
action :onRowsSelect
|
|
525
|
+
action :onRowsSucceed
|
|
526
|
+
action :onSubmitStart
|
|
527
|
+
action :onSubmitEnd
|
|
528
|
+
|
|
529
|
+
# Experimental
|
|
530
|
+
# panels_builder :accessory, :header, :footer
|
|
531
|
+
|
|
532
|
+
def viewCells(values)
|
|
533
|
+
json.set! 'viewCells' do
|
|
534
|
+
values.each do |value|
|
|
535
|
+
value.call(page.view_builder)
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def dataRows(value)
|
|
541
|
+
json.dataRows value
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def viewRows(value)
|
|
545
|
+
json.viewRows value
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# The Surfguard members-import table: a fork of bulkEdit2's builder for
|
|
551
|
+
# glib-web-npm's bulkEditSg component (same skeleton — form-peer
|
|
552
|
+
# submission, per-row POSTs, viewCells/viewHeaders — plus the Surfguard
|
|
553
|
+
# parse inputs). Deliberately SG-specific; consolidate with BulkEdit2 when
|
|
554
|
+
# a second consumer proves which props generalize.
|
|
555
|
+
class BulkEditSg < View
|
|
556
|
+
hash :import, required: [:submitUrl, :paramName]
|
|
557
|
+
|
|
558
|
+
# Override
|
|
559
|
+
def created
|
|
560
|
+
# Same form-peer rule as bulkEdit2 (see its comment).
|
|
561
|
+
if page.current_form
|
|
562
|
+
raise 'panels_bulkEditSg is not allowed inside a form; place it as a sibling and target it with forms_submit targetId'
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# Maximum number of data rows an uploaded file may contain; files with
|
|
567
|
+
# more rows are rejected with an error dialog. No limit when unset.
|
|
568
|
+
int :maxRows
|
|
569
|
+
|
|
570
|
+
array :viewHeaders
|
|
571
|
+
|
|
572
|
+
# string :paramNameForFormData
|
|
573
|
+
string :paramNameForRowId
|
|
574
|
+
action :onCellChange
|
|
575
|
+
action :onRowsLoad
|
|
576
|
+
action :onRowsSelect
|
|
577
|
+
action :onRowsSucceed
|
|
578
|
+
# Fires when non-importable rows drain out of the table on save (they
|
|
579
|
+
# never POST), so the page can keep its Loaded/Uploaded/Skipped
|
|
580
|
+
# arithmetic honest.
|
|
581
|
+
action :onRowsSkip
|
|
582
|
+
action :onSubmitStart
|
|
583
|
+
action :onSubmitEnd
|
|
584
|
+
|
|
585
|
+
# Surfguard file shape: CSV header => form field. The parser follows
|
|
586
|
+
# this map, so a header-spelling fix is a one-line server change.
|
|
587
|
+
hash :headerMap
|
|
588
|
+
array :requiredHeaders
|
|
589
|
+
string :statusFilter
|
|
590
|
+
|
|
591
|
+
def viewCells(values)
|
|
592
|
+
json.set! 'viewCells' do
|
|
593
|
+
values.each do |value|
|
|
594
|
+
value.call(page.view_builder)
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def dataRows(value)
|
|
600
|
+
json.dataRows value
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def viewRows(value)
|
|
604
|
+
json.viewRows value
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# The generic bulk-edit table: bulkEdit2's skeleton plus everything
|
|
610
|
+
# bulkEditSg hard-coded for the Surfguard imports, expressed as config so
|
|
611
|
+
# any consumer can compose the same behavior (bulkEditSg's replacement --
|
|
612
|
+
# see the component's header comment for the full mapping).
|
|
613
|
+
class BulkEdit3 < View
|
|
614
|
+
hash :import, required: [:submitUrl, :paramName]
|
|
615
|
+
|
|
616
|
+
# Override
|
|
617
|
+
def created
|
|
618
|
+
# Same form-peer rule as bulkEdit2 (see its comment).
|
|
513
619
|
if page.current_form
|
|
514
620
|
raise 'panels_bulkEdit3 is not allowed inside a form; place it as a sibling and target it with forms_submit targetId'
|
|
515
621
|
end
|
|
@@ -535,7 +641,7 @@ class Glib::JsonUi::ViewBuilder
|
|
|
535
641
|
action :onSubmitEnd
|
|
536
642
|
|
|
537
643
|
# The file shape. Without it the table takes plain CSVs (header row
|
|
538
|
-
# first, header names = field ids):
|
|
644
|
+
# first, header names = field ids -- the bulkEdit2 shape):
|
|
539
645
|
# detectHeader: { allOf: [...], anyOf: [...], scanRows: N } -- find
|
|
540
646
|
# the header row BY CONTENT (report preambles survive);
|
|
541
647
|
# headerMap: { csvHeader => field } -- header-keyed, order-independent
|
|
@@ -610,6 +716,36 @@ class Glib::JsonUi::ViewBuilder
|
|
|
610
716
|
|
|
611
717
|
end
|
|
612
718
|
|
|
719
|
+
# deprecated
|
|
720
|
+
class BulkEdit < View
|
|
721
|
+
hash :nextPage
|
|
722
|
+
hash :export, required: [:label, :fileName]
|
|
723
|
+
hash :import, required: [:submitUrl, :paramName]
|
|
724
|
+
action :onScrollToTop
|
|
725
|
+
action :onScrollToBottom
|
|
726
|
+
string :statusViewIdPrefix
|
|
727
|
+
action :onLoadRows
|
|
728
|
+
|
|
729
|
+
# Experimental
|
|
730
|
+
panels_builder :accessory, :header, :footer
|
|
731
|
+
|
|
732
|
+
def firstSection(block)
|
|
733
|
+
json.sections [1] do
|
|
734
|
+
block.call page.table_section_builder
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def sections(blocks)
|
|
739
|
+
json.sections do
|
|
740
|
+
blocks.each do |block|
|
|
741
|
+
json.child! do
|
|
742
|
+
block.call page.table_section_builder
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
end
|
|
748
|
+
|
|
613
749
|
# Scrollable container panel for content that exceeds viewport height.
|
|
614
750
|
#
|
|
615
751
|
# Scroll panels automatically add scrollbars when content overflows.
|
|
@@ -12,9 +12,7 @@ require 'hashdiff'
|
|
|
12
12
|
# 4. ActiveStorage in diff_associations (convert Attached::One/Many to attachment records).
|
|
13
13
|
# 5. same_as_before? derives from the computed diff instead of the instance-local
|
|
14
14
|
# `snapshot_changed` flag (mapping-web's flag-gating silently dropped a target's
|
|
15
|
-
# snapshot when taken after the owner's -- see "Snapshot ordering" below, issue #481)
|
|
16
|
-
# with the flag re-entering as a NEGATIVE signal for first snapshots only (see
|
|
17
|
-
# `same_as_before?` for why an empty association baseline cannot decide those).
|
|
15
|
+
# snapshot when taken after the owner's -- see "Snapshot ordering" below, issue #481).
|
|
18
16
|
#
|
|
19
17
|
# ## Snapshot ordering: owner vs association targets
|
|
20
18
|
#
|
|
@@ -29,8 +27,7 @@ require 'hashdiff'
|
|
|
29
27
|
# reading the target back through the association snapshots the persisted state instead.
|
|
30
28
|
#
|
|
31
29
|
# `snapshot_changed` / `attributes_before_save` remain writable instance accessors for
|
|
32
|
-
# call-site use
|
|
33
|
-
# signal on first snapshots (see `same_as_before?`).
|
|
30
|
+
# call-site use, but they are no longer consulted for the skip decision.
|
|
34
31
|
#
|
|
35
32
|
# To get all records of a model, you can use the following query:
|
|
36
33
|
# MyModel.joins(:snapshots).distinct
|
|
@@ -63,16 +60,7 @@ module Glib
|
|
|
63
60
|
# Only set snapshot_changed if not already set (handles case where before_save
|
|
64
61
|
# is called multiple times - e.g., when associated records are saved before parent)
|
|
65
62
|
if snapshot_changed.nil?
|
|
66
|
-
|
|
67
|
-
# unchanged persisted record has no database-side delta -- which would make a
|
|
68
|
-
# first snapshot diff every attribute as an addition. Capture the full
|
|
69
|
-
# persisted state instead, so an unchanged save yields an empty item diff and
|
|
70
|
-
# the nil-guard keeps that baseline stable for later changes on the same
|
|
71
|
-
# instance. New records keep `attributes_in_database` (their all-nil
|
|
72
|
-
# originals): that non-empty baseline is exactly why a create's version 1
|
|
73
|
-
# records the full initial state. `unless` keeps the accessor nil, never
|
|
74
|
-
# `false`, for call-site nil-safety.
|
|
75
|
-
self.attributes_before_save = attributes_in_database.presence || (attributes unless new_record?)
|
|
63
|
+
self.attributes_before_save = attributes_in_database
|
|
76
64
|
self.snapshot_changed = check_snapshot_changed
|
|
77
65
|
end
|
|
78
66
|
end
|
|
@@ -137,10 +125,8 @@ module Glib
|
|
|
137
125
|
}
|
|
138
126
|
|
|
139
127
|
# Don't create version if same as before. Reuses `calculated_diff` instead of
|
|
140
|
-
# recomputing it inside `same_as_before?`.
|
|
141
|
-
|
|
142
|
-
# association baseline is empty and the flag carries decision weight.
|
|
143
|
-
unless same_as_before?(calculated_diff, first_snapshot: version == 1)
|
|
128
|
+
# recomputing it inside `same_as_before?`.
|
|
129
|
+
unless same_as_before?(calculated_diff)
|
|
144
130
|
# No nil guard: active_snapshot's `create_snapshot!` raises on failure and
|
|
145
131
|
# always returns the snapshot.
|
|
146
132
|
snapshot = create_snapshot!(**snapshot_obj)
|
|
@@ -200,28 +186,13 @@ module Glib
|
|
|
200
186
|
end
|
|
201
187
|
|
|
202
188
|
# Decides whether `glib_create_snapshot!` should skip the version write. Derived from
|
|
203
|
-
# the computed diff (persisted state),
|
|
189
|
+
# the computed diff (persisted state), never from the instance-local `snapshot_changed`
|
|
204
190
|
# flag: the flag dies with its instance, and an owner's snapshot replaces association
|
|
205
191
|
# targets with fresh instances (`with_lock`'s reload clears the association cache, then
|
|
206
192
|
# the diff walk re-reads the targets), so flag-gating used to silently drop a target's
|
|
207
|
-
# follow-up snapshot taken through the association (issue #481).
|
|
208
|
-
#
|
|
209
|
-
|
|
210
|
-
# meaningless. With no previous version, `fetch_snapshot_items(nil)` returns `{}`
|
|
211
|
-
# for associations, so every pre-existing child row reads as `+` and the diff alone
|
|
212
|
-
# can never prove a no-op — any never-snapshotted record with children would mint a
|
|
213
|
-
# version on every snapshot call (observed as spurious "Edited" audit entries on
|
|
214
|
-
# routine re-saves). There the flag is consulted as a NEGATIVE signal:
|
|
215
|
-
# - `false` — this instance crossed the before_save boundary and
|
|
216
|
-
# `check_snapshot_changed` found no item or child change, and the item diff is
|
|
217
|
-
# net-zero against `attributes_before_save` => skip;
|
|
218
|
-
# - `nil` — no save boundary on this instance (an owner-triggered snapshot of a
|
|
219
|
-
# replaced association target, issue #481) => mint;
|
|
220
|
-
# - `true` — a change was detected at the save boundary; falls through to the
|
|
221
|
-
# diff-based decision (a reverted change still no-ops).
|
|
222
|
-
def same_as_before?(computed_diff = diff, first_snapshot: false)
|
|
223
|
-
return true if first_snapshot && snapshot_changed == false && computed_diff['item'].blank?
|
|
224
|
-
|
|
193
|
+
# follow-up snapshot taken through the association (issue #481). For a first snapshot
|
|
194
|
+
# (no previous version) the diff runs current state against `attributes_before_save || {}`.
|
|
195
|
+
def same_as_before?(computed_diff = diff)
|
|
225
196
|
item_unchanged = computed_diff['item'].blank?
|
|
226
197
|
assoc_diff = computed_diff['associations']
|
|
227
198
|
associations_unchanged = assoc_diff.nil? || assoc_diff.values.all?(&:blank?)
|
|
@@ -37,17 +37,17 @@ page.scroll childViews: ->(scroll) do
|
|
|
37
37
|
displayed: false
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
wres.
|
|
40
|
+
wres.panels_bulkEdit2 \
|
|
41
41
|
id: 'import_table',
|
|
42
42
|
width: 'matchParent',
|
|
43
43
|
paramNameForRowId: 'row_id',
|
|
44
44
|
import: { submitUrl: json_ui_garage_url(path: 'forms/bulk_edit_post2'), paramName: 'user' },
|
|
45
45
|
viewHeaders: [
|
|
46
|
-
{ text: 'Month', id: 'month', tooltip: { text: 'Month', placement: 'top' } },
|
|
47
|
-
{ text: 'Electricity usage', id: 'electricity_usage' },
|
|
48
|
-
{ text: 'Gas usage', id: 'gas_usage' },
|
|
49
|
-
{ text: 'Sources', id: 'sources' },
|
|
50
|
-
{ text: 'Compliant', id: 'compliant' }
|
|
46
|
+
{ text: 'Month', id: 'month', importable: true, tooltip: { text: 'Month', placement: 'top' } },
|
|
47
|
+
{ text: 'Electricity usage', id: 'electricity_usage', importable: true },
|
|
48
|
+
{ text: 'Gas usage', id: 'gas_usage', importable: true },
|
|
49
|
+
{ text: 'Sources', id: 'sources', importable: true },
|
|
50
|
+
{ text: 'Compliant', id: 'compliant', importable: true }
|
|
51
51
|
],
|
|
52
52
|
viewCells: [
|
|
53
53
|
->(view) do
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
json.title 'Test Page (Panels Bulk Edit 2)'
|
|
2
|
+
|
|
3
|
+
page = json_ui_page json
|
|
4
|
+
|
|
5
|
+
options = [{ text: 'yes', value: 'yes' }, { text: 'no', value: 'no' }]
|
|
6
|
+
source_options = ['Generators', 'Power Station', 'Solar Panels'].map do |source|
|
|
7
|
+
{ text: source, value: source }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
default_headers = [
|
|
11
|
+
{ text: 'Month', id: 'month', importable: true, tooltip: { text: 'Month', placement: 'top' } },
|
|
12
|
+
{ text: 'Electricity usage', id: 'electricity_usage', importable: true },
|
|
13
|
+
{ text: 'Gas usage', id: 'gas_usage', importable: true },
|
|
14
|
+
{ text: 'Sources', id: 'sources', importable: true },
|
|
15
|
+
{ text: 'Compliant', id: 'compliant', importable: true }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
default_cells = [
|
|
19
|
+
->(view) do
|
|
20
|
+
view.fields_number width: 'matchParent'
|
|
21
|
+
end,
|
|
22
|
+
->(view) do
|
|
23
|
+
view.fields_number width: 'matchParent'
|
|
24
|
+
end,
|
|
25
|
+
->(view) do
|
|
26
|
+
view.fields_number width: 'matchParent'
|
|
27
|
+
end,
|
|
28
|
+
->(view) do
|
|
29
|
+
view.fields_select width: 'matchParent', options: source_options, multiple: true
|
|
30
|
+
end,
|
|
31
|
+
->(view) do
|
|
32
|
+
view.fields_select width: 'matchParent', options: options
|
|
33
|
+
end
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[
|
|
37
|
+
{ text: 'Month', id: 'month', importable: true },
|
|
38
|
+
{ text: 'Usage (kWh)', id: 'electricity_usage', importable: true },
|
|
39
|
+
{ text: 'Usage (Therms)', id: 'gas_usage', importable: true }
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[
|
|
43
|
+
->(view) do
|
|
44
|
+
view.fields_number width: 120
|
|
45
|
+
end,
|
|
46
|
+
->(view) do
|
|
47
|
+
view.fields_number width: 140
|
|
48
|
+
end,
|
|
49
|
+
->(view) do
|
|
50
|
+
view.fields_number width: 140
|
|
51
|
+
end
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
import_settings = { submitUrl: json_ui_garage_url(path: 'forms/bulk_edit_post2'), paramName: 'user' }
|
|
55
|
+
|
|
56
|
+
render 'json_ui/garage/test_page/header', json: json, page: page
|
|
57
|
+
|
|
58
|
+
page.body(
|
|
59
|
+
childViews: ->(body) do
|
|
60
|
+
body.panels_responsive(
|
|
61
|
+
padding: glib_json_padding_body,
|
|
62
|
+
childViews: ->(res) do
|
|
63
|
+
res.h2 text: 'Overview'
|
|
64
|
+
res.p text: 'panels_bulkEdit2 renders an editable grid with import-backed rows and bulk submission events.'
|
|
65
|
+
res.spacer height: 12
|
|
66
|
+
res.hr width: 'matchParent'
|
|
67
|
+
res.h2 text: 'Basic example'
|
|
68
|
+
res.spacer height: 8
|
|
69
|
+
res.panels_horizontal(
|
|
70
|
+
align: 'middle',
|
|
71
|
+
childViews: ->(horizontal) do
|
|
72
|
+
horizontal.label text: 'Loaded: '
|
|
73
|
+
horizontal.label id: 'bulk_edit_loaded_count', text: '0'
|
|
74
|
+
horizontal.spacer width: 12
|
|
75
|
+
horizontal.label text: 'Selected: '
|
|
76
|
+
horizontal.label id: 'bulk_edit_selected_count', text: '0'
|
|
77
|
+
horizontal.spacer width: 12
|
|
78
|
+
horizontal.chip id: 'bulk_edit_succeeded_count', styleClass: 'success', text: 'Uploaded: 0', displayed: false
|
|
79
|
+
end
|
|
80
|
+
)
|
|
81
|
+
res.spacer height: 8
|
|
82
|
+
res.panels_bulkEdit2(
|
|
83
|
+
id: 'bulk_edit_main',
|
|
84
|
+
width: 'matchParent',
|
|
85
|
+
paramNameForRowId: 'row_id',
|
|
86
|
+
# Files with more data rows than this are rejected wholesale with an
|
|
87
|
+
# error dialog (the header row doesn't count).
|
|
88
|
+
maxRows: 3,
|
|
89
|
+
import: import_settings,
|
|
90
|
+
viewHeaders: default_headers,
|
|
91
|
+
viewCells: default_cells,
|
|
92
|
+
onCellChange: ->(action) do
|
|
93
|
+
action.snackbars_alert debug: true, paramName: 'message', fieldName: 'row'
|
|
94
|
+
end,
|
|
95
|
+
onSubmitStart: ->(action) do
|
|
96
|
+
action.runMultiple(
|
|
97
|
+
childActions: ->(multiple) do
|
|
98
|
+
multiple.logics_set targetId: 'bulk_edit_submit_top', data: { disabled: true }
|
|
99
|
+
multiple.logics_set targetId: 'bulk_edit_submit_bottom', data: { disabled: true }
|
|
100
|
+
multiple.logics_set targetId: 'bulk_edit_cancel', data: { displayed: true }
|
|
101
|
+
end
|
|
102
|
+
)
|
|
103
|
+
end,
|
|
104
|
+
onSubmitEnd: ->(action) do
|
|
105
|
+
action.runMultiple(
|
|
106
|
+
childActions: ->(multiple) do
|
|
107
|
+
multiple.logics_set targetId: 'bulk_edit_submit_top', data: { disabled: false }
|
|
108
|
+
multiple.logics_set targetId: 'bulk_edit_submit_bottom', data: { disabled: false }
|
|
109
|
+
multiple.logics_set targetId: 'bulk_edit_cancel', data: { displayed: false }
|
|
110
|
+
end
|
|
111
|
+
)
|
|
112
|
+
end,
|
|
113
|
+
onRowsLoad: ->(action) do
|
|
114
|
+
action.runMultiple(
|
|
115
|
+
debug: true,
|
|
116
|
+
fieldName: 'data',
|
|
117
|
+
childActions: ->(multiple) do
|
|
118
|
+
multiple.logics_set(
|
|
119
|
+
targetId: 'bulk_edit_loaded_count',
|
|
120
|
+
debug: true,
|
|
121
|
+
paramName: 'variables',
|
|
122
|
+
fieldName: 'data',
|
|
123
|
+
conditionalData: {
|
|
124
|
+
text: { "count": [{ "var": 'data.loadedRows' }] }
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
multiple.logics_set targetId: 'bulk_edit_submit_top', data: { displayed: true }
|
|
128
|
+
multiple.logics_set targetId: 'bulk_edit_submit_bottom', data: { displayed: true }
|
|
129
|
+
end
|
|
130
|
+
)
|
|
131
|
+
end,
|
|
132
|
+
onRowsSelect: ->(action) do
|
|
133
|
+
action.logics_set(
|
|
134
|
+
targetId: 'bulk_edit_selected_count',
|
|
135
|
+
debug: true,
|
|
136
|
+
paramName: 'variables',
|
|
137
|
+
fieldName: 'data',
|
|
138
|
+
conditionalData: {
|
|
139
|
+
text: { "count": [{ "var": 'data.selectedRows' }] }
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
end,
|
|
143
|
+
onRowsSucceed: ->(action) do
|
|
144
|
+
action.logics_set(
|
|
145
|
+
targetId: 'bulk_edit_succeeded_count',
|
|
146
|
+
debug: true,
|
|
147
|
+
paramName: 'variables',
|
|
148
|
+
fieldName: 'data',
|
|
149
|
+
conditionalData: {
|
|
150
|
+
text: {
|
|
151
|
+
"printf": [
|
|
152
|
+
'Uploaded: {0}',
|
|
153
|
+
{ "count": [{ "var": 'data.succeededRows' }] }
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
displayed: true
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
)
|
|
161
|
+
res.spacer height: 12
|
|
162
|
+
res.panels_flow(
|
|
163
|
+
innerPadding: { bottom: 0 },
|
|
164
|
+
childViews: ->(flow) do
|
|
165
|
+
flow.button(
|
|
166
|
+
id: 'bulk_edit_submit_top',
|
|
167
|
+
text: 'Submit (top)',
|
|
168
|
+
displayed: false,
|
|
169
|
+
onClick: ->(action) do
|
|
170
|
+
action.forms_submit targetId: 'bulk_edit_main'
|
|
171
|
+
end
|
|
172
|
+
)
|
|
173
|
+
flow.spacer width: 8
|
|
174
|
+
flow.button(
|
|
175
|
+
id: 'bulk_edit_submit_bottom',
|
|
176
|
+
text: 'Submit (bottom)',
|
|
177
|
+
displayed: false,
|
|
178
|
+
onClick: ->(action) do
|
|
179
|
+
action.forms_submit targetId: 'bulk_edit_main'
|
|
180
|
+
end
|
|
181
|
+
)
|
|
182
|
+
flow.spacer width: 8
|
|
183
|
+
flow.button(
|
|
184
|
+
id: 'bulk_edit_cancel',
|
|
185
|
+
text: 'Cancel import',
|
|
186
|
+
displayed: false,
|
|
187
|
+
onClick: ->(action) do
|
|
188
|
+
action.components_invoke targetId: 'bulk_edit_main', name: 'cancelImport'
|
|
189
|
+
end
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
)
|
|
193
|
+
res.spacer height: 12
|
|
194
|
+
res.hr width: 'matchParent'
|
|
195
|
+
# res.h2 text: 'Variants/props'
|
|
196
|
+
# res.spacer height: 8
|
|
197
|
+
# res.panels_flow innerPadding: { bottom: 0 }, childViews: ->(flow) do
|
|
198
|
+
# flow.button text: 'Default columns', onClick: ->(action) do
|
|
199
|
+
# action.logics_set targetId: 'bulk_edit_main', data: { viewHeaders: default_headers, viewCells: default_cells }
|
|
200
|
+
# end
|
|
201
|
+
# flow.spacer width: 8
|
|
202
|
+
# flow.button text: 'Compact columns', onClick: ->(action) do
|
|
203
|
+
# action.logics_set targetId: 'bulk_edit_main', data: { viewHeaders: compact_headers, viewCells: compact_cells }
|
|
204
|
+
# end
|
|
205
|
+
# flow.spacer width: 8
|
|
206
|
+
# flow.button text: 'Reset counts', onClick: ->(action) do
|
|
207
|
+
# action.runMultiple childActions: ->(multiple) do
|
|
208
|
+
# multiple.logics_set targetId: 'bulk_edit_loaded_count', data: { text: '0' }
|
|
209
|
+
# multiple.logics_set targetId: 'bulk_edit_selected_count', data: { text: '0' }
|
|
210
|
+
# multiple.logics_set targetId: 'bulk_edit_succeeded_count', data: { text: 'Uploaded: 0', displayed: false }
|
|
211
|
+
# end
|
|
212
|
+
# end
|
|
213
|
+
# end
|
|
214
|
+
# res.spacer height: 12
|
|
215
|
+
# res.hr width: 'matchParent'
|
|
216
|
+
res.h2 text: 'Actions/events'
|
|
217
|
+
res.spacer height: 8
|
|
218
|
+
res.panels_flow(
|
|
219
|
+
innerPadding: { bottom: 0 },
|
|
220
|
+
childViews: ->(flow) do
|
|
221
|
+
flow.button(
|
|
222
|
+
text: 'Submit (forms_submit)',
|
|
223
|
+
onClick: ->(action) do
|
|
224
|
+
action.forms_submit targetId: 'bulk_edit_main'
|
|
225
|
+
end
|
|
226
|
+
)
|
|
227
|
+
flow.spacer width: 8
|
|
228
|
+
flow.button(
|
|
229
|
+
text: 'Cancel import (invoke)',
|
|
230
|
+
onClick: ->(action) do
|
|
231
|
+
action.components_invoke targetId: 'bulk_edit_main', name: 'cancelImport'
|
|
232
|
+
end
|
|
233
|
+
)
|
|
234
|
+
end
|
|
235
|
+
)
|
|
236
|
+
res.spacer height: 12
|
|
237
|
+
res.hr width: 'matchParent'
|
|
238
|
+
res.h2 text: 'Edge/advanced'
|
|
239
|
+
res.spacer height: 8
|
|
240
|
+
res.label text: 'Swap to compact headers then try editing rows before submitting.'
|
|
241
|
+
end
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
)
|
|
@@ -83,13 +83,13 @@ page.body(
|
|
|
83
83
|
padding: glib_json_padding_body,
|
|
84
84
|
childViews: ->(res) do
|
|
85
85
|
res.h2 text: 'Overview'
|
|
86
|
-
res.p text: 'panels_bulkEdit3 is the generic bulk-edit table:
|
|
86
|
+
res.p text: 'panels_bulkEdit3 is the generic bulk-edit table: bulkEdit2’s skeleton plus config-driven file parsing, row gates, read-only columns, view chips and submission extras.'
|
|
87
87
|
res.spacer height: 12
|
|
88
88
|
res.hr width: 'matchParent'
|
|
89
89
|
|
|
90
90
|
res.h2 text: 'Basic example (plain CSV)'
|
|
91
91
|
res.spacer height: 8
|
|
92
|
-
res.p text: 'Without parse config the table takes plain CSVs: header row first, header names as field ids. maxRows 3 rejects oversized files whole.'
|
|
92
|
+
res.p text: 'Without parse config the table takes plain CSVs: header row first, header names as field ids (the bulkEdit2 shape). maxRows 3 rejects oversized files whole.'
|
|
93
93
|
res.spacer height: 8
|
|
94
94
|
res.panels_horizontal(
|
|
95
95
|
align: 'middle',
|
|
@@ -16,7 +16,7 @@ module Glib
|
|
|
16
16
|
raise "Submit action needs to be inside a form: #{http.router.last_log}" unless form
|
|
17
17
|
|
|
18
18
|
case Glib::JsonUi::Name.normalize(form['view'])
|
|
19
|
-
when 'panels/bulkEdit3'
|
|
19
|
+
when 'panels/bulkEdit2', 'panels/bulkEdit3'
|
|
20
20
|
method = 'post'
|
|
21
21
|
action = "forms/#{method}"
|
|
22
22
|
url = form['import']['submitUrl']
|
data/lib/glib/test_helpers.rb
CHANGED
|
@@ -239,6 +239,7 @@ module Glib
|
|
|
239
239
|
name = Glib::JsonUi::Name.normalize(view['view'])
|
|
240
240
|
name&.start_with?('panels/form') ||
|
|
241
241
|
name&.start_with?('panels/fullPageForm') ||
|
|
242
|
+
name == 'panels/bulkEdit2' ||
|
|
242
243
|
name == 'panels/bulkEdit3' ||
|
|
243
244
|
name == 'panels/splitForm'
|
|
244
245
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.0.
|
|
4
|
+
version: 7.0.1.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
@@ -497,6 +497,7 @@ files:
|
|
|
497
497
|
- app/views/json_ui/garage/test_page/multimedia_video.json.jbuilder
|
|
498
498
|
- app/views/json_ui/garage/test_page/pagination.json.jbuilder
|
|
499
499
|
- app/views/json_ui/garage/test_page/panels.json.jbuilder
|
|
500
|
+
- app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder
|
|
500
501
|
- app/views/json_ui/garage/test_page/panels_bulkEdit3.json.jbuilder
|
|
501
502
|
- app/views/json_ui/garage/test_page/popovers.json.jbuilder
|
|
502
503
|
- app/views/json_ui/garage/test_page/progressCircle.json.jbuilder
|