hyper-model 1.0.alpha1.1 → 1.0.alpha1.6

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.rspec +0 -1
  4. data/Gemfile +6 -6
  5. data/Rakefile +27 -3
  6. data/hyper-model.gemspec +10 -19
  7. data/lib/active_record_base.rb +101 -33
  8. data/lib/hyper-model.rb +4 -2
  9. data/lib/hyper_model/version.rb +1 -1
  10. data/lib/hyper_react/input_tags.rb +2 -1
  11. data/lib/reactive_record/active_record/associations.rb +130 -34
  12. data/lib/reactive_record/active_record/base.rb +17 -0
  13. data/lib/reactive_record/active_record/class_methods.rb +124 -52
  14. data/lib/reactive_record/active_record/error.rb +2 -0
  15. data/lib/reactive_record/active_record/errors.rb +10 -6
  16. data/lib/reactive_record/active_record/instance_methods.rb +74 -6
  17. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
  18. data/lib/reactive_record/active_record/reactive_record/base.rb +56 -30
  19. data/lib/reactive_record/active_record/reactive_record/collection.rb +219 -70
  20. data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
  21. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
  22. data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
  23. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +73 -46
  24. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
  25. data/lib/reactive_record/active_record/reactive_record/operations.rb +10 -3
  26. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -0
  27. data/lib/reactive_record/active_record/reactive_record/setters.rb +108 -71
  28. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +258 -41
  29. data/lib/reactive_record/broadcast.rb +62 -25
  30. data/lib/reactive_record/interval.rb +3 -3
  31. data/lib/reactive_record/permissions.rb +14 -2
  32. data/lib/reactive_record/scope_description.rb +3 -2
  33. data/lib/reactive_record/server_data_cache.rb +99 -49
  34. data/polymorph-notes.md +143 -0
  35. data/spec_fails.txt +3 -0
  36. metadata +49 -162
  37. 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(*args) # model and id
37
- `#{@records_by_id}[#{args}]` || nil
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
- # AccessViolations are already sent to on_error
69
- Hyperstack.on_error(e, :fetch_error, params.to_h) unless e.is_a? Hyperstack::AccessViolation
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
- if assoc.inverse.collection?
47
- update_has_many_through_associations assoc, value
48
- update_inverse_collections assoc, value
49
- else
50
- update_inverse_attribute assoc, value
51
- end
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::Store::State.set_state(self, attr, new_value, data_loading?)
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::Store::State.set_state(self, attr, nil) unless data_loading?
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::Store::State.set_state(self, '!CHANGED!', !changed_attributes.empty?, true)
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
- def update_inverse_attribute(association, value)
129
- # when updating the inverse attribute of a belongs_to that is itself a belongs_to
130
- # (i.e. 1-1 relationship) we clear the existing inverse value and then
131
- # write the current record to the new value
132
- current_value = @attributes[association.attribute]
133
- inverse_attr = association.inverse.attribute
134
- current_value.attributes[inverse_attr] = nil unless current_value.nil?
135
- return if value.nil?
136
- value.attributes[inverse_attr] = @ar_instance
137
- return if data_loading?
138
- Hyperstack::Internal::Store::State.set_state(value.backing_record, inverse_attr, @ar_instance)
139
- end
140
-
141
- def update_inverse_collections(association, value)
142
- # when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
143
- # we need to first remove the current associated value (if non-nil), then add the new
144
- # value to the collection. If the inverse collection is not yet initialized we do it here.
145
- current_value = @attributes[association.attribute]
146
- inverse_attr = association.inverse.attribute
147
- if value.nil?
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
- value.backing_record.push_onto_collection(@model, association.inverse, @ar_instance)
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] << ar_instance
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 update_has_many_through_associations(association, value)
160
- association.through_associations.each { |ta| update_through_association(ta, value) }
161
- association.source_associations.each { |sa| update_source_association(sa, value) }
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::Store::State.observed?(self, :loaded_at)
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::Store::State.get_state(self, :loaded_at)
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::Store::State.set_state(self, :quiet, false)
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::Store::State.set_state(self, :loaded_at, loaded_at)
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::Store::State.get_state(self, :quiet)
327
+ Hyperstack::Internal::State::Variable.get(self, :quiet)
142
328
  end
143
329
 
144
330
  def page_loaded?
145
- Hyperstack::Internal::Store::State.get_state(self, :page_loaded)
331
+ Hyperstack::Internal::State::Variable.get(self, :page_loaded)
146
332
  end
147
333
 
148
334
  def quiet!
149
- Hyperstack::Internal::Store::State.set_state(self, :quiet, true)
150
- after(1) { Hyperstack::Internal::Store::State.set_state(self, :page_loaded, true) } unless on_opal_server? or @page_loaded
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
- before_mount do
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
- var nodes = node.querySelectorAll(':nth-child(-1n+'+#{@LoadedChildren.count}+')');
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
- after_update do
204
- @waiting_on_resources = @Loading
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
- def render
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 = [props[:class], props[:className], "reactive_record_while_loading_container_#{@uniq_id}"].compact.join(" ")
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
- @LoadedChildren + @LoadingChildren
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
- result = loading_display_block.call
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