hyper-model 1.0.alpha1.1 → 1.0.alpha1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -6
- data/Rakefile +27 -3
- data/hyper-model.gemspec +10 -19
- data/lib/active_record_base.rb +101 -33
- data/lib/hyper-model.rb +4 -2
- data/lib/hyper_model/version.rb +1 -1
- data/lib/hyper_react/input_tags.rb +2 -1
- data/lib/reactive_record/active_record/associations.rb +130 -34
- data/lib/reactive_record/active_record/base.rb +17 -0
- data/lib/reactive_record/active_record/class_methods.rb +124 -52
- data/lib/reactive_record/active_record/error.rb +2 -0
- data/lib/reactive_record/active_record/errors.rb +10 -6
- data/lib/reactive_record/active_record/instance_methods.rb +74 -6
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
- data/lib/reactive_record/active_record/reactive_record/base.rb +56 -30
- data/lib/reactive_record/active_record/reactive_record/collection.rb +219 -70
- data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
- data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +73 -46
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
- data/lib/reactive_record/active_record/reactive_record/operations.rb +10 -3
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -0
- data/lib/reactive_record/active_record/reactive_record/setters.rb +108 -71
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +258 -41
- data/lib/reactive_record/broadcast.rb +62 -25
- data/lib/reactive_record/interval.rb +3 -3
- data/lib/reactive_record/permissions.rb +14 -2
- data/lib/reactive_record/scope_description.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +99 -49
- data/polymorph-notes.md +143 -0
- data/spec_fails.txt +3 -0
- metadata +49 -162
- data/Gemfile.lock +0 -431
@@ -14,15 +14,15 @@ module ReactiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def waiting_for_save(model)
|
17
|
-
@waiting_for_save[model]
|
17
|
+
@waiting_for_save[model.base_class]
|
18
18
|
end
|
19
19
|
|
20
20
|
def wait_for_save(model, &block)
|
21
|
-
@waiting_for_save[model] << block
|
21
|
+
@waiting_for_save[model.base_class] << block
|
22
22
|
end
|
23
23
|
|
24
24
|
def clear_waiting_for_save(model)
|
25
|
-
@waiting_for_save[model] = []
|
25
|
+
@waiting_for_save[model.base_class] = []
|
26
26
|
end
|
27
27
|
|
28
28
|
def lookup_by_object_id(object_id)
|
@@ -33,8 +33,8 @@ module ReactiveRecord
|
|
33
33
|
`#{@records_by_object_id}[#{record.object_id}] = #{record}`
|
34
34
|
end
|
35
35
|
|
36
|
-
def lookup_by_id(
|
37
|
-
`#{@records_by_id}[#{
|
36
|
+
def lookup_by_id(model, id) # model and id
|
37
|
+
`#{@records_by_id}[#{[model.base_class, id]}]` || nil
|
38
38
|
end
|
39
39
|
|
40
40
|
def set_id_lookup(record)
|
@@ -12,6 +12,10 @@ module ReactiveRecord
|
|
12
12
|
|
13
13
|
FORMAT = '0x%x'
|
14
14
|
|
15
|
+
class << self
|
16
|
+
attr_accessor :last_response_sent_at
|
17
|
+
end
|
18
|
+
|
15
19
|
def self.serialize_params(hash)
|
16
20
|
hash['associations'].each do |assoc|
|
17
21
|
assoc['parent_id'] = FORMAT % assoc['parent_id']
|
@@ -38,6 +42,7 @@ module ReactiveRecord
|
|
38
42
|
response[:saved_models].each do |saved_model|
|
39
43
|
saved_model[0] = FORMAT % saved_model[0]
|
40
44
|
end if response.is_a?(Hash) && response[:saved_models]
|
45
|
+
response[:sent_at] = Time.now.to_f
|
41
46
|
response
|
42
47
|
end
|
43
48
|
|
@@ -45,6 +50,7 @@ module ReactiveRecord
|
|
45
50
|
response[:saved_models].each do |saved_model|
|
46
51
|
saved_model[0] = saved_model[0].to_i(16)
|
47
52
|
end if response.is_a?(Hash) && response[:saved_models]
|
53
|
+
Base.last_response_sent_at = response.delete(:sent_at)
|
48
54
|
response
|
49
55
|
end
|
50
56
|
end
|
@@ -65,8 +71,9 @@ module ReactiveRecord
|
|
65
71
|
]
|
66
72
|
end
|
67
73
|
failed do |e|
|
68
|
-
|
69
|
-
|
74
|
+
e.define_singleton_method(:__hyperstack_on_error) do |operation, params, fmted_message|
|
75
|
+
Hyperstack.on_error(operation, self, params, fmted_message)
|
76
|
+
end
|
70
77
|
raise e
|
71
78
|
end
|
72
79
|
end
|
@@ -92,7 +99,7 @@ module ReactiveRecord
|
|
92
99
|
class Destroy < Base
|
93
100
|
param :acting_user, nils: true
|
94
101
|
param :model
|
95
|
-
param :id
|
102
|
+
param :id, nils: true
|
96
103
|
param :vector
|
97
104
|
step do
|
98
105
|
ReactiveRecord::Base.destroy_record(
|
@@ -9,7 +9,10 @@ module ReactiveRecord
|
|
9
9
|
def set_pre_sync_related_records(related_records, _record = nil)
|
10
10
|
@pre_sync_related_records = nil
|
11
11
|
ReactiveRecord::Base.catch_db_requests do
|
12
|
+
# puts "#{self}.set_pre_sync_related_records filter_records(#{related_records})"
|
13
|
+
|
12
14
|
@pre_sync_related_records = filter_records(related_records)
|
15
|
+
# puts "returns #{@pre_sync_related_records}"
|
13
16
|
live_scopes.each do |scope|
|
14
17
|
scope.set_pre_sync_related_records(@pre_sync_related_records)
|
15
18
|
end
|
@@ -43,17 +43,26 @@ module ReactiveRecord
|
|
43
43
|
|
44
44
|
def set_belongs_to(assoc, raw_value)
|
45
45
|
set_common(assoc.attribute, raw_value) do |value, attr|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# itself will just reactively read the value (a model instance) by doing a .id
|
53
|
-
update_belongs_to attr, value.itself
|
46
|
+
current_value = @attributes[assoc.attribute]
|
47
|
+
update_has_many_through_associations assoc, nil, current_value, :remove_member
|
48
|
+
update_has_many_through_associations assoc, nil, value, :add_member
|
49
|
+
remove_current_inverse_attribute assoc, nil, current_value
|
50
|
+
add_new_inverse_attribute assoc, nil, value
|
51
|
+
update_belongs_to attr, value.itself
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
55
|
+
def set_belongs_to_via_has_many(orig, value)
|
56
|
+
assoc = orig.inverse
|
57
|
+
attr = assoc.attribute
|
58
|
+
current_value = @attributes[attr]
|
59
|
+
update_has_many_through_associations assoc, orig, current_value, :remove_member
|
60
|
+
update_has_many_through_associations assoc, orig, value, :add_member
|
61
|
+
remove_current_inverse_attribute assoc, orig, current_value
|
62
|
+
add_new_inverse_attribute assoc, orig, value
|
63
|
+
update_belongs_to attr, value.itself
|
64
|
+
end
|
65
|
+
|
57
66
|
def sync_has_many(attr)
|
58
67
|
set_change_status_and_notify_only attr, @attributes[attr] != @synced_attributes[attr]
|
59
68
|
end
|
@@ -85,30 +94,28 @@ module ReactiveRecord
|
|
85
94
|
end
|
86
95
|
|
87
96
|
def set_attribute_change_status_and_notify(attr, changed, new_value)
|
88
|
-
if @virgin
|
97
|
+
if @virgin || @being_destroyed
|
89
98
|
@attributes[attr] = new_value
|
90
99
|
else
|
91
100
|
change_status_and_notify_helper(attr, changed) do |had_key, current_value|
|
92
101
|
@attributes[attr] = new_value
|
93
102
|
if !data_loading? ||
|
94
103
|
(on_opal_client? && had_key && current_value.loaded? && current_value != new_value)
|
95
|
-
Hyperstack::Internal::
|
104
|
+
Hyperstack::Internal::State::Variable.set(self, attr, new_value, data_loading?)
|
96
105
|
end
|
97
106
|
end
|
98
107
|
end
|
99
108
|
end
|
100
109
|
|
101
110
|
def set_change_status_and_notify_only(attr, changed)
|
102
|
-
return if @virgin
|
111
|
+
return if @virgin || @being_destroyed
|
103
112
|
change_status_and_notify_helper(attr, changed) do
|
104
|
-
Hyperstack::Internal::
|
113
|
+
Hyperstack::Internal::State::Variable.set(self, attr, nil) unless data_loading?
|
105
114
|
end
|
106
115
|
end
|
107
116
|
|
108
117
|
def change_status_and_notify_helper(attr, changed)
|
109
118
|
empty_before = changed_attributes.empty?
|
110
|
-
# TODO: confirm this works:
|
111
|
-
# || data_loading? added so that model.new can be wrapped in a ReactiveRecord.load_data
|
112
119
|
if !changed || data_loading?
|
113
120
|
changed_attributes.delete(attr)
|
114
121
|
elsif !changed_attributes.include?(attr)
|
@@ -117,7 +124,7 @@ module ReactiveRecord
|
|
117
124
|
yield @attributes.key?(attr), @attributes[attr]
|
118
125
|
return unless empty_before != changed_attributes.empty?
|
119
126
|
if on_opal_client? && !data_loading?
|
120
|
-
Hyperstack::Internal::
|
127
|
+
Hyperstack::Internal::State::Variable.set(self, '!CHANGED!', !changed_attributes.empty?, true)
|
121
128
|
end
|
122
129
|
return unless aggregate_owner
|
123
130
|
aggregate_owner.set_change_status_and_notify_only(
|
@@ -125,70 +132,100 @@ module ReactiveRecord
|
|
125
132
|
)
|
126
133
|
end
|
127
134
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
current_value.attributes[inverse_attr].delete(@ar_instance) unless current_value.nil?
|
135
|
+
# when updating the inverse attribute of a belongs_to that is itself a belongs_to
|
136
|
+
# (i.e. 1-1 relationship) we clear the existing inverse value and then
|
137
|
+
# write the current record to the new value
|
138
|
+
|
139
|
+
# when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
|
140
|
+
# we need to first remove the current associated value (if non-nil), then add the new
|
141
|
+
# value to the collection. If the inverse collection is not yet initialized we do it here.
|
142
|
+
|
143
|
+
# the above is split into three methods, because the inverse of apolymorphic belongs to may
|
144
|
+
# change from has_one to has_many. So we first deal with the current value, then
|
145
|
+
# update the new value which uses the push_onto_collection helper
|
146
|
+
|
147
|
+
def remove_current_inverse_attribute(association, orig, model)
|
148
|
+
return if model.nil?
|
149
|
+
inverse_association = association.inverse(model)
|
150
|
+
return if inverse_association == orig
|
151
|
+
if inverse_association.collection?
|
152
|
+
# note we don't have to check if the collection exists, since it must
|
153
|
+
# exist as at this ar_instance is already part of it.
|
154
|
+
model.attributes[inverse_association.attribute].delete(@ar_instance)
|
149
155
|
else
|
150
|
-
|
156
|
+
model.attributes[inverse_association.attribute] = nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_new_inverse_attribute(association, orig, model)
|
161
|
+
return if model.nil?
|
162
|
+
inverse_association = association.inverse(model)
|
163
|
+
return if inverse_association == orig
|
164
|
+
if inverse_association.collection?
|
165
|
+
model.backing_record.push_onto_collection(@model, inverse_association, @ar_instance)
|
166
|
+
else
|
167
|
+
inverse_attr = inverse_association.attribute
|
168
|
+
model.attributes[inverse_attr] = @ar_instance
|
169
|
+
return if data_loading?
|
170
|
+
Hyperstack::Internal::State::Variable.set(model.backing_record, inverse_attr, @ar_instance)
|
151
171
|
end
|
152
172
|
end
|
153
173
|
|
154
174
|
def push_onto_collection(model, association, ar_instance)
|
155
175
|
@attributes[association.attribute] ||= Collection.new(model, @ar_instance, association)
|
156
|
-
@attributes[association.attribute]
|
176
|
+
@attributes[association.attribute]._internal_push ar_instance
|
177
|
+
end
|
178
|
+
|
179
|
+
# class Membership < ActiveRecord::Base
|
180
|
+
# belongs_to :uzer
|
181
|
+
# belongs_to :memerable, polymorphic: true
|
182
|
+
# end
|
183
|
+
#
|
184
|
+
# class Project < ActiveRecord::Base
|
185
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
186
|
+
# has_many :uzers, through: :memberships
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# class Group < ActiveRecord::Base
|
190
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
191
|
+
# has_many :uzers, through: :memberships
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# class Uzer < ActiveRecord::Base
|
195
|
+
# has_many :memberships
|
196
|
+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
|
197
|
+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
|
198
|
+
# end
|
199
|
+
|
200
|
+
# membership.uzer = some_new_uzer (i.e. through association is changing)
|
201
|
+
# means membership.some_new_uzer.(groups OR projects) << uzer.memberable (depending on type of memberable)
|
202
|
+
# and we have to remove the current value of the source association (memerable) from the current uzer group or project
|
203
|
+
# and we have to then find any inverse has_many_through association (i.e. group or projects.uzers) and delete the
|
204
|
+
# current value from those collections and push the new value on
|
205
|
+
|
206
|
+
def update_has_many_through_associations(assoc, orig, value, method)
|
207
|
+
return if value.nil?
|
208
|
+
assoc.through_associations(value).each do |ta|
|
209
|
+
next if orig == ta
|
210
|
+
source_value = @attributes[ta.source]
|
211
|
+
# skip if source value is nil or if type of the association does not match type of source
|
212
|
+
next unless source_value.class.to_s == ta.source_type
|
213
|
+
ta.send method, source_value, value
|
214
|
+
ta.source_associations(source_value).each do |sa|
|
215
|
+
sa.send method, value, source_value
|
216
|
+
end
|
217
|
+
end
|
157
218
|
end
|
158
219
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
end
|
220
|
+
# def remove_src_assoc(sa, source_value, current_value)
|
221
|
+
# source_inverse_collection = source_value.attributes[sa.attribute]
|
222
|
+
# source_inverse_collection.delete(current_value) if source_inverse_collection
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# def add_src_assoc(sa, source_value, new_value)
|
226
|
+
# source_value.attributes[sa.attribute] ||= Collection.new(sa.owner_class, source_value, sa)
|
227
|
+
# source_value.attributes[sa.attribute] << new_value
|
228
|
+
# end
|
163
229
|
|
164
|
-
def update_through_association(ta, new_belongs_to_value)
|
165
|
-
# appointment.doctor = doctor_new_value (i.e. through association is changing)
|
166
|
-
# means appointment.doctor_new_value.patients << appointment.patient
|
167
|
-
# and we have to appointment.doctor_current_value.patients.delete(appointment.patient)
|
168
|
-
source_value = @attributes[ta.source]
|
169
|
-
current_belongs_to_value = @attributes[ta.inverse.attribute]
|
170
|
-
return unless source_value
|
171
|
-
unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil?
|
172
|
-
current_belongs_to_value.attributes[ta.attribute].delete(source_value)
|
173
|
-
end
|
174
|
-
return unless new_belongs_to_value
|
175
|
-
new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(ta.klass, new_belongs_to_value, ta)
|
176
|
-
new_belongs_to_value.attributes[ta.attribute] << source_value
|
177
|
-
end
|
178
|
-
|
179
|
-
def update_source_association(sa, new_source_value)
|
180
|
-
# appointment.patient = patient_value (i.e. source is changing)
|
181
|
-
# means appointment.doctor.patients.delete(appointment.patient)
|
182
|
-
# means appointment.doctor.patients << patient_value
|
183
|
-
belongs_to_value = @attributes[sa.inverse.attribute]
|
184
|
-
current_source_value = @attributes[sa.source]
|
185
|
-
return unless belongs_to_value
|
186
|
-
unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil?
|
187
|
-
belongs_to_value.attributes[sa.attribute].delete(current_source_value)
|
188
|
-
end
|
189
|
-
return unless new_source_value
|
190
|
-
belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass, belongs_to_value, sa)
|
191
|
-
belongs_to_value.attributes[sa.attribute] << new_source_value
|
192
|
-
end
|
193
230
|
end
|
194
231
|
end
|
@@ -74,6 +74,191 @@ module ReactiveRecord
|
|
74
74
|
# To notify React that something is loading use React::WhileLoading.loading!
|
75
75
|
# once everything is loaded then do React::WhileLoading.loaded_at message (typically a time stamp just for debug purposes)
|
76
76
|
|
77
|
+
# class WhileLoading
|
78
|
+
#
|
79
|
+
# include Hyperstack::Component::IsomorphicHelpers
|
80
|
+
#
|
81
|
+
# before_first_mount do
|
82
|
+
# @css_to_preload = ""
|
83
|
+
# @while_loading_counter = 0
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# def self.get_next_while_loading_counter
|
87
|
+
# @while_loading_counter += 1
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# def self.preload_css(css)
|
91
|
+
# @css_to_preload += "#{css}\n"
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# def self.has_observers?
|
95
|
+
# Hyperstack::Internal::State::Variable.observed?(self, :loaded_at)
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# class << self
|
99
|
+
# alias :observed? :has_observers?
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# prerender_footer do
|
103
|
+
# "<style>\n#{@css_to_preload}\n</style>".tap { @css_to_preload = ""}
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# if RUBY_ENGINE == 'opal'
|
107
|
+
#
|
108
|
+
# # +: I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
|
109
|
+
# # -: You think wrong. add_style_sheet uses the jQuery $, after_mount too, others too
|
110
|
+
# # -: I removed those references. Now you think right.
|
111
|
+
#
|
112
|
+
# include Hyperstack::Component
|
113
|
+
#
|
114
|
+
# param :render_original_child
|
115
|
+
# param :loading
|
116
|
+
#
|
117
|
+
# class << self
|
118
|
+
#
|
119
|
+
# def loading?
|
120
|
+
# @is_loading
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# def loading!
|
124
|
+
# Hyperstack::Internal::Component::RenderingContext.waiting_on_resources = true
|
125
|
+
# Hyperstack::Internal::State::Variable.get(self, :loaded_at)
|
126
|
+
# # this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
|
127
|
+
# # Hyperstack::Internal::State::Variable.set(self, :quiet, false)
|
128
|
+
# @is_loading = true
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# def loaded_at(loaded_at)
|
132
|
+
# Hyperstack::Internal::State::Variable.set(self, :loaded_at, loaded_at)
|
133
|
+
# @is_loading = false
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# def quiet?
|
137
|
+
# Hyperstack::Internal::State::Variable.get(self, :quiet)
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# def page_loaded?
|
141
|
+
# Hyperstack::Internal::State::Variable.get(self, :page_loaded)
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# def quiet!
|
145
|
+
# Hyperstack::Internal::State::Variable.set(self, :quiet, true)
|
146
|
+
# after(1) { Hyperstack::Internal::State::Variable.set(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
|
147
|
+
# @page_loaded = true
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# def add_style_sheet
|
151
|
+
# # directly assigning the code to the variable triggers a opal 0.10.5 compiler bug.
|
152
|
+
# unless @style_sheet_added
|
153
|
+
# %x{
|
154
|
+
# var style_el = document.createElement("style");
|
155
|
+
# style_el.setAttribute("type", "text/css");
|
156
|
+
# style_el.innerHTML = ".reactive_record_is_loading > .reactive_record_show_when_loaded { display: none !important; }\n" +
|
157
|
+
# ".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none !important; }";
|
158
|
+
# document.head.append(style_el);
|
159
|
+
# }
|
160
|
+
# @style_sheet_added = true
|
161
|
+
# end
|
162
|
+
# end
|
163
|
+
#
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# def after_mount_and_update
|
167
|
+
# @waiting_on_resources = @Loading
|
168
|
+
# node = dom_node
|
169
|
+
# %x{
|
170
|
+
# Array.from(node.children).forEach(
|
171
|
+
# function(current_node, current_index, list_obj) {
|
172
|
+
# if (current_index > 0 && current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
|
173
|
+
# current_node.className = current_node.className + ' reactive_record_show_when_loaded';
|
174
|
+
# } else if (current_index == 0 && current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
|
175
|
+
# current_node.className = current_node.className + ' reactive_record_show_while_loading';
|
176
|
+
# }
|
177
|
+
# }
|
178
|
+
# );
|
179
|
+
# }
|
180
|
+
# nil
|
181
|
+
# end
|
182
|
+
#
|
183
|
+
# before_mount do
|
184
|
+
# @uniq_id = WhileLoading.get_next_while_loading_counter
|
185
|
+
# WhileLoading.preload_css(
|
186
|
+
# ":not(.reactive_record_is_loading).reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1) {\n"+
|
187
|
+
# " display: none;\n"+
|
188
|
+
# "}\n"
|
189
|
+
# )
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# after_mount do
|
193
|
+
# WhileLoading.add_style_sheet
|
194
|
+
# after_mount_and_update
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# after_update :after_mount_and_update
|
198
|
+
#
|
199
|
+
# render do
|
200
|
+
# @RenderOriginalChild.call(@uniq_id)
|
201
|
+
# end
|
202
|
+
#
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
# end
|
206
|
+
#
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# module Hyperstack
|
210
|
+
# module Component
|
211
|
+
#
|
212
|
+
# class Element
|
213
|
+
#
|
214
|
+
# def while_loading(display = "", &loading_display_block)
|
215
|
+
# original_block = block || -> () {}
|
216
|
+
#
|
217
|
+
# if display.respond_to? :as_node
|
218
|
+
# display = display.as_node
|
219
|
+
# block = lambda { display.render; instance_eval(&original_block) }
|
220
|
+
# elsif !loading_display_block
|
221
|
+
# block = lambda { display; instance_eval(&original_block) }
|
222
|
+
# else
|
223
|
+
# block = ->() { instance_eval(&loading_display_block); instance_eval(&original_block) }
|
224
|
+
# end
|
225
|
+
# loading_child = Internal::Component::RenderingContext.build do |buffer|
|
226
|
+
# Hyperstack::Internal::Component::RenderingContext.render(:span, key: 1, &loading_display_block) #{ to_s }
|
227
|
+
# buffer.dup
|
228
|
+
# end
|
229
|
+
# children = `#{@native}.props.children.slice(0)`
|
230
|
+
# children.unshift(loading_child[0].instance_eval { @native })
|
231
|
+
# @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n}, #{children})`
|
232
|
+
# render_original_child = lambda do |uniq_id|
|
233
|
+
# classes = [
|
234
|
+
# @properties[:class], @properties[:className],
|
235
|
+
# "reactive_record_while_loading_container_#{uniq_id}"
|
236
|
+
# ].compact.join(' ')
|
237
|
+
# @properties.merge!({
|
238
|
+
# "data-reactive_record_while_loading_container_id" => uniq_id,
|
239
|
+
# "data-reactive_record_enclosing_while_loading_container_id" => uniq_id,
|
240
|
+
# className: classes
|
241
|
+
# })
|
242
|
+
# @native = `React.cloneElement(#{@native}, #{@properties.shallow_to_n})`
|
243
|
+
# render
|
244
|
+
# end
|
245
|
+
# delete
|
246
|
+
# ReactAPI.create_element(
|
247
|
+
# ReactiveRecord::WhileLoading,
|
248
|
+
# loading: waiting_on_resources,
|
249
|
+
# render_original_child: render_original_child)
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# def hide_while_loading
|
253
|
+
# while_loading
|
254
|
+
# end
|
255
|
+
#
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
# end
|
259
|
+
#
|
260
|
+
|
261
|
+
|
77
262
|
class WhileLoading
|
78
263
|
|
79
264
|
include Hyperstack::Component::IsomorphicHelpers
|
@@ -92,7 +277,7 @@ module ReactiveRecord
|
|
92
277
|
end
|
93
278
|
|
94
279
|
def self.has_observers?
|
95
|
-
Hyperstack::Internal::
|
280
|
+
Hyperstack::Internal::State::Variable.observed?(self, :loaded_at)
|
96
281
|
end
|
97
282
|
|
98
283
|
class << self
|
@@ -116,6 +301,7 @@ module ReactiveRecord
|
|
116
301
|
param :loading_children
|
117
302
|
param :element_type
|
118
303
|
param :element_props
|
304
|
+
others :other_props
|
119
305
|
param :display, default: ''
|
120
306
|
|
121
307
|
class << self
|
@@ -126,28 +312,28 @@ module ReactiveRecord
|
|
126
312
|
|
127
313
|
def loading!
|
128
314
|
Hyperstack::Internal::Component::RenderingContext.waiting_on_resources = true
|
129
|
-
Hyperstack::Internal::
|
315
|
+
Hyperstack::Internal::State::Variable.get(self, :loaded_at)
|
130
316
|
# this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
|
131
|
-
# Hyperstack::Internal::
|
317
|
+
# Hyperstack::Internal::State::Variable.set(self, :quiet, false)
|
132
318
|
@is_loading = true
|
133
319
|
end
|
134
320
|
|
135
321
|
def loaded_at(loaded_at)
|
136
|
-
Hyperstack::Internal::
|
322
|
+
Hyperstack::Internal::State::Variable.set(self, :loaded_at, loaded_at)
|
137
323
|
@is_loading = false
|
138
324
|
end
|
139
325
|
|
140
326
|
def quiet?
|
141
|
-
Hyperstack::Internal::
|
327
|
+
Hyperstack::Internal::State::Variable.get(self, :quiet)
|
142
328
|
end
|
143
329
|
|
144
330
|
def page_loaded?
|
145
|
-
Hyperstack::Internal::
|
331
|
+
Hyperstack::Internal::State::Variable.get(self, :page_loaded)
|
146
332
|
end
|
147
333
|
|
148
334
|
def quiet!
|
149
|
-
Hyperstack::Internal::
|
150
|
-
after(1) { Hyperstack::Internal::
|
335
|
+
Hyperstack::Internal::State::Variable.set(self, :quiet, true)
|
336
|
+
after(1) { Hyperstack::Internal::State::Variable.set(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
|
151
337
|
@page_loaded = true
|
152
338
|
end
|
153
339
|
|
@@ -157,8 +343,8 @@ module ReactiveRecord
|
|
157
343
|
%x{
|
158
344
|
var style_el = document.createElement("style");
|
159
345
|
style_el.setAttribute("type", "text/css");
|
160
|
-
style_el.innerHTML = ".reactive_record_is_loading > .reactive_record_show_when_loaded { display: none; }\n" +
|
161
|
-
".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none; }";
|
346
|
+
style_el.innerHTML = ".reactive_record_is_loading > .reactive_record_show_when_loaded { display: none !important; }\n" +
|
347
|
+
".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none !important; }";
|
162
348
|
document.head.append(style_el);
|
163
349
|
}
|
164
350
|
@style_sheet_added = true
|
@@ -167,53 +353,57 @@ module ReactiveRecord
|
|
167
353
|
|
168
354
|
end
|
169
355
|
|
170
|
-
|
171
|
-
@uniq_id = WhileLoading.get_next_while_loading_counter
|
172
|
-
WhileLoading.preload_css(
|
173
|
-
".reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1n+#{@LoadedChildren.count+1}) {\n"+
|
174
|
-
" display: none;\n"+
|
175
|
-
"}\n"
|
176
|
-
)
|
177
|
-
end
|
178
|
-
|
179
|
-
after_mount do
|
356
|
+
def after_mount_and_update(*)
|
180
357
|
@waiting_on_resources = @Loading
|
181
|
-
WhileLoading.add_style_sheet
|
182
358
|
node = dom_node
|
183
359
|
%x{
|
184
|
-
|
185
|
-
nodes.forEach(
|
360
|
+
Array.from(node.children).forEach(
|
186
361
|
function(current_node, current_index, list_obj) {
|
187
|
-
if (current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
|
362
|
+
if (current_index > 0 && current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
|
188
363
|
current_node.className = current_node.className + ' reactive_record_show_when_loaded';
|
189
|
-
}
|
190
|
-
}
|
191
|
-
);
|
192
|
-
nodes = node.querySelectorAll(':nth-child(1n+'+#{@LoadedChildren.count+1}+')');
|
193
|
-
nodes.forEach(
|
194
|
-
function(current_node, current_index, list_obj) {
|
195
|
-
if (current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
|
364
|
+
} else if (current_index == 0 && current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
|
196
365
|
current_node.className = current_node.className + ' reactive_record_show_while_loading';
|
197
366
|
}
|
198
367
|
}
|
199
368
|
);
|
200
369
|
}
|
370
|
+
nil
|
201
371
|
end
|
202
372
|
|
203
|
-
|
204
|
-
@
|
373
|
+
before_mount do
|
374
|
+
@uniq_id = WhileLoading.get_next_while_loading_counter
|
375
|
+
WhileLoading.preload_css(
|
376
|
+
":not(.reactive_record_is_loading).reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1) {\n"+
|
377
|
+
" display: none;\n"+
|
378
|
+
"}\n"
|
379
|
+
)
|
380
|
+
end
|
381
|
+
|
382
|
+
after_mount do
|
383
|
+
WhileLoading.add_style_sheet
|
384
|
+
after_mount_and_update
|
205
385
|
end
|
206
386
|
|
207
|
-
|
387
|
+
after_update :after_mount_and_update
|
388
|
+
|
389
|
+
render do
|
390
|
+
# return ReactAPI.create_element(@ElementType[0], @ElementProps.dup) do
|
391
|
+
# @LoadedChildren
|
392
|
+
# end
|
208
393
|
props = @ElementProps.dup
|
209
|
-
classes = [
|
394
|
+
classes = [
|
395
|
+
props[:class], props[:className],
|
396
|
+
@OtherProps.delete(:class), @OtherProps.delete(:className),
|
397
|
+
"reactive_record_while_loading_container_#{@uniq_id}"
|
398
|
+
].compact.join(" ")
|
210
399
|
props.merge!({
|
211
400
|
"data-reactive_record_while_loading_container_id" => @uniq_id,
|
212
401
|
"data-reactive_record_enclosing_while_loading_container_id" => @uniq_id,
|
213
402
|
class: classes
|
214
403
|
})
|
404
|
+
props.merge!(@OtherProps)
|
215
405
|
ReactAPI.create_element(@ElementType[0], props) do
|
216
|
-
@
|
406
|
+
@LoadingChildren + @LoadedChildren
|
217
407
|
end.tap { |e| e.waiting_on_resources = @Loading }
|
218
408
|
end
|
219
409
|
|
@@ -231,6 +421,10 @@ module Hyperstack
|
|
231
421
|
def while_loading(display = "", &loading_display_block)
|
232
422
|
loaded_children = []
|
233
423
|
loaded_children = block.call.dup if block
|
424
|
+
if loaded_children.last.is_a? String
|
425
|
+
loaded_children <<
|
426
|
+
Hyperstack::Internal::Component::ReactWrapper.create_element(:span) { loaded_children.pop }
|
427
|
+
end
|
234
428
|
if display.respond_to? :as_node
|
235
429
|
display = display.as_node
|
236
430
|
loading_display_block = lambda { display.render }
|
@@ -238,12 +432,10 @@ module Hyperstack
|
|
238
432
|
loading_display_block = lambda { display }
|
239
433
|
end
|
240
434
|
loading_children = Internal::Component::RenderingContext.build do |buffer|
|
241
|
-
|
242
|
-
result = result.to_s if result.try :acts_as_string?
|
243
|
-
result.span.tap { |e| e.waiting_on_resources = Internal::Component::RenderingContext.waiting_on_resources } if result.is_a? String
|
435
|
+
Hyperstack::Internal::Component::RenderingContext.render(:span, &loading_display_block) #{ to_s }
|
244
436
|
buffer.dup
|
245
437
|
end
|
246
|
-
|
438
|
+
as_node
|
247
439
|
new_element = ReactAPI.create_element(
|
248
440
|
ReactiveRecord::WhileLoading,
|
249
441
|
loading: waiting_on_resources,
|
@@ -252,7 +444,7 @@ module Hyperstack
|
|
252
444
|
element_type: [type],
|
253
445
|
element_props: properties)
|
254
446
|
|
255
|
-
Internal::Component::RenderingContext.replace(self, new_element)
|
447
|
+
#Internal::Component::RenderingContext.replace(self, new_element)
|
256
448
|
end
|
257
449
|
|
258
450
|
def hide_while_loading
|
@@ -267,6 +459,10 @@ if RUBY_ENGINE == 'opal'
|
|
267
459
|
module Hyperstack
|
268
460
|
module Component
|
269
461
|
|
462
|
+
def quiet?
|
463
|
+
Hyperstack::Internal::State::Variable.get(ReactiveRecord::WhileLoading, :quiet)
|
464
|
+
end
|
465
|
+
|
270
466
|
alias_method :original_component_did_mount, :component_did_mount
|
271
467
|
|
272
468
|
def component_did_mount(*args)
|
@@ -282,6 +478,27 @@ if RUBY_ENGINE == 'opal'
|
|
282
478
|
reactive_record_link_set_while_loading_container_class
|
283
479
|
end
|
284
480
|
|
481
|
+
# This is required to support legacy browsers (Internet Explorer 9+)
|
482
|
+
# https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
|
483
|
+
`
|
484
|
+
if (typeof(Element) != 'undefined' && !Element.prototype.matches) {
|
485
|
+
Element.prototype.matches = Element.prototype.msMatchesSelector ||
|
486
|
+
Element.prototype.webkitMatchesSelector;
|
487
|
+
}
|
488
|
+
|
489
|
+
if (typeof(Element) != 'undefined' && !Element.prototype.closest) {
|
490
|
+
Element.prototype.closest = function(s) {
|
491
|
+
var el = this;
|
492
|
+
|
493
|
+
do {
|
494
|
+
if (el.matches(s)) return el;
|
495
|
+
el = el.parentElement || el.parentNode;
|
496
|
+
} while (el !== null && el.nodeType === 1);
|
497
|
+
return null;
|
498
|
+
};
|
499
|
+
}
|
500
|
+
`
|
501
|
+
|
285
502
|
def reactive_record_link_to_enclosing_while_loading_container
|
286
503
|
# Call after any component mounts - attaches the containers loading id to this component
|
287
504
|
# Fyi, the while_loading container is responsible for setting its own link to itself
|