brick 1.0.212 → 1.0.214
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/brick/extensions.rb +96 -47
- data/lib/brick/frameworks/rails/engine.rb +17 -7
- data/lib/brick/frameworks/rails/form_tags.rb +268 -229
- data/lib/brick/frameworks/rails.rb +0 -1
- data/lib/brick/join_array.rb +6 -0
- data/lib/brick/reflect_tables.rb +1 -1
- data/lib/brick/version_number.rb +1 -1
- data/lib/generators/brick/seeds_generator.rb +6 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afed5350a655b7eca570609ff4d15f998ecfe746345c4771b7eb910274df14ae
|
4
|
+
data.tar.gz: 45d5cccb07240fe0995bf812dc4b0cc34d21edd1bb0ab3cf212cd255c8a0f6a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca420002527953167f6ba1112e43d197bea259bf360c89ff92a01c9753c53da1b7060c15cd021c27e6106c4e77e7d3c30f589c89509ae21c2a2fe69d42e6f079
|
7
|
+
data.tar.gz: e6b1fd3745b5c69bfaa9486a4158b7a8efc03c7c43aa884a13f630e9b023e40e0893e1862e6e7fee545049efb46c5b3686cd4f05363d19eb3dfcf0d8f6b1cef3
|
data/lib/brick/extensions.rb
CHANGED
@@ -71,15 +71,24 @@ module ActiveRecord
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def real_model(params)
|
74
|
-
if params && (
|
75
|
-
|
74
|
+
if params && ((sub_name = params.fetch(inheritance_column, nil)).present? ||
|
75
|
+
(sub_name = params[name.underscore]&.fetch(inheritance_column, nil)))
|
76
|
+
sub_name = sub_name.first if sub_name.is_a?(Array) # Support the params style that gets returned from #_brick_querying
|
76
77
|
# Make sure the chosen model is really the same or a subclass of this model
|
77
|
-
|
78
|
+
return self if sub_name.blank?
|
79
|
+
|
80
|
+
(possible_model = sub_name.constantize) <= self ? possible_model : self
|
78
81
|
else
|
79
82
|
self
|
80
83
|
end
|
81
84
|
end
|
82
85
|
|
86
|
+
# Accommodate STI
|
87
|
+
def real_singular(params)
|
88
|
+
real_model = real_model(params)
|
89
|
+
[real_model, real_model.name.underscore.split('/').last]
|
90
|
+
end
|
91
|
+
|
83
92
|
def json_column?(col)
|
84
93
|
col.type == :json || ::Brick.config.json_columns[table_name]&.include?(col.name) ||
|
85
94
|
(
|
@@ -230,11 +239,7 @@ module ActiveRecord
|
|
230
239
|
end
|
231
240
|
if first_parts
|
232
241
|
if (parts = prefix + first_parts + [parts[-1]]).length > 1 && klass
|
233
|
-
unless is_polymorphic
|
234
|
-
s = join_array
|
235
|
-
parts[0..-3].each { |v| s = s[v.to_sym] }
|
236
|
-
s[parts[-2]] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
237
|
-
end
|
242
|
+
join_array.add_parts(parts) unless is_polymorphic
|
238
243
|
translations[parts[0..-2].join('.')] = klass
|
239
244
|
end
|
240
245
|
if klass&.column_names.exclude?(parts.last) &&
|
@@ -538,7 +543,11 @@ module ActiveRecord
|
|
538
543
|
end
|
539
544
|
|
540
545
|
def self.brick_where(*args)
|
541
|
-
|
546
|
+
all.brick_where(*args)
|
547
|
+
end
|
548
|
+
|
549
|
+
def self.brick_group(*args, withhold_ids: true, **kwargs)
|
550
|
+
all.brick_group(*args, withhold_ids: withhold_ids, **kwargs)
|
542
551
|
end
|
543
552
|
|
544
553
|
private
|
@@ -612,27 +621,36 @@ module ActiveRecord
|
|
612
621
|
pluck(selects)
|
613
622
|
end
|
614
623
|
|
615
|
-
def
|
624
|
+
def brick_group(*args, **kwargs)
|
625
|
+
grouping = args[0].is_a?(Array) ? args[0] : args
|
626
|
+
_brick_querying(select_values.frozen? ? select_values.dup : select_values,
|
627
|
+
grouping: grouping, **kwargs)
|
628
|
+
self
|
629
|
+
end
|
630
|
+
|
631
|
+
def _brick_querying(*args, grouping: nil, withhold_ids: nil, params: {}, order_by: nil, translations: {},
|
616
632
|
join_array: ::Brick::JoinArray.new,
|
617
633
|
cust_col_override: nil,
|
618
634
|
brick_col_names: nil)
|
619
635
|
selects = args[0].is_a?(Array) ? args[0] : args
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
636
|
+
unless cust_col_override
|
637
|
+
if selects.present? # See if there's any fancy ones in the select list
|
638
|
+
idx = 0
|
639
|
+
while idx < selects.length
|
640
|
+
v = selects[idx]
|
641
|
+
if v.is_a?(String) && v.index('.')
|
642
|
+
# No prefixes and not polymorphic
|
643
|
+
pieces = self.brick_parse_dsl(join_array, [], translations, false, dsl = "[#{v}]")
|
644
|
+
(cust_col_override ||= {})[v.tr('.', '_').to_sym] = [pieces, dsl, true]
|
645
|
+
selects.delete_at(idx)
|
646
|
+
else
|
647
|
+
idx += 1
|
648
|
+
end
|
631
649
|
end
|
650
|
+
elsif selects.is_a?(Hash) && params.empty? # Make sense of things if they've passed in only params
|
651
|
+
params = selects
|
652
|
+
selects = []
|
632
653
|
end
|
633
|
-
elsif selects.is_a?(Hash) && params.empty? && cust_col_override.nil? # Make sense of things if they've passed in only params
|
634
|
-
params = selects
|
635
|
-
selects = []
|
636
654
|
end
|
637
655
|
is_add_bts = is_add_hms = !cust_col_override
|
638
656
|
|
@@ -710,6 +728,14 @@ module ActiveRecord
|
|
710
728
|
end
|
711
729
|
end
|
712
730
|
|
731
|
+
# Establish necessary JOINs for any custom GROUP BY columns
|
732
|
+
grouping&.each do |group_item|
|
733
|
+
# JOIN in all the same ways as the pathing describes
|
734
|
+
if group_item.is_a?(String) && (ref_parts = group_item.split('.')).length > 1
|
735
|
+
join_array.add_parts(ref_parts)
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
713
739
|
if join_array.present?
|
714
740
|
if ActiveRecord.version < Gem::Version.new('4.2')
|
715
741
|
self.joins_values += join_array # Same as: joins!(join_array)
|
@@ -725,7 +751,9 @@ module ActiveRecord
|
|
725
751
|
|
726
752
|
# CUSTOM COLUMNS
|
727
753
|
# ==============
|
728
|
-
|
754
|
+
cust_cols = cust_col_override
|
755
|
+
cust_cols ||= klass._br_cust_cols unless withhold_ids
|
756
|
+
cust_cols&.each do |k, cc|
|
729
757
|
brick_links # Intentionally create a relation duplicate
|
730
758
|
if @_brick_rel_dup.respond_to?(k) # Name already taken?
|
731
759
|
# %%% Use ensure_unique here in this kind of fashion:
|
@@ -753,7 +781,7 @@ module ActiveRecord
|
|
753
781
|
col_alias = "#{col_prefix}#{k}__#{table_name.tr('.', '_')}_#{cc_part.first}"
|
754
782
|
elsif brick_col_names ||
|
755
783
|
used_col_aliases.key?(col_alias = k.to_s) # This sets a simpler custom column name if possible
|
756
|
-
while cc_part_idx
|
784
|
+
while cc_part_idx >= 0 &&
|
757
785
|
(col_alias = "#{col_prefix}#{k}__#{cc_part[cc_part_idx..-1].map(&:to_s).join('__').tr('.', '_')}") &&
|
758
786
|
used_col_aliases.key?(col_alias)
|
759
787
|
cc_part_idx -= 1
|
@@ -988,6 +1016,20 @@ JOIN (SELECT #{hm_selects.map { |s| _br_quoted_name("#{'br_t0.' if from_clause}#
|
|
988
1016
|
klass._br_hm_counts.delete(n)
|
989
1017
|
end
|
990
1018
|
|
1019
|
+
# Rewrite the group values to reference table and correlation names built out by AREL
|
1020
|
+
if grouping
|
1021
|
+
group2 = (gvgu = (group_values + grouping).uniq).each_with_object([]) do |v, s|
|
1022
|
+
if v.is_a?(Symbol) || (v_parts = v.split('.')).length == 1
|
1023
|
+
s << v
|
1024
|
+
elsif (tbl_name = brick_links[v_parts[0..-2].join('.')]&.split('.')&.last)
|
1025
|
+
s << "#{tbl_name}.#{v_parts.last}"
|
1026
|
+
else
|
1027
|
+
s << v
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
group!(*group2)
|
1031
|
+
end
|
1032
|
+
|
991
1033
|
unless wheres.empty?
|
992
1034
|
# Rewrite the wheres to reference table and correlation names built out by AREL
|
993
1035
|
where_nots = {}
|
@@ -998,7 +1040,7 @@ JOIN (SELECT #{hm_selects.map { |s| _br_quoted_name("#{'br_t0.' if from_clause}#
|
|
998
1040
|
if (v_parts = v.first.split('.')).length == 1
|
999
1041
|
(is_not ? where_nots : s)[v.first] = v.last
|
1000
1042
|
else
|
1001
|
-
tbl_name = brick_links[v_parts.
|
1043
|
+
tbl_name = brick_links[v_parts[0..-2].join('.')].split('.').last
|
1002
1044
|
(is_not ? where_nots : s)["#{tbl_name}.#{v_parts.last}"] = v.last
|
1003
1045
|
end
|
1004
1046
|
end
|
@@ -1095,13 +1137,9 @@ JOIN (SELECT #{hm_selects.map { |s| _br_quoted_name("#{'br_t0.' if from_clause}#
|
|
1095
1137
|
# && joins_values.empty? # Make sure we don't step on any toes if they've already specified JOIN things
|
1096
1138
|
ja = nil
|
1097
1139
|
opts.each do |k, v|
|
1140
|
+
# JOIN in all the same ways as the pathing describes
|
1098
1141
|
if k.is_a?(String) && (ref_parts = k.split('.')).length > 1
|
1099
|
-
|
1100
|
-
linkage = (ja ||= ::Brick::JoinArray.new)
|
1101
|
-
ref_parts[0..-3].each do |prefix_part|
|
1102
|
-
linkage = linkage[prefix_part.to_sym]
|
1103
|
-
end
|
1104
|
-
linkage[ref_parts[-2].to_sym] = nil
|
1142
|
+
(ja ||= ::Brick::JoinArray.new).add_parts(ref_parts)
|
1105
1143
|
end
|
1106
1144
|
end
|
1107
1145
|
if ja&.present?
|
@@ -1709,7 +1747,9 @@ class Object
|
|
1709
1747
|
end
|
1710
1748
|
# Accommodate singular or camel-cased table names such as "order_detail" or "OrderDetails"
|
1711
1749
|
code << " self.table_name = '#{self.table_name = matching}'\n" if (inheritable_name || model_name).underscore.pluralize != matching
|
1712
|
-
|
1750
|
+
|
1751
|
+
if (inh_col = relation.fetch(:sti_col, nil) ||
|
1752
|
+
::Brick.config.sti_type_column.find { |_k, v| v.include?(matching) }&.first)
|
1713
1753
|
new_model_class.inheritance_column = inh_col
|
1714
1754
|
code << " self.inheritance_column = '#{inh_col}'\n"
|
1715
1755
|
end
|
@@ -1760,11 +1800,6 @@ class Object
|
|
1760
1800
|
end
|
1761
1801
|
end
|
1762
1802
|
|
1763
|
-
if (sti_col = relation.fetch(:sti_col, nil))
|
1764
|
-
new_model_class.send(:'inheritance_column=', sti_col)
|
1765
|
-
code << " self.inheritance_column = #{sti_col.inspect}\n"
|
1766
|
-
end
|
1767
|
-
|
1768
1803
|
unless is_sti
|
1769
1804
|
fks = relation[:fks] || {}
|
1770
1805
|
# Do the bulk of the has_many / belongs_to processing, and store details about HMT so they can be done at the very last
|
@@ -2418,13 +2453,15 @@ class Object
|
|
2418
2453
|
code << " @#{plural_table_name}._brick_querying(params, brick_col_names: true)\n"
|
2419
2454
|
code << " end\n"
|
2420
2455
|
|
2421
|
-
|
2456
|
+
# ----------------------------------------------------------------------------------
|
2457
|
+
|
2422
2458
|
if pk.present?
|
2423
2459
|
code << " def show\n"
|
2424
2460
|
code << " #{find_by_name = "find_#{singular_table_name}"}\n"
|
2425
2461
|
code << " end\n"
|
2426
2462
|
self.define_method :show do
|
2427
2463
|
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
2464
|
+
_, singular_table_name = model.real_singular(params)
|
2428
2465
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
2429
2466
|
add_csp_hash("'unsafe-inline'")
|
2430
2467
|
end
|
@@ -2443,7 +2480,8 @@ class Object
|
|
2443
2480
|
send(params_name_sym)
|
2444
2481
|
rescue
|
2445
2482
|
end
|
2446
|
-
|
2483
|
+
real_model, singular_table_name = model.real_singular(params)
|
2484
|
+
new_params ||= real_model.attribute_names.each_with_object({}) do |a, s|
|
2447
2485
|
if (val = params["__#{a}"])
|
2448
2486
|
# val = case new_obj.class.column_for_attribute(a).type
|
2449
2487
|
# when :datetime, :date, :time, :timestamp
|
@@ -2454,7 +2492,7 @@ class Object
|
|
2454
2492
|
s[a] = val
|
2455
2493
|
end
|
2456
2494
|
end
|
2457
|
-
if (new_obj =
|
2495
|
+
if (new_obj = real_model.new(new_params)).respond_to?(:serializable_hash)
|
2458
2496
|
# Convert any Filename objects with nil into an empty string so that #encode can be called on them
|
2459
2497
|
new_obj.serializable_hash.each do |k, v|
|
2460
2498
|
new_obj.send("#{k}=", ::ActiveStorage::Filename.new('')) if v.is_a?(::ActiveStorage::Filename) && !v.instance_variable_get(:@filename)
|
@@ -2480,12 +2518,20 @@ class Object
|
|
2480
2518
|
end
|
2481
2519
|
render json: { result: ::Brick.unexclude_column(table_name, col) }
|
2482
2520
|
else
|
2483
|
-
|
2484
|
-
|
2521
|
+
real_model = model.real_model(params)
|
2522
|
+
singular_table_name = real_model.name.underscore.split('/').last
|
2523
|
+
created_obj = model.send(:new, send(params_name_sym))
|
2524
|
+
if created_obj.respond_to?(inh_col = model.inheritance_column) && created_obj.send(inh_col) == ''
|
2525
|
+
created_obj.send("#{inh_col}=", model.name)
|
2526
|
+
end
|
2527
|
+
created_obj.save
|
2528
|
+
@_lookup_context.instance_variable_set(:@_brick_model, real_model)
|
2485
2529
|
if created_obj.errors.empty?
|
2530
|
+
instance_variable_set("@#{singular_table_name}".to_sym, created_obj)
|
2486
2531
|
index
|
2487
2532
|
render :index
|
2488
2533
|
else # Surface errors to the user in a flash message
|
2534
|
+
instance_variable_set("@#{singular_table_name}".to_sym, created_obj)
|
2489
2535
|
flash.now.alert = (created_obj.errors.errors.map { |err| "<b>#{err.attribute}</b> #{err.message}" }.join(', '))
|
2490
2536
|
new
|
2491
2537
|
render :new
|
@@ -2504,6 +2550,7 @@ class Object
|
|
2504
2550
|
code << " end\n"
|
2505
2551
|
self.define_method :edit do
|
2506
2552
|
_schema, @_is_show_schema_list = ::Brick.set_db_schema(params)
|
2553
|
+
_, singular_table_name = model.real_singular(params)
|
2507
2554
|
instance_variable_set("@#{singular_table_name}".to_sym, find_obj)
|
2508
2555
|
add_csp_hash
|
2509
2556
|
end
|
@@ -2531,6 +2578,7 @@ class Object
|
|
2531
2578
|
# return
|
2532
2579
|
end
|
2533
2580
|
|
2581
|
+
_, singular_table_name = model.real_singular(params)
|
2534
2582
|
instance_variable_set("@#{singular_table_name}".to_sym, (obj = find_obj))
|
2535
2583
|
upd_params = send(params_name_sym)
|
2536
2584
|
json_overrides = ::Brick.config.json_columns&.fetch(table_name, nil)
|
@@ -2617,11 +2665,11 @@ class Object
|
|
2617
2665
|
if is_need_params
|
2618
2666
|
code << " def #{params_name}\n"
|
2619
2667
|
permits_txt = model._brick_find_permits(model, permits = model._brick_all_fields(true))
|
2620
|
-
code << " params.require(:#{
|
2668
|
+
code << " params.require(:#{model.base_class.name.underscore.tr('/', '_')
|
2621
2669
|
}).permit(#{permits_txt.map(&:inspect).join(', ')})\n"
|
2622
2670
|
code << " end\n"
|
2623
2671
|
self.define_method(params_name) do
|
2624
|
-
params.require(
|
2672
|
+
params.require(model.base_class.name.underscore.tr('/', '_').to_sym).permit(permits)
|
2625
2673
|
end
|
2626
2674
|
private params_name
|
2627
2675
|
# Get column names for params from relations[model.table_name][:cols].keys
|
@@ -2916,7 +2964,8 @@ module Brick
|
|
2916
2964
|
else
|
2917
2965
|
inverse_table = [primary_table] if polymorphic_class
|
2918
2966
|
assoc_bt = bts[cnstr_name] = { is_bt: true, fk: fk[2], assoc_name: bt_assoc_name, inverse_table: inverse_table || primary_table }
|
2919
|
-
assoc_bt[:optional] = true if is_optional
|
2967
|
+
assoc_bt[:optional] = true if is_optional ||
|
2968
|
+
(is_optional.nil? && !relations[fk[1]][:cols][fk[2]][3])
|
2920
2969
|
assoc_bt[:polymorphic] = [polymorphic_class] if polymorphic_class
|
2921
2970
|
end
|
2922
2971
|
if is_class
|
@@ -809,6 +809,7 @@ h1, h3 {
|
|
809
809
|
background-repeat: no-repeat;
|
810
810
|
background-size: 100% 100%;
|
811
811
|
width: 28px;
|
812
|
+
height: 32px;
|
812
813
|
cursor: pointer;
|
813
814
|
}
|
814
815
|
#mermaidErd {
|
@@ -942,10 +943,10 @@ table.shadow > tbody > tr {
|
|
942
943
|
table tbody tr:nth-of-type(even) {
|
943
944
|
background-color: #f3f3f3;
|
944
945
|
}
|
945
|
-
table tbody tr:nth-of-type(even) .
|
946
|
+
table tbody tr:nth-of-type(even) .alternating-gray {
|
946
947
|
background-color: #fff;
|
947
948
|
}
|
948
|
-
table tbody tr:nth-of-type(odd) .
|
949
|
+
table tbody tr:nth-of-type(odd) .alternating-gray {
|
949
950
|
background-color: #f3f3f3;
|
950
951
|
}
|
951
952
|
|
@@ -991,6 +992,11 @@ a.big-arrow {
|
|
991
992
|
background-color: red;
|
992
993
|
color: white;
|
993
994
|
}
|
995
|
+
.brick-note {
|
996
|
+
font-size: 0.7em;
|
997
|
+
color: #A0FFA0;
|
998
|
+
max-width: 0;
|
999
|
+
}
|
994
1000
|
|
995
1001
|
#revertTemplate {
|
996
1002
|
display: none;
|
@@ -1336,8 +1342,7 @@ end
|
|
1336
1342
|
end}
|
1337
1343
|
%>
|
1338
1344
|
|
1339
|
-
#{"<hr><%=
|
1340
|
-
obj_name}\", #{new_path_name}, { class: '__brick' }) if respond_to?(:#{new_path_name}) %>" unless @_brick_model.is_view?}
|
1345
|
+
#{"<hr><%= link_to_brick(model, new: true, class: '__brick') %>" unless @_brick_model.is_view?}
|
1341
1346
|
#{script}
|
1342
1347
|
</body>
|
1343
1348
|
</html>
|
@@ -1437,7 +1442,7 @@ end
|
|
1437
1442
|
<head>
|
1438
1443
|
#{css}
|
1439
1444
|
<title><%=
|
1440
|
-
base_model = (model = (obj = @#{obj_name})
|
1445
|
+
base_model = (model = (obj = @#{obj_name}).class).base_class
|
1441
1446
|
see_all_path = send(\"#\{base_model._brick_index}_path\")
|
1442
1447
|
#{(inh_col = @_brick_model.inheritance_column).present? &&
|
1443
1448
|
" if obj.respond_to?(:#{inh_col}) && (model_name = @#{obj_name}.#{inh_col}) &&
|
@@ -1445,6 +1450,7 @@ end
|
|
1445
1450
|
see_all_path << \"?#{inh_col}=#\{model_name}\"
|
1446
1451
|
end
|
1447
1452
|
model_name = base_model.name if model_name.is_a?(Numeric)"}
|
1453
|
+
model_name = nil if model_name == ''
|
1448
1454
|
page_title = (\"#\{model_name ||= model.name}: #\{obj&.brick_descrip || controller_name}\")
|
1449
1455
|
%></title>
|
1450
1456
|
</head>
|
@@ -1510,8 +1516,12 @@ end
|
|
1510
1516
|
# path_options = [obj.#{pk}]
|
1511
1517
|
# path_options << { '_brick_schema': } if
|
1512
1518
|
options = {}
|
1513
|
-
|
1514
|
-
|
1519
|
+
options[:url] = if obj.new_record?
|
1520
|
+
link_to_brick(obj.class, path_only: true) # Properly supports STI, but only works for :new
|
1521
|
+
else
|
1522
|
+
path_helper = obj.new_record? ? #{model_name}._brick_index : #{model_name}._brick_index(:singular)
|
1523
|
+
options[:url] = send(\"#\{path_helper}_path\".to_sym, obj) if ::Brick.config.path_prefix || (path_helper != obj.class.table_name)
|
1524
|
+
end
|
1515
1525
|
%>
|
1516
1526
|
<br><br>
|
1517
1527
|
|
@@ -6,70 +6,20 @@ module Brick::Rails::FormTags
|
|
6
6
|
# When a relation is not provided, first see if one exists which matches the controller name or
|
7
7
|
# something has turned up in the instance variables.
|
8
8
|
relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
|
9
|
+
klass = relation.klass
|
9
10
|
|
10
11
|
nfc = Brick.config.sidescroll.fetch(relation.table_name, nil)&.fetch(:num_frozen_columns, nil) ||
|
11
12
|
Brick.config.sidescroll.fetch(:num_frozen_columns, nil) ||
|
12
13
|
0
|
13
14
|
|
14
|
-
|
15
|
-
out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
|
16
|
-
"
|
17
|
-
klass = relation.klass
|
15
|
+
out = +''
|
18
16
|
rel = ::Brick.relations&.fetch(relation.table_name, nil)
|
19
|
-
|
20
|
-
out <<
|
21
|
-
<div id=\"headerButtonBox\">
|
22
|
-
"
|
23
|
-
unless show_row_count == false
|
24
|
-
out << " <div id=\"rowCount\"></div>
|
25
|
-
"
|
26
|
-
end
|
27
|
-
unless show_erd_button == false
|
28
|
-
out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
|
29
|
-
"
|
30
|
-
end
|
31
|
-
if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
|
32
|
-
begin
|
33
|
-
in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
|
34
|
-
out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
|
35
|
-
"
|
36
|
-
rescue ActionController::UrlGenerationError # Avoid snags like "No route matches {:action=>"index", :controller=>"categories/products"}, missing required keys: [:category_id]"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
|
40
|
-
out << "
|
41
|
-
<td>#{link_to_brick(
|
42
|
-
::Brick::Rails::AVO_SVG.html_safe,
|
43
|
-
{ index_proc: Proc.new do |_avo_model, relation|
|
44
|
-
path_helper = "resources_#{relation.fetch(:auto_prefixed_schema, nil)}#{klass.model_name.route_key}_path".to_sym
|
45
|
-
::Avo.railtie_routes_url_helpers.send(path_helper) if ::Avo.railtie_routes_url_helpers.respond_to?(path_helper)
|
46
|
-
end,
|
47
|
-
title: "#{klass.name} in Avo" }
|
48
|
-
)}</td>
|
49
|
-
"
|
50
|
-
end
|
51
|
-
|
52
|
-
if show_aa_button != false && Object.const_defined?('ActiveAdmin')
|
53
|
-
ActiveAdmin.application.namespaces.names.each do |ns|
|
54
|
-
out << "
|
55
|
-
<td>#{link_to_brick(
|
56
|
-
::Brick::Rails::AA_PNG.html_safe,
|
57
|
-
{ index_proc: Proc.new do |aa_model, relation|
|
58
|
-
path_helper = "#{ns}_#{relation.fetch(:auto_prefixed_schema, nil)}#{aa_model.model_name.route_key}_path".to_sym
|
59
|
-
send(path_helper) if respond_to?(path_helper)
|
60
|
-
end,
|
61
|
-
title: "#{klass.name} in ActiveAdmin" }
|
62
|
-
)}</td>
|
63
|
-
"
|
64
|
-
end
|
65
|
-
end
|
66
|
-
out << " </div>
|
67
|
-
</div>
|
68
|
-
"
|
17
|
+
if show_header != false
|
18
|
+
out << brick_header(rel, klass, show_row_count, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
|
69
19
|
end
|
70
20
|
|
71
|
-
|
72
|
-
<table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\"#{ " x-num-frozen=\"#{nfc}\"" if nfc.positive? }>
|
21
|
+
# HTML for brick_grid
|
22
|
+
out << "<table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\"#{ " x-num-frozen=\"#{nfc}\"" if nfc.positive? }>
|
73
23
|
<thead><tr>"
|
74
24
|
pk = klass.primary_key || []
|
75
25
|
pk = [pk] unless pk.is_a?(Array)
|
@@ -145,13 +95,13 @@ module Brick::Rails::FormTags
|
|
145
95
|
row_count = 0
|
146
96
|
relation.each do |obj|
|
147
97
|
out << "<tr>\n"
|
148
|
-
out << "<td class=\"col-sticky\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
|
98
|
+
out << "<td class=\"col-sticky alternating-gray\">#{link_to('⇛', send("#{klass._brick_index(:singular)}_path".to_sym,
|
149
99
|
pk.map { |pk_part| obj.send(pk_part.to_sym) }), { class: 'big-arrow' })}</td>\n" if pk.present?
|
150
100
|
sequence.each_with_index do |col_name, idx|
|
151
101
|
val = obj.attributes[col_name]
|
152
102
|
bt = bts[col_name] || composite_bt_names[col_name]
|
153
103
|
out << '<td'
|
154
|
-
(classes ||= []) << 'col-sticky' if idx < nfc
|
104
|
+
(classes ||= []) << 'col-sticky alternating-gray' if idx < nfc
|
155
105
|
(classes ||= []) << 'dimmed' unless cols.key?(col_name) || (cust_col = cust_cols[col_name]) ||
|
156
106
|
(col_name.is_a?(Symbol) && bts.key?(col_name)) # HOT
|
157
107
|
(classes ||= []) << 'right' if val.is_a?(Numeric) && !bt
|
@@ -255,161 +205,14 @@ module Brick::Rails::FormTags
|
|
255
205
|
out << '</tr>'
|
256
206
|
row_count += 1
|
257
207
|
end
|
258
|
-
if rel && (total_row_count = rel.fetch(:rowcount, nil))
|
259
|
-
total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
|
260
|
-
end
|
261
208
|
out << " </tbody>
|
262
209
|
</table>
|
263
|
-
<script>
|
264
|
-
var rowCount = document.getElementById(\"rowCount\");
|
265
|
-
if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} \";
|
266
|
-
</script>
|
267
|
-
"
|
268
|
-
|
269
|
-
# Javascript for brick_grid
|
270
|
-
(@_brick_javascripts ||= {})[:grid_scripts] = "
|
271
|
-
var #{table_name}HtColumns;
|
272
|
-
|
273
|
-
// Snag first TR for sticky header
|
274
|
-
var grid = document.getElementById(\"#{table_name}\");
|
275
|
-
#{table_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
|
276
|
-
var headerTop = document.getElementById(\"headerTop\");
|
277
|
-
var headerCols;
|
278
|
-
if (grid) {
|
279
|
-
// COLUMN HEADER AND TABLE CELL HIGHLIGHTING
|
280
|
-
var gridHighHeader = null,
|
281
|
-
gridHighCell = null;
|
282
|
-
grid.addEventListener(\"mouseenter\", gridMove);
|
283
|
-
grid.addEventListener(\"mousemove\", gridMove);
|
284
|
-
grid.addEventListener(\"mouseleave\", function (evt) {
|
285
|
-
if (gridHighCell) gridHighCell.classList.remove(\"highlight\");
|
286
|
-
gridHighCell = null;
|
287
|
-
if (gridHighHeader) gridHighHeader.classList.remove(\"highlight\");
|
288
|
-
gridHighHeader = null;
|
289
|
-
});
|
290
|
-
function gridMove(evt) {
|
291
|
-
var lastHighCell = gridHighCell;
|
292
|
-
gridHighCell = document.elementFromPoint(evt.x, evt.y);
|
293
|
-
while (gridHighCell && gridHighCell.tagName !== \"TD\" && gridHighCell.tagName !== \"TH\")
|
294
|
-
gridHighCell = gridHighCell.parentElement;
|
295
|
-
if (gridHighCell) {
|
296
|
-
if (lastHighCell !== gridHighCell) {
|
297
|
-
gridHighCell.classList.add(\"highlight\");
|
298
|
-
if (lastHighCell) lastHighCell.classList.remove(\"highlight\");
|
299
|
-
}
|
300
|
-
var lastHighHeader = gridHighHeader;
|
301
|
-
if ((gridHighHeader = headerCols[gridHighCell.cellIndex]) && lastHighHeader !== gridHighHeader) {
|
302
|
-
if (gridHighHeader) gridHighHeader.classList.add(\"highlight\");
|
303
|
-
if (lastHighHeader) lastHighHeader.classList.remove(\"highlight\");
|
304
|
-
}
|
305
|
-
}
|
306
|
-
}
|
307
|
-
// // LESS TOUCHY NAVIGATION BACK OR FORWARD IN HISTORY WHEN USING MOUSE WHEEL
|
308
|
-
// grid.addEventListener(\"wheel\", function (evt) {
|
309
|
-
// grid.scrollLeft += evt.deltaX;
|
310
|
-
// document.body.scrollTop += (evt.deltaY * 0.6);
|
311
|
-
// evt.preventDefault();
|
312
|
-
// return false;
|
313
|
-
// });
|
314
|
-
}
|
315
|
-
function setHeaderSizes() {
|
316
|
-
if (grid.clientWidth > window.outerWidth)
|
317
|
-
document.getElementById(\"titleBox\").style.width = grid.clientWidth;
|
318
|
-
// console.log(\"start\");
|
319
|
-
// See if the headerTop is already populated
|
320
|
-
// %%% Grab the TRs from headerTop, clear it out, do this stuff, add them back
|
321
|
-
headerTop.innerHTML = \"\"; // %%% Would love to not have to clear it out like this every time! (Currently doing this to support resize events.)
|
322
|
-
var isEmpty = headerTop.childElementCount === 0;
|
323
|
-
var numFixed = parseInt(grid.getAttribute(\"x-num-frozen\")) || 0;
|
324
|
-
var fixedColLefts = [0];
|
325
|
-
|
326
|
-
// Set up proper sizings of sticky column header
|
327
|
-
var node;
|
328
|
-
for (var j = 0; j < #{table_name}HtColumns.length; ++j) {
|
329
|
-
var row = #{table_name}HtColumns[j];
|
330
|
-
var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
|
331
|
-
tr.innerHTML = row.innerHTML.trim();
|
332
|
-
var curLeft = 0.0;
|
333
|
-
// Match up widths from the original column headers
|
334
|
-
for (var i = 0; i < row.childNodes.length; ++i) {
|
335
|
-
node = row.childNodes[i];
|
336
|
-
if (node.nodeType === 1) {
|
337
|
-
var th = tr.childNodes[i];
|
338
|
-
th.style.minWidth = th.style.maxWidth = getComputedStyle(node).width;
|
339
|
-
// Add \"left: __px\" style to the fixed-width column THs
|
340
|
-
if (i <= numFixed) {
|
341
|
-
th.style.position = \"sticky\";
|
342
|
-
th.style.backgroundColor = \"#008061\";
|
343
|
-
th.style.zIndex = \"1\";
|
344
|
-
th.style.left = curLeft + \"px\";
|
345
|
-
fixedColLefts.push(curLeft += node.clientWidth);
|
346
|
-
}
|
347
|
-
if (#{pk&.present? ? 'i > 0' : 'true'}) {
|
348
|
-
// Add <span> at the end
|
349
|
-
var span = document.createElement(\"SPAN\");
|
350
|
-
span.className = \"exclude\";
|
351
|
-
span.innerHTML = \"X\";
|
352
|
-
span.addEventListener(\"click\", function (e) {
|
353
|
-
e.stopPropagation();
|
354
|
-
doFetch(\"POST\", {_brick_exclude: this.parentElement.getAttribute(\"x-order\")});
|
355
|
-
});
|
356
|
-
th.appendChild(span);
|
357
|
-
}
|
358
|
-
}
|
359
|
-
}
|
360
|
-
headerCols = tr.childNodes;
|
361
|
-
if (isEmpty) headerTop.appendChild(tr);
|
362
|
-
}
|
363
|
-
// Add \"left: __px\" style to all fixed-width column TDs
|
364
|
-
[...grid.children[1].children].forEach(function (row) {
|
365
|
-
for (var j = 1; j <= numFixed; ++j) {
|
366
|
-
row.children[j].style.left = fixedColLefts[j] + 'px';
|
367
|
-
}
|
368
|
-
});
|
369
|
-
grid.style.marginTop = \"-\" + getComputedStyle(headerTop).height;
|
370
|
-
// console.log(\"end\");
|
371
|
-
}
|
372
|
-
|
373
|
-
if (headerTop) {
|
374
|
-
onImagesLoaded(function() {
|
375
|
-
setHeaderSizes();
|
376
|
-
});
|
377
|
-
window.addEventListener(\"resize\", function(event) {
|
378
|
-
setHeaderSizes();
|
379
|
-
}, true);#{
|
380
|
-
if !klass.is_view? && respond_to?(new_path_name = "new_#{klass._brick_index(:singular)}_path")
|
381
|
-
"
|
382
|
-
var headerButtonBox = document.getElementById(\"headerButtonBox\");
|
383
|
-
if (headerButtonBox) {
|
384
|
-
var addNew = document.createElement(\"A\");
|
385
|
-
addNew.id = \"addNew\";
|
386
|
-
addNew.href = \"#{send(new_path_name)}\";
|
387
|
-
addNew.title = \"New #{table_name.singularize}\";
|
388
|
-
addNew.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>';
|
389
|
-
headerButtonBox.append(addNew);
|
390
|
-
}
|
391
|
-
"
|
392
|
-
end}
|
393
|
-
}
|
394
|
-
|
395
|
-
function onImagesLoaded(event) {
|
396
|
-
var images = document.getElementsByTagName(\"IMG\");
|
397
|
-
var numLoaded = images.length;
|
398
|
-
for (var i = 0; i < images.length; ++i) {
|
399
|
-
if (images[i].complete)
|
400
|
-
--numLoaded;
|
401
|
-
else {
|
402
|
-
images[i].addEventListener(\"load\", function() {
|
403
|
-
if (--numLoaded <= 0)
|
404
|
-
event();
|
405
|
-
});
|
406
|
-
}
|
407
|
-
}
|
408
|
-
if (numLoaded <= 0)
|
409
|
-
event();
|
410
|
-
}
|
411
210
|
"
|
211
|
+
if rel && (total_row_count = rel.fetch(:rowcount, nil))
|
212
|
+
total_row_count = total_row_count > row_count ? " (out of #{total_row_count})" : nil
|
213
|
+
end
|
412
214
|
|
215
|
+
set_grid_javascript(klass, pk, show_new_button, row_count, total_row_count)
|
413
216
|
out.html_safe
|
414
217
|
end # brick_grid
|
415
218
|
|
@@ -418,7 +221,7 @@ function onImagesLoaded(event) {
|
|
418
221
|
def brick_form_for(obj, options = {}, model = obj.class, bts = {}, pk = (obj.class.primary_key || []))
|
419
222
|
pk = [pk] unless pk.is_a?(Array)
|
420
223
|
pk.map!(&:to_s)
|
421
|
-
form_for(obj.becomes(model), options) do |f|
|
224
|
+
form_for(obj.becomes(model.base_class), options) do |f|
|
422
225
|
out = +'<table class="shadow">'
|
423
226
|
has_fields = false
|
424
227
|
# If it's a new record, set any default polymorphic types
|
@@ -525,28 +328,37 @@ function onImagesLoaded(event) {
|
|
525
328
|
|
526
329
|
# ------------------------------------------
|
527
330
|
# Our cool N:M checkbox constellation editor
|
528
|
-
def brick_constellation(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {}
|
331
|
+
def brick_constellation(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {},
|
332
|
+
show_header: nil, show_erd_button: nil, show_in_app_button: nil, show_new_button: nil, show_avo_button: nil, show_aa_button: nil)
|
333
|
+
relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
|
334
|
+
klass = relation.klass
|
529
335
|
x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
|
530
336
|
|
337
|
+
out = +''
|
338
|
+
rel = ::Brick.relations&.fetch(relation.table_name, nil)
|
339
|
+
if show_header != false
|
340
|
+
out << brick_header(rel, klass, false, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
|
341
|
+
end
|
342
|
+
|
531
343
|
# HTML for constellation
|
532
344
|
prefix = options[:prefix]
|
533
|
-
out
|
345
|
+
out << "<form action=\"#{"#{prefix}/" if prefix}brick_constellation\">
|
534
346
|
<table id=\"#{table_name = relation.table_name.split('.').last}\" class=\"shadow\">
|
535
|
-
<thead><tr><td
|
536
|
-
"
|
347
|
+
<thead><tr><td class=\"brick-note\">Checkbox changes are saved immediately</td>"
|
537
348
|
# Header row with X axis values
|
349
|
+
# (In order for grid highlighting to function, these TH elements must have no whitespace between them.
|
350
|
+
# In this way the Javascript headerCols array will be set properly.
|
538
351
|
x_list.each do |x_item|
|
539
|
-
out << "
|
540
|
-
"
|
352
|
+
out << "<th>#{x_item.first}</th>"
|
541
353
|
end
|
542
|
-
out << "
|
354
|
+
out << "</tr></thead>
|
543
355
|
<tbody>
|
544
356
|
"
|
545
|
-
obj_path = "#{
|
357
|
+
obj_path = "#{klass._brick_index(:singular)}_path".to_sym
|
546
358
|
link_arrow = link_to('⇛', send(obj_path, '____'), { class: 'big-arrow' })
|
547
|
-
pk_as_array =
|
359
|
+
pk_as_array = klass._pk_as_array
|
548
360
|
y_list.each do |y_item|
|
549
|
-
out << " <tr><th>#{y_item.first}</th>
|
361
|
+
out << " <tr><th class=\"col-sticky\">#{y_item.first}</th>
|
550
362
|
"
|
551
363
|
x_list.each do |x_item|
|
552
364
|
checked = existing.find { |e| e[1] == x_item.last && e[2] == y_item.last }
|
@@ -570,7 +382,7 @@ function onImagesLoaded(event) {
|
|
570
382
|
_this = this;
|
571
383
|
if (this.checked) {
|
572
384
|
var ids = this.value.split(\"_\");
|
573
|
-
doFetch(\"POST\", {modelName: \"#{
|
385
|
+
doFetch(\"POST\", {modelName: \"#{klass.name}\",
|
574
386
|
args: [#{x_axis[1].inspect}, ids[0], #{y_axis[1].inspect}, ids[1]],
|
575
387
|
_brick_action: \"/#{prefix}brick_associate\"},
|
576
388
|
function (p) { // If it returns successfully, create an <a> element
|
@@ -586,7 +398,7 @@ function onImagesLoaded(event) {
|
|
586
398
|
}
|
587
399
|
);
|
588
400
|
} else if (nextSib = this.nextElementSibling) {
|
589
|
-
doFetch(\"DELETE\", {modelName: \"#{
|
401
|
+
doFetch(\"DELETE\", {modelName: \"#{klass.name}\",
|
590
402
|
id: this.getAttribute(\"x-id\"),
|
591
403
|
_brick_action: \"/#{prefix}brick_associate\"},
|
592
404
|
function (p) { // If it returns successfully, remove the an <a> element
|
@@ -599,6 +411,7 @@ function onImagesLoaded(event) {
|
|
599
411
|
</script>
|
600
412
|
</form>
|
601
413
|
"
|
414
|
+
set_grid_javascript(klass, pk_as_array, false)
|
602
415
|
out.html_safe
|
603
416
|
end # brick_constellation
|
604
417
|
|
@@ -606,7 +419,9 @@ function onImagesLoaded(event) {
|
|
606
419
|
# Our cool N:M bezier visualisation
|
607
420
|
# (...... work in progress .......)
|
608
421
|
def brick_bezier(relation = nil, options = {}, x_axis: nil, y_axis: nil, bt_descrip: nil, bts: {})
|
422
|
+
relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
|
609
423
|
x_axis, x_list, y_axis, y_list, existing = _n_m_prep(relation, x_axis, y_axis)
|
424
|
+
rel = ::Brick.relations&.fetch(relation.table_name, nil)
|
610
425
|
# HTML for constellation
|
611
426
|
# X axis (List on left side)
|
612
427
|
out = +"<table id=\"#{x_axis.first}\" class=\"shadow\">
|
@@ -629,13 +444,222 @@ function onImagesLoaded(event) {
|
|
629
444
|
out.html_safe
|
630
445
|
end # brick_bezier
|
631
446
|
|
447
|
+
# ---------------------------------------------------------------------------------------------------------
|
448
|
+
def brick_header(rel, klass, show_row_count, show_erd_button, show_in_app_button, show_avo_button, show_aa_button)
|
449
|
+
out = +"<div id=\"headerTopContainer\"><table id=\"headerTop\"></table>
|
450
|
+
<div id=\"headerTopAddNew\">
|
451
|
+
<div id=\"headerButtonBox\">
|
452
|
+
"
|
453
|
+
unless show_row_count == false
|
454
|
+
out << " <div id=\"rowCount\"></div>
|
455
|
+
"
|
456
|
+
end
|
457
|
+
unless show_erd_button == false
|
458
|
+
out << " <div id=\"imgErd\" title=\"Show ERD\"></div>
|
459
|
+
"
|
460
|
+
end
|
461
|
+
if rel && show_in_app_button != false && (in_app = rel.fetch(:existing, nil)&.fetch(:index, nil))
|
462
|
+
begin
|
463
|
+
in_app = send("#{in_app}_path") if in_app.is_a?(Symbol)
|
464
|
+
out << " <td title=\"Show in app\">#{link_to(::Brick::Rails::IN_APP.html_safe, in_app)}</td>
|
465
|
+
"
|
466
|
+
rescue ActionController::UrlGenerationError # Avoid snags like "No route matches {:action=>"index", :controller=>"categories/products"}, missing required keys: [:category_id]"
|
467
|
+
end
|
468
|
+
end
|
469
|
+
if show_avo_button != false && Object.const_defined?('Avo') && ::Avo.respond_to?(:railtie_namespace) && klass.name.exclude?('::')
|
470
|
+
out << "
|
471
|
+
<td>#{link_to_brick(
|
472
|
+
::Brick::Rails::AVO_SVG.html_safe,
|
473
|
+
{ index_proc: Proc.new do |_avo_model, relation|
|
474
|
+
path_helper = "resources_#{relation.fetch(:auto_prefixed_schema, nil)}#{klass.model_name.route_key}_path".to_sym
|
475
|
+
::Avo.railtie_routes_url_helpers.send(path_helper) if ::Avo.railtie_routes_url_helpers.respond_to?(path_helper)
|
476
|
+
end,
|
477
|
+
title: "#{klass.name} in Avo" }
|
478
|
+
)}</td>
|
479
|
+
"
|
480
|
+
end
|
481
|
+
|
482
|
+
if show_aa_button != false && Object.const_defined?('ActiveAdmin')
|
483
|
+
ActiveAdmin.application.namespaces.names.each do |ns|
|
484
|
+
out << "
|
485
|
+
<td>#{link_to_brick(
|
486
|
+
::Brick::Rails::AA_PNG.html_safe,
|
487
|
+
{ index_proc: Proc.new do |aa_model, relation|
|
488
|
+
path_helper = "#{ns}_#{relation.fetch(:auto_prefixed_schema, nil)}#{aa_model.model_name.route_key}_path".to_sym
|
489
|
+
send(path_helper) if respond_to?(path_helper)
|
490
|
+
end,
|
491
|
+
title: "#{rel[:class_name]} in ActiveAdmin" }
|
492
|
+
)}</td>
|
493
|
+
"
|
494
|
+
end
|
495
|
+
end
|
496
|
+
out << " </div>
|
497
|
+
</div>
|
498
|
+
</div>
|
499
|
+
"
|
500
|
+
out
|
501
|
+
end # brick_header
|
502
|
+
|
503
|
+
# -----------------------------------------------------------------------------------------------
|
504
|
+
def set_grid_javascript(klass, pk, show_new_button = nil, row_count = nil, total_row_count = nil)
|
505
|
+
table_name = klass.table_name.split('.').last
|
506
|
+
|
507
|
+
# Javascript for brick_grid and brick_constellation
|
508
|
+
grid_scripts = (@_brick_javascripts ||= {})[:grid_scripts] = +''
|
509
|
+
|
510
|
+
grid_scripts << "
|
511
|
+
// Plunk the row count in now that we know it
|
512
|
+
var rowCount = document.getElementById(\"rowCount\");
|
513
|
+
if (rowCount) rowCount.innerHTML = \"#{pluralize(row_count, "row")}#{total_row_count} \";
|
514
|
+
var #{table_name}HtColumns;
|
515
|
+
" unless row_count.nil?
|
516
|
+
|
517
|
+
grid_scripts << "
|
518
|
+
// Snag first TR for sticky header
|
519
|
+
var grid = document.getElementById(\"#{table_name}\");
|
520
|
+
#{table_name}HtColumns = grid && [grid.getElementsByTagName(\"TR\")[0]];
|
521
|
+
var headerTop = document.getElementById(\"headerTop\");
|
522
|
+
var headerCols;
|
523
|
+
if (grid) {
|
524
|
+
// COLUMN HEADER AND TABLE CELL HIGHLIGHTING
|
525
|
+
var gridHighHeader = null,
|
526
|
+
gridHighCell = null;
|
527
|
+
grid.addEventListener(\"mouseenter\", gridMove);
|
528
|
+
grid.addEventListener(\"mousemove\", gridMove);
|
529
|
+
grid.addEventListener(\"mouseleave\", function (evt) {
|
530
|
+
if (gridHighCell) gridHighCell.classList.remove(\"highlight\");
|
531
|
+
gridHighCell = null;
|
532
|
+
if (gridHighHeader) gridHighHeader.classList.remove(\"highlight\");
|
533
|
+
gridHighHeader = null;
|
534
|
+
});
|
535
|
+
function gridMove(evt) {
|
536
|
+
var lastHighCell = gridHighCell;
|
537
|
+
gridHighCell = document.elementFromPoint(evt.x, evt.y);
|
538
|
+
while (gridHighCell && gridHighCell.tagName !== \"TD\" && gridHighCell.tagName !== \"TH\")
|
539
|
+
gridHighCell = gridHighCell.parentElement;
|
540
|
+
if (gridHighCell) {
|
541
|
+
if (lastHighCell !== gridHighCell) {
|
542
|
+
gridHighCell.classList.add(\"highlight\");
|
543
|
+
if (lastHighCell) lastHighCell.classList.remove(\"highlight\");
|
544
|
+
}
|
545
|
+
var lastHighHeader = gridHighHeader;
|
546
|
+
if ((gridHighHeader = headerCols[gridHighCell.cellIndex]) && lastHighHeader !== gridHighHeader) {
|
547
|
+
if (gridHighHeader) gridHighHeader.classList.add(\"highlight\");
|
548
|
+
if (lastHighHeader) lastHighHeader.classList.remove(\"highlight\");
|
549
|
+
}
|
550
|
+
}
|
551
|
+
}
|
552
|
+
// // Less touchy navigation back or forward in history when using mouse wheel
|
553
|
+
// grid.addEventListener(\"wheel\", function (evt) {
|
554
|
+
// grid.scrollLeft += evt.deltaX;
|
555
|
+
// document.body.scrollTop += (evt.deltaY * 0.6);
|
556
|
+
// evt.preventDefault();
|
557
|
+
// return false;
|
558
|
+
// });
|
559
|
+
}
|
560
|
+
function setHeaderSizes() {
|
561
|
+
if (grid.clientWidth > window.outerWidth)
|
562
|
+
document.getElementById(\"titleBox\").style.width = grid.clientWidth;
|
563
|
+
// console.log(\"start\");
|
564
|
+
// See if the headerTop is already populated
|
565
|
+
// %%% Grab the TRs from headerTop, clear it out, do this stuff, add them back
|
566
|
+
headerTop.innerHTML = \"\"; // %%% Would love to not have to clear it out like this every time! (Currently doing this to support resize events.)
|
567
|
+
var isEmpty = headerTop.childElementCount === 0;
|
568
|
+
var numFixed = parseInt(grid.getAttribute(\"x-num-frozen\")) || 0;
|
569
|
+
var fixedColLefts = [0];
|
570
|
+
|
571
|
+
// Set up proper sizings of sticky column header
|
572
|
+
var node;
|
573
|
+
for (var j = 0; j < #{table_name}HtColumns.length; ++j) {
|
574
|
+
var row = #{table_name}HtColumns[j];
|
575
|
+
var tr = isEmpty ? document.createElement(\"TR\") : headerTop.childNodes[j];
|
576
|
+
tr.innerHTML = row.innerHTML.trim();
|
577
|
+
var curLeft = 0.0;
|
578
|
+
// Match up widths from the original column headers
|
579
|
+
for (var i = 0; i < row.childNodes.length; ++i) {
|
580
|
+
node = row.childNodes[i];
|
581
|
+
if (node.nodeType === 1) {
|
582
|
+
var th = tr.childNodes[i];
|
583
|
+
th.style.minWidth = th.style.maxWidth = getComputedStyle(node).width;
|
584
|
+
// Add \"left: __px\" style to the fixed-width column THs
|
585
|
+
if (i <= numFixed) {
|
586
|
+
th.style.position = \"sticky\";
|
587
|
+
th.style.backgroundColor = \"#008061\";
|
588
|
+
th.style.zIndex = \"1\";
|
589
|
+
th.style.left = curLeft + \"px\";
|
590
|
+
fixedColLefts.push(curLeft += node.clientWidth);
|
591
|
+
}
|
592
|
+
if (#{pk&.present? ? 'i > 0' : 'true'}) {
|
593
|
+
// Add <span> at the end
|
594
|
+
var span = document.createElement(\"SPAN\");
|
595
|
+
span.className = \"exclude\";
|
596
|
+
span.innerHTML = \"X\";
|
597
|
+
span.addEventListener(\"click\", function (e) {
|
598
|
+
e.stopPropagation();
|
599
|
+
doFetch(\"POST\", {_brick_exclude: this.parentElement.getAttribute(\"x-order\")});
|
600
|
+
});
|
601
|
+
th.appendChild(span);
|
602
|
+
}
|
603
|
+
}
|
604
|
+
}
|
605
|
+
headerCols = tr.childNodes;
|
606
|
+
if (isEmpty) headerTop.appendChild(tr);
|
607
|
+
}
|
608
|
+
// Add \"left: __px\" style to all fixed-width column TDs
|
609
|
+
[...grid.children[1].children].forEach(function (row) {
|
610
|
+
for (var j = 1; j <= numFixed; ++j) {
|
611
|
+
row.children[j].style.left = fixedColLefts[j] + 'px';
|
612
|
+
}
|
613
|
+
});
|
614
|
+
grid.style.marginTop = \"-\" + getComputedStyle(headerTop).height;
|
615
|
+
// console.log(\"end\");
|
616
|
+
}
|
617
|
+
|
618
|
+
if (headerTop) {
|
619
|
+
onImagesLoaded(function() {
|
620
|
+
setHeaderSizes();
|
621
|
+
});
|
622
|
+
window.addEventListener(\"resize\", function(event) {
|
623
|
+
setHeaderSizes();
|
624
|
+
}, true);#{
|
625
|
+
"
|
626
|
+
var headerButtonBox = document.getElementById(\"headerButtonBox\");
|
627
|
+
if (headerButtonBox) {
|
628
|
+
var addNew = document.createElement(\"A\");
|
629
|
+
addNew.id = \"addNew\";
|
630
|
+
addNew.href = \"#{link_to_brick(klass, new: true, path_only: true)}\";
|
631
|
+
addNew.title = \"New #{table_name.singularize}\";
|
632
|
+
addNew.innerHTML = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"#fff\" d=\"M24 10h-10v-10h-4v10h-10v4h10v10h4v-10h10z\"/></svg>';
|
633
|
+
headerButtonBox.append(addNew);
|
634
|
+
}
|
635
|
+
" unless klass.is_view? || show_new_button == false
|
636
|
+
}
|
637
|
+
}
|
638
|
+
|
639
|
+
function onImagesLoaded(event) {
|
640
|
+
var images = document.getElementsByTagName(\"IMG\");
|
641
|
+
var numLoaded = images.length;
|
642
|
+
for (var i = 0; i < images.length; ++i) {
|
643
|
+
if (images[i].complete)
|
644
|
+
--numLoaded;
|
645
|
+
else {
|
646
|
+
images[i].addEventListener(\"load\", function() {
|
647
|
+
if (--numLoaded <= 0)
|
648
|
+
event();
|
649
|
+
});
|
650
|
+
}
|
651
|
+
}
|
652
|
+
if (numLoaded <= 0)
|
653
|
+
event();
|
654
|
+
}
|
655
|
+
"
|
656
|
+
end
|
657
|
+
|
658
|
+
# -------------------------------------
|
632
659
|
def _n_m_prep(relation, x_axis, y_axis)
|
633
|
-
relation ||= (instance_variable_get("@#{controller_name}".to_sym) || _brick_resource_from_iv)
|
634
660
|
# Just find the first two BT things at this point
|
635
661
|
|
636
662
|
klass = relation.klass
|
637
|
-
rel = ::Brick.relations&.fetch(relation.table_name, nil)
|
638
|
-
# fk_assocs = rel[:fks].map { |k, fk| [fk[:assoc_name], fk[:fk]] }
|
639
663
|
fk_assocs = klass.reflect_on_all_associations.each_with_object([]) do |assoc, s|
|
640
664
|
s << [assoc.name.to_s, assoc.foreign_key, assoc.klass] if assoc.belongs_to?
|
641
665
|
end
|
@@ -690,8 +714,10 @@ function onImagesLoaded(event) {
|
|
690
714
|
kwargs[:visited] = {}
|
691
715
|
end
|
692
716
|
|
693
|
-
|
694
|
-
|
717
|
+
unless (is_path_only = kwargs.delete(:path_only))
|
718
|
+
text = ((args.first.is_a?(String) || args.first.is_a?(Proc)) && args.shift) || args[1]
|
719
|
+
text = text.call if text.is_a?(Proc)
|
720
|
+
end
|
695
721
|
klass_or_obj = ((args.first.is_a?(ActiveRecord::Relation) ||
|
696
722
|
args.first.is_a?(ActiveRecord::Base) ||
|
697
723
|
args.first.is_a?(Class)) &&
|
@@ -739,6 +765,8 @@ function onImagesLoaded(event) {
|
|
739
765
|
filter_parts << "#{whr.first}=#{whr.last}" if whr.last && !whr.last.is_a?(Array)
|
740
766
|
end
|
741
767
|
klass_or_obj = klass_or_obj.klass
|
768
|
+
end
|
769
|
+
if klass_or_obj.is_a?(Class) && klass_or_obj <= ActiveRecord::Base
|
742
770
|
type_col = klass_or_obj.inheritance_column
|
743
771
|
if klass_or_obj.column_names.include?(type_col) && klass_or_obj.name != klass_or_obj.base_class.name
|
744
772
|
filter_parts << "#{type_col}=#{klass_or_obj.name}"
|
@@ -750,11 +778,22 @@ function onImagesLoaded(event) {
|
|
750
778
|
relation = ::Brick.relations.fetch(rel_name || klass.table_name, nil)
|
751
779
|
if (klass_or_obj&.is_a?(Class) && klass_or_obj < ActiveRecord::Base) ||
|
752
780
|
(klass_or_obj&.is_a?(ActiveRecord::Base) && klass_or_obj.new_record? && (klass_or_obj = klass_or_obj.class))
|
753
|
-
|
754
|
-
|
781
|
+
if kwargs.delete(:new)
|
782
|
+
path = (proc = kwargs[:new_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(:singular, '/', relation, true), action: :new)}#{filter}"
|
783
|
+
return path if is_path_only
|
784
|
+
|
785
|
+
lt_args = [text || "New #{klass_or_obj.name}", path]
|
786
|
+
else
|
787
|
+
path = (proc = kwargs[:index_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.base_class._brick_index(nil, '/', relation, true), action: :index)}#{filter}"
|
788
|
+
return path if is_path_only
|
789
|
+
|
790
|
+
lt_args = [text || "Index for #{klass_or_obj.name.pluralize}", path]
|
791
|
+
end
|
755
792
|
else
|
756
793
|
# If there are multiple incoming parameters then last one is probably the actual ID, and first few might be some nested tree of stuff leading up to it
|
757
794
|
path = (proc = kwargs[:show_proc]) ? proc.call(klass_or_obj, relation) : "#{app_routes.path_for(controller: klass_or_obj.class.base_class._brick_index(nil, '/', relation, true), action: :show, id: klass_or_obj)}#{filter}"
|
795
|
+
return path if is_path_only
|
796
|
+
|
758
797
|
lt_args = [text || "Show this #{klass_or_obj.class.name}", path]
|
759
798
|
end
|
760
799
|
kwargs.delete(:visited)
|
@@ -173,7 +173,6 @@ erDiagram
|
|
173
173
|
<%= erd_sidelinks(shown_classes, hm_class).html_safe %>
|
174
174
|
<% end
|
175
175
|
def dt_lookup(dt)
|
176
|
-
puts dt.inspect
|
177
176
|
{ 'integer' => 'int', 'character varying' => 'varchar', 'double precision' => 'float',
|
178
177
|
'timestamp without time zone' => 'timestamp',
|
179
178
|
'timestamp with time zone' => 'timestamp',
|
data/lib/brick/join_array.rb
CHANGED
@@ -165,6 +165,12 @@ module Brick
|
|
165
165
|
end.tap { |member| push(member) }
|
166
166
|
end
|
167
167
|
end
|
168
|
+
|
169
|
+
def add_parts(parts)
|
170
|
+
s = self
|
171
|
+
parts[0..-3].each { |part| s = s[part.to_sym] }
|
172
|
+
s[parts[-2].to_sym] = nil # unless parts[-2].empty? # Using []= will "hydrate" any missing part(s) in our whole series
|
173
|
+
end
|
168
174
|
end
|
169
175
|
|
170
176
|
class JoinHash < Hash
|
data/lib/brick/reflect_tables.rb
CHANGED
@@ -341,7 +341,7 @@ ORDER BY 1, 2, c.internal_column_id, acc.position"
|
|
341
341
|
fk[4].downcase! if fk[4] =~ /^[A-Z0-9_]+$/
|
342
342
|
fk[2] = connection.send(:oracle_downcase, fk[2])
|
343
343
|
end
|
344
|
-
::Brick._add_bt_and_hm(fk, relations)
|
344
|
+
::Brick._add_bt_and_hm(fk, relations, nil, nil)
|
345
345
|
end
|
346
346
|
kcus = nil # Allow this large item to be garbage collected
|
347
347
|
end
|
data/lib/brick/version_number.rb
CHANGED
@@ -53,7 +53,12 @@ module Brick
|
|
53
53
|
s[v_parts.first] = nil unless [::Brick.default_schema, 'public'].include?(v_parts.first)
|
54
54
|
end
|
55
55
|
end
|
56
|
-
seeds = +
|
56
|
+
seeds = +'# Seeds file for '
|
57
|
+
if (arbc = ActiveRecord::Base.connection).respond_to?(:current_database) # SQLite3 can't do this!
|
58
|
+
seeds << "#{arbc.current_database}:\n"
|
59
|
+
elsif (filename = arbc.instance_variable_get(:@connection_parameters)&.fetch(:database, nil))
|
60
|
+
seeds << "#{filename}:\n"
|
61
|
+
end
|
57
62
|
done = []
|
58
63
|
fks = {}
|
59
64
|
stuck = {}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brick
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.214
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lorin Thwaits
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|