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.
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