hyper-model 0.6.0 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +35 -41
- data/.rspec +2 -0
- data/.travis.yml +33 -0
- data/CHANGELOG.md +34 -0
- data/DOCS.md +735 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +298 -224
- data/{LICENSE → LICENSE.txt} +6 -6
- data/README.md +51 -2
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/codeship.database.yml +18 -0
- data/hyper-model.gemspec +62 -36
- data/lib/active_model_client_stubs.rb +16 -0
- data/lib/active_record_base.rb +331 -0
- data/{examples/chat-app/app/assets/images/.keep → lib/acts_as_string.rb} +0 -0
- data/lib/enumerable/pluck.rb +6 -0
- data/lib/hyper-model.rb +59 -8
- data/lib/hyper_model/version.rb +3 -0
- data/lib/hyper_react/input_tags.rb +47 -0
- data/lib/hyperloop/model/load.rb +1 -1
- data/lib/kernel/itself.rb +5 -0
- data/lib/object/tap.rb +7 -0
- data/lib/opal/equality_patches.rb +15 -0
- data/lib/opal/parse_patch.rb +14 -0
- data/lib/opal/set_patches.rb +8 -0
- data/lib/reactive_record/active_record/aggregations.rb +69 -0
- data/lib/reactive_record/active_record/associations.rb +118 -0
- data/lib/reactive_record/active_record/base.rb +10 -0
- data/lib/reactive_record/active_record/class_methods.rb +406 -0
- data/lib/reactive_record/active_record/error.rb +31 -0
- data/lib/reactive_record/active_record/errors.rb +374 -0
- data/lib/reactive_record/active_record/instance_methods.rb +187 -0
- data/lib/reactive_record/active_record/public_columns_hash.rb +44 -0
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +36 -0
- data/lib/reactive_record/active_record/reactive_record/base.rb +416 -0
- data/lib/reactive_record/active_record/reactive_record/collection.rb +558 -0
- data/lib/reactive_record/active_record/reactive_record/column_types.rb +75 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +236 -0
- data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +576 -0
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
- data/lib/reactive_record/active_record/reactive_record/operations.rb +107 -0
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +62 -0
- data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
- data/lib/reactive_record/active_record/reactive_record/unscoped_collection.rb +16 -0
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +343 -0
- data/lib/reactive_record/active_record_error.rb +4 -0
- data/lib/reactive_record/broadcast.rb +223 -0
- data/lib/reactive_record/engine.rb +11 -0
- data/lib/reactive_record/interval.rb +190 -0
- data/lib/reactive_record/permissions.rb +117 -0
- data/lib/reactive_record/pry.rb +13 -0
- data/lib/reactive_record/reactive_scope.rb +18 -0
- data/lib/reactive_record/scope_description.rb +121 -0
- data/lib/reactive_record/serializers.rb +7 -0
- data/lib/reactive_record/server_data_cache.rb +478 -0
- data/path_release_steps.md +9 -0
- metadata +399 -109
- data/CODE_OF_CONDUCT.md +0 -49
- data/examples/chat-app/.gitignore +0 -21
- data/examples/chat-app/Gemfile +0 -62
- data/examples/chat-app/Gemfile.lock +0 -309
- data/examples/chat-app/README.md +0 -3
- data/examples/chat-app/Rakefile +0 -6
- data/examples/chat-app/app/assets/config/manifest.js +0 -3
- data/examples/chat-app/app/assets/javascripts/application.js +0 -3
- data/examples/chat-app/app/assets/stylesheets/application.scss +0 -33
- data/examples/chat-app/app/controllers/application_controller.rb +0 -3
- data/examples/chat-app/app/controllers/home_controller.rb +0 -5
- data/examples/chat-app/app/hyperloop/components/app.rb +0 -12
- data/examples/chat-app/app/hyperloop/components/formatted_div.rb +0 -15
- data/examples/chat-app/app/hyperloop/components/input_box.rb +0 -26
- data/examples/chat-app/app/hyperloop/components/message.rb +0 -29
- data/examples/chat-app/app/hyperloop/components/messages.rb +0 -8
- data/examples/chat-app/app/hyperloop/components/nav.rb +0 -30
- data/examples/chat-app/app/hyperloop/models/application_record.rb +0 -3
- data/examples/chat-app/app/hyperloop/models/message.rb +0 -6
- data/examples/chat-app/app/hyperloop/operations/operations.rb +0 -13
- data/examples/chat-app/app/hyperloop/stores/message_store.rb +0 -17
- data/examples/chat-app/app/policies/application_policy.rb +0 -9
- data/examples/chat-app/app/views/layouts/application.html.erb +0 -12
- data/examples/chat-app/bin/bundle +0 -3
- data/examples/chat-app/bin/rails +0 -9
- data/examples/chat-app/bin/rake +0 -9
- data/examples/chat-app/bin/setup +0 -34
- data/examples/chat-app/bin/spring +0 -17
- data/examples/chat-app/bin/update +0 -29
- data/examples/chat-app/config.ru +0 -5
- data/examples/chat-app/config/application.rb +0 -12
- data/examples/chat-app/config/boot.rb +0 -3
- data/examples/chat-app/config/cable.yml +0 -9
- data/examples/chat-app/config/database.yml +0 -25
- data/examples/chat-app/config/environment.rb +0 -5
- data/examples/chat-app/config/environments/development.rb +0 -56
- data/examples/chat-app/config/environments/production.rb +0 -86
- data/examples/chat-app/config/environments/test.rb +0 -42
- data/examples/chat-app/config/initializers/application_controller_renderer.rb +0 -6
- data/examples/chat-app/config/initializers/assets.rb +0 -11
- data/examples/chat-app/config/initializers/backtrace_silencers.rb +0 -7
- data/examples/chat-app/config/initializers/cookies_serializer.rb +0 -5
- data/examples/chat-app/config/initializers/filter_parameter_logging.rb +0 -4
- data/examples/chat-app/config/initializers/hyperloop.rb +0 -6
- data/examples/chat-app/config/initializers/inflections.rb +0 -16
- data/examples/chat-app/config/initializers/mime_types.rb +0 -4
- data/examples/chat-app/config/initializers/new_framework_defaults.rb +0 -24
- data/examples/chat-app/config/initializers/session_store.rb +0 -3
- data/examples/chat-app/config/initializers/wrap_parameters.rb +0 -14
- data/examples/chat-app/config/locales/en.yml +0 -23
- data/examples/chat-app/config/puma.rb +0 -47
- data/examples/chat-app/config/routes.rb +0 -5
- data/examples/chat-app/config/secrets.yml +0 -22
- data/examples/chat-app/config/spring.rb +0 -6
- data/examples/chat-app/db/migrate/20170319194429_create_message.rb +0 -9
- data/examples/chat-app/db/schema.rb +0 -48
- data/examples/chat-app/db/seeds.rb +0 -7
- data/examples/chat-app/lib/assets/.keep +0 -0
- data/examples/chat-app/lib/tasks/.keep +0 -0
- data/examples/chat-app/log/.keep +0 -0
- data/examples/chat-app/public/404.html +0 -67
- data/examples/chat-app/public/422.html +0 -67
- data/examples/chat-app/public/500.html +0 -66
- data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
- data/examples/chat-app/public/apple-touch-icon.png +0 -0
- data/examples/chat-app/public/favicon.ico +0 -0
- data/examples/chat-app/public/robots.txt +0 -5
- data/examples/chat-app/test/controllers/.keep +0 -0
- data/examples/chat-app/test/fixtures/.keep +0 -0
- data/examples/chat-app/test/fixtures/files/.keep +0 -0
- data/examples/chat-app/test/helpers/.keep +0 -0
- data/examples/chat-app/test/integration/.keep +0 -0
- data/examples/chat-app/test/mailers/.keep +0 -0
- data/examples/chat-app/test/models/.keep +0 -0
- data/examples/chat-app/test/test_helper.rb +0 -10
- data/examples/chat-app/tmp/.keep +0 -0
- data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
- data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
- data/lib/hyperloop/model/version.rb +0 -5
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module ReactiveRecord
|
|
2
|
+
module LookupTables
|
|
3
|
+
def initialize_lookup_tables
|
|
4
|
+
@records = Hash.new { |hash, key| hash[key] = [] }
|
|
5
|
+
@records_by_id = `{}`
|
|
6
|
+
@records_by_vector = `{}`
|
|
7
|
+
@records_by_object_id = `{}`
|
|
8
|
+
@class_scopes = Hash.new { |hash, key| hash[key] = {} }
|
|
9
|
+
@waiting_for_save = Hash.new { |hash, key| hash[key] = [] }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def class_scopes(model)
|
|
13
|
+
@class_scopes[model.base_class]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def waiting_for_save(model)
|
|
17
|
+
@waiting_for_save[model]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait_for_save(model, &block)
|
|
21
|
+
@waiting_for_save[model] << block
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear_waiting_for_save(model)
|
|
25
|
+
@waiting_for_save[model] = []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def lookup_by_object_id(object_id)
|
|
29
|
+
`#{@records_by_object_id}[#{object_id}]`.ar_instance
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set_object_id_lookup(record)
|
|
33
|
+
`#{@records_by_object_id}[#{record.object_id}] = #{record}`
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lookup_by_id(*args) # model and id
|
|
37
|
+
`#{@records_by_id}[#{args}]` || nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def set_id_lookup(record)
|
|
41
|
+
`#{@records_by_id}[#{[record.model, record.id]}] = #{record}`
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def lookup_by_vector(vector)
|
|
45
|
+
`#{@records_by_vector}[#{vector}]` || nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def set_vector_lookup(record, vector)
|
|
49
|
+
record.vector = vector
|
|
50
|
+
`delete #{@records_by_vector}[#{record.vector}]`
|
|
51
|
+
`#{@records_by_vector}[#{vector}] = record`
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module ReactiveRecord
|
|
2
|
+
# redefine if you want to process errors (i.e. logging, rollbar, etc)
|
|
3
|
+
def self.on_fetch_error(e, params); end
|
|
4
|
+
|
|
5
|
+
# associations: {parent_id: record.object_id, attribute: attribute, child_id: assoc_record.object_id}
|
|
6
|
+
# models: {id: record.object_id, model: record.model.model_name.to_s, attributes: changed_attributes}
|
|
7
|
+
|
|
8
|
+
module Operations
|
|
9
|
+
# to make debug easier we convert all the object_id strings to be hex representation
|
|
10
|
+
class Base < Hyperloop::ControllerOp
|
|
11
|
+
param :acting_user, nils: true
|
|
12
|
+
|
|
13
|
+
FORMAT = '0x%x'
|
|
14
|
+
|
|
15
|
+
def self.serialize_params(hash)
|
|
16
|
+
hash['associations'].each do |assoc|
|
|
17
|
+
assoc['parent_id'] = FORMAT % assoc['parent_id']
|
|
18
|
+
assoc['child_id'] = FORMAT % assoc['child_id']
|
|
19
|
+
end if hash['associations']
|
|
20
|
+
hash['models'].each do |assoc|
|
|
21
|
+
assoc['id'] = FORMAT % assoc[:id]
|
|
22
|
+
end if hash['models']
|
|
23
|
+
hash
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.deserialize_params(hash)
|
|
27
|
+
hash['associations'].each do |assoc|
|
|
28
|
+
assoc['parent_id'] = assoc['parent_id'].to_i(16)
|
|
29
|
+
assoc['child_id'] = assoc['child_id'].to_i(16)
|
|
30
|
+
end if hash['associations']
|
|
31
|
+
hash['models'].each do |assoc|
|
|
32
|
+
assoc['id'] = assoc['id'].to_i(16)
|
|
33
|
+
end if hash['models']
|
|
34
|
+
hash
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.serialize_response(response)
|
|
38
|
+
response[:saved_models].each do |saved_model|
|
|
39
|
+
saved_model[0] = FORMAT % saved_model[0]
|
|
40
|
+
end if response.is_a?(Hash) && response[:saved_models]
|
|
41
|
+
response
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.deserialize_response(response)
|
|
45
|
+
response[:saved_models].each do |saved_model|
|
|
46
|
+
saved_model[0] = saved_model[0].to_i(16)
|
|
47
|
+
end if response.is_a?(Hash) && response[:saved_models]
|
|
48
|
+
response
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
# fetch queued up records from the server
|
|
52
|
+
# subclass of ControllerOp so we can pass the controller
|
|
53
|
+
# along to on_error
|
|
54
|
+
class Fetch < Base
|
|
55
|
+
param :acting_user, nils: true
|
|
56
|
+
param models: []
|
|
57
|
+
param associations: []
|
|
58
|
+
param :pending_fetches
|
|
59
|
+
step do
|
|
60
|
+
ReactiveRecord::ServerDataCache[
|
|
61
|
+
params.models.map(&:with_indifferent_access),
|
|
62
|
+
params.associations.map(&:with_indifferent_access),
|
|
63
|
+
params.pending_fetches,
|
|
64
|
+
params.acting_user
|
|
65
|
+
]
|
|
66
|
+
end
|
|
67
|
+
failed do |e|
|
|
68
|
+
# AccessViolations are already sent to on_error
|
|
69
|
+
Hyperloop.on_error(e, :fetch_error, params.to_h) unless e.is_a? Hyperloop::AccessViolation
|
|
70
|
+
raise e
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Save < Base
|
|
75
|
+
param :acting_user, nils: true
|
|
76
|
+
param models: []
|
|
77
|
+
param associations: []
|
|
78
|
+
param :save, type: :boolean
|
|
79
|
+
param :validate, type: :boolean
|
|
80
|
+
|
|
81
|
+
step do
|
|
82
|
+
ReactiveRecord::Base.save_records(
|
|
83
|
+
params.models.map(&:with_indifferent_access),
|
|
84
|
+
params.associations.map(&:with_indifferent_access),
|
|
85
|
+
params.acting_user,
|
|
86
|
+
params.validate,
|
|
87
|
+
params.save
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class Destroy < Base
|
|
93
|
+
param :acting_user, nils: true
|
|
94
|
+
param :model
|
|
95
|
+
param :id
|
|
96
|
+
param :vector
|
|
97
|
+
step do
|
|
98
|
+
ReactiveRecord::Base.destroy_record(
|
|
99
|
+
params.model,
|
|
100
|
+
params.id,
|
|
101
|
+
params.vector,
|
|
102
|
+
params.acting_user
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module ReactiveRecord
|
|
2
|
+
# The base collection class works with relationships
|
|
3
|
+
# method overrides for scoped collections
|
|
4
|
+
module ScopedCollection
|
|
5
|
+
[:filter?, :collector?, :joins_with?, :related_records_for].each do |method|
|
|
6
|
+
define_method(method) { |*args| @scope_description.send method, *args }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def set_pre_sync_related_records(related_records, _record = nil)
|
|
10
|
+
@pre_sync_related_records = nil
|
|
11
|
+
ReactiveRecord::Base.catch_db_requests do
|
|
12
|
+
@pre_sync_related_records = filter_records(related_records)
|
|
13
|
+
live_scopes.each do |scope|
|
|
14
|
+
scope.set_pre_sync_related_records(@pre_sync_related_records)
|
|
15
|
+
end
|
|
16
|
+
end if filter?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sync_scopes(related_records, record, filtering = true)
|
|
20
|
+
filtering =
|
|
21
|
+
@pre_sync_related_records && filtering &&
|
|
22
|
+
ReactiveRecord::Base.catch_db_requests do
|
|
23
|
+
related_records = update_collection(related_records)
|
|
24
|
+
end
|
|
25
|
+
reload_from_db if !filtering && joins_with?(record)
|
|
26
|
+
live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) }
|
|
27
|
+
ensure
|
|
28
|
+
@pre_sync_related_records = nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update_collection(related_records)
|
|
32
|
+
if collector?
|
|
33
|
+
update_collector_scope(related_records)
|
|
34
|
+
else
|
|
35
|
+
related_records = filter_records(related_records)
|
|
36
|
+
update_filter_scope(@pre_sync_related_records, related_records)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update_collector_scope(related_records)
|
|
41
|
+
current = Set.new([*@collection])
|
|
42
|
+
(related_records - @pre_sync_related_records).each { |r| current << r }
|
|
43
|
+
(@pre_sync_related_records - related_records).each { |r| current.delete(r) }
|
|
44
|
+
replace(filter_records(current))
|
|
45
|
+
Set.new([*@collection])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def update_filter_scope(before, after)
|
|
49
|
+
if (collection || !@count.nil?) && before != after
|
|
50
|
+
if collection
|
|
51
|
+
(after - before).each { |r| push r }
|
|
52
|
+
(before - after).each { |r| delete r }
|
|
53
|
+
else
|
|
54
|
+
@count += (after - before).count
|
|
55
|
+
@count -= (before - after).count
|
|
56
|
+
notify_of_change self # TODO: remove self .... and retest
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
after
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
module ReactiveRecord
|
|
2
|
+
module Setters
|
|
3
|
+
def set_attr_value(attr, raw_value)
|
|
4
|
+
set_common(attr, raw_value) { |value| update_simple_attribute(attr, value) }
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def set_ar_aggregate(aggr, raw_value)
|
|
8
|
+
set_common(aggr.attribute, raw_value) do |value, attr|
|
|
9
|
+
@attributes[attr] ||= aggr.klass.new if new?
|
|
10
|
+
abr = @attributes[attr].backing_record
|
|
11
|
+
abr.virgin = false
|
|
12
|
+
map = value.attributes if value
|
|
13
|
+
aggr.mapped_attributes.each do |mapped_attr|
|
|
14
|
+
abr.update_aggregate_attribute mapped_attr, map && map[mapped_attr]
|
|
15
|
+
end
|
|
16
|
+
return @attributes[attr]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def set_non_ar_aggregate(aggregation, raw_value)
|
|
21
|
+
set_common(aggregation.attribute, raw_value) do |value, attr|
|
|
22
|
+
if data_loading?
|
|
23
|
+
@synced_attributes[attr] = aggregation.deserialize(aggregation.serialize(value))
|
|
24
|
+
else
|
|
25
|
+
changed = !@synced_attributes.key?(attr) || @synced_attributes[attr] != value
|
|
26
|
+
end
|
|
27
|
+
set_attribute_change_status_and_notify attr, changed, value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set_has_many(assoc, raw_value)
|
|
32
|
+
set_common(assoc.attribute, raw_value) do |value, attr|
|
|
33
|
+
# create a new collection to hold value, shove it in, and return the new collection
|
|
34
|
+
# the replace method will take care of updating the inverse belongs_to links as
|
|
35
|
+
# the collection is overwritten
|
|
36
|
+
collection = Collection.new(assoc.klass, @ar_instance, assoc)
|
|
37
|
+
collection.replace(value || [])
|
|
38
|
+
@synced_attributes[attr] = value if data_loading?
|
|
39
|
+
set_attribute_change_status_and_notify attr, value != @synced_attributes[attr], collection
|
|
40
|
+
return collection
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def set_belongs_to(assoc, raw_value)
|
|
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
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sync_has_many(attr)
|
|
58
|
+
set_change_status_and_notify_only attr, @attributes[attr] != @synced_attributes[attr]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def update_simple_attribute(attr, value)
|
|
62
|
+
if data_loading?
|
|
63
|
+
@synced_attributes[attr] = value
|
|
64
|
+
else
|
|
65
|
+
changed = !@synced_attributes.key?(attr) || @synced_attributes[attr] != value
|
|
66
|
+
end
|
|
67
|
+
set_attribute_change_status_and_notify attr, changed, value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
alias update_belongs_to update_simple_attribute
|
|
71
|
+
alias update_aggregate_attribute update_simple_attribute
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def set_common(attr, value)
|
|
76
|
+
value = convert(attr, value)
|
|
77
|
+
@virgin = false unless data_loading?
|
|
78
|
+
if !@destroyed && (
|
|
79
|
+
!@attributes.key?(attr) ||
|
|
80
|
+
@attributes[attr].is_a?(Base::DummyValue) ||
|
|
81
|
+
@attributes[attr] != value)
|
|
82
|
+
yield value, attr
|
|
83
|
+
end
|
|
84
|
+
value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def set_attribute_change_status_and_notify(attr, changed, new_value)
|
|
88
|
+
if @virgin
|
|
89
|
+
@attributes[attr] = new_value
|
|
90
|
+
else
|
|
91
|
+
change_status_and_notify_helper(attr, changed) do |had_key, current_value|
|
|
92
|
+
@attributes[attr] = new_value
|
|
93
|
+
if !data_loading? ||
|
|
94
|
+
(on_opal_client? && had_key && current_value.loaded? && current_value != new_value)
|
|
95
|
+
React::State.set_state(self, attr, new_value, data_loading?)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def set_change_status_and_notify_only(attr, changed)
|
|
102
|
+
return if @virgin
|
|
103
|
+
change_status_and_notify_helper(attr, changed) do
|
|
104
|
+
React::State.set_state(self, attr, nil) unless data_loading?
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def change_status_and_notify_helper(attr, changed)
|
|
109
|
+
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
|
+
if !changed || data_loading?
|
|
113
|
+
changed_attributes.delete(attr)
|
|
114
|
+
elsif !changed_attributes.include?(attr)
|
|
115
|
+
changed_attributes << attr
|
|
116
|
+
end
|
|
117
|
+
yield @attributes.key?(attr), @attributes[attr]
|
|
118
|
+
return unless empty_before != changed_attributes.empty?
|
|
119
|
+
if on_opal_client? && !data_loading?
|
|
120
|
+
React::State.set_state(self, '!CHANGED!', !changed_attributes.empty?, true)
|
|
121
|
+
end
|
|
122
|
+
return unless aggregate_owner
|
|
123
|
+
aggregate_owner.set_change_status_and_notify_only(
|
|
124
|
+
attr, !@attributes[attr].backing_record.changed_attributes.empty?
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
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
|
+
React::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?
|
|
149
|
+
else
|
|
150
|
+
value.backing_record.push_onto_collection(@model, association.inverse, @ar_instance)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def push_onto_collection(model, association, ar_instance)
|
|
155
|
+
@attributes[association.attribute] ||= Collection.new(model, @ar_instance, association)
|
|
156
|
+
@attributes[association.attribute] << ar_instance
|
|
157
|
+
end
|
|
158
|
+
|
|
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
|
|
163
|
+
|
|
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
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module ReactiveRecord
|
|
2
|
+
# The base collection class works with relationships
|
|
3
|
+
# method overrides for the unscoped collection
|
|
4
|
+
module UnscopedCollection
|
|
5
|
+
def set_pre_sync_related_records(related_records, _record = nil)
|
|
6
|
+
@pre_sync_related_records = related_records
|
|
7
|
+
live_scopes.each { |scope| scope.set_pre_sync_related_records(@pre_sync_related_records) }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sync_scopes(related_records, record, filtering = true)
|
|
11
|
+
live_scopes.each { |scope| scope.sync_scopes(related_records, record, filtering) }
|
|
12
|
+
ensure
|
|
13
|
+
@pre_sync_related_records = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
module HyperMesh
|
|
2
|
+
def self.load(&block)
|
|
3
|
+
ReactiveRecord.load(&block)
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ReactiveRecord
|
|
8
|
+
|
|
9
|
+
# will repeatedly execute the block until it is loaded
|
|
10
|
+
# immediately returns a promise that will resolve once the block is loaded
|
|
11
|
+
|
|
12
|
+
def self.load(&block)
|
|
13
|
+
promise = Promise.new
|
|
14
|
+
@load_stack ||= []
|
|
15
|
+
@load_stack << @loads_pending
|
|
16
|
+
@loads_pending = nil
|
|
17
|
+
result = block.call.itself
|
|
18
|
+
if @loads_pending
|
|
19
|
+
@blocks_to_load ||= []
|
|
20
|
+
@blocks_to_load << [Base.current_fetch_id, promise, block]
|
|
21
|
+
else
|
|
22
|
+
promise.resolve result
|
|
23
|
+
end
|
|
24
|
+
@loads_pending = @load_stack.pop
|
|
25
|
+
promise
|
|
26
|
+
rescue Exception => e
|
|
27
|
+
React::IsomorphicHelpers.log "ReactiveRecord.load exception raised during initial load: #{e}", :error
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.loads_pending!
|
|
31
|
+
@loads_pending = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.check_loads_pending
|
|
35
|
+
if @loads_pending
|
|
36
|
+
if Base.pending_fetches.count > 0
|
|
37
|
+
true
|
|
38
|
+
else # this happens when for example loading foo.x results in somebody looking at foo.y while foo.y is still being loaded
|
|
39
|
+
ReactiveRecord::WhileLoading.loaded_at Base.current_fetch_id
|
|
40
|
+
ReactiveRecord::WhileLoading.quiet!
|
|
41
|
+
false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.run_blocks_to_load(fetch_id, failure = nil)
|
|
47
|
+
if @blocks_to_load
|
|
48
|
+
blocks_to_load_now = @blocks_to_load.select { |data| data.first == fetch_id }
|
|
49
|
+
@blocks_to_load = @blocks_to_load.reject { |data| data.first == fetch_id }
|
|
50
|
+
@load_stack ||= []
|
|
51
|
+
blocks_to_load_now.each do |data|
|
|
52
|
+
id, promise, block = data
|
|
53
|
+
@load_stack << @loads_pending
|
|
54
|
+
@loads_pending = nil
|
|
55
|
+
result = block.call(failure)
|
|
56
|
+
if check_loads_pending && !failure
|
|
57
|
+
@blocks_to_load << [Base.current_fetch_id, promise, block]
|
|
58
|
+
else
|
|
59
|
+
promise.resolve result
|
|
60
|
+
end
|
|
61
|
+
@loads_pending = @load_stack.pop
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
rescue Exception => e
|
|
65
|
+
React::IsomorphicHelpers.log "ReactiveRecord.load exception raised during retry: #{e}", :error
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Adds while_loading feature to React
|
|
70
|
+
# to use attach a .while_loading handler to any element for example
|
|
71
|
+
# div { "displayed if everything is loaded" }.while_loading { "displayed while I'm loading" }
|
|
72
|
+
# the contents of the div will be switched (using javascript classes) depending on the state of contents of the first block
|
|
73
|
+
|
|
74
|
+
# To notify React that something is loading use React::WhileLoading.loading!
|
|
75
|
+
# once everything is loaded then do React::WhileLoading.loaded_at message (typically a time stamp just for debug purposes)
|
|
76
|
+
|
|
77
|
+
class WhileLoading
|
|
78
|
+
|
|
79
|
+
include React::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
|
+
React::State.has_observers?(self, :loaded_at)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
prerender_footer do
|
|
99
|
+
"<style>\n#{@css_to_preload}\n</style>".tap { @css_to_preload = ""}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if RUBY_ENGINE == 'opal'
|
|
103
|
+
|
|
104
|
+
# +: I DONT THINK WE USE opal-jquery in this module anymore - require 'opal-jquery' if opal_client?
|
|
105
|
+
# -: You think wrong. add_style_sheet uses the jQuery $, after_mount too, others too
|
|
106
|
+
# -: I removed those references. Now you think right.
|
|
107
|
+
|
|
108
|
+
include Hyperloop::Component::Mixin
|
|
109
|
+
|
|
110
|
+
param :loading
|
|
111
|
+
param :loaded_children
|
|
112
|
+
param :loading_children
|
|
113
|
+
param :element_type
|
|
114
|
+
param :element_props
|
|
115
|
+
param :display, default: ''
|
|
116
|
+
|
|
117
|
+
class << self
|
|
118
|
+
|
|
119
|
+
def loading?
|
|
120
|
+
@is_loading
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def loading!
|
|
124
|
+
React::RenderingContext.waiting_on_resources = true
|
|
125
|
+
React::State.get_state(self, :loaded_at)
|
|
126
|
+
# this was moved to where the fetch is actually pushed on to the fetch array in isomorphic base
|
|
127
|
+
# React::State.set_state(self, :quiet, false)
|
|
128
|
+
@is_loading = true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def loaded_at(loaded_at)
|
|
132
|
+
React::State.set_state(self, :loaded_at, loaded_at)
|
|
133
|
+
@is_loading = false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def quiet?
|
|
137
|
+
React::State.get_state(self, :quiet)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def page_loaded?
|
|
141
|
+
React::State.get_state(self, :page_loaded)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def quiet!
|
|
145
|
+
React::State.set_state(self, :quiet, true)
|
|
146
|
+
after(1) { React::State.set_state(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; }\n" +
|
|
157
|
+
".reactive_record_is_loaded > .reactive_record_show_while_loading { display: none; }";
|
|
158
|
+
document.head.append(style_el);
|
|
159
|
+
}
|
|
160
|
+
@style_sheet_added = true
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
before_mount do
|
|
167
|
+
@uniq_id = WhileLoading.get_next_while_loading_counter
|
|
168
|
+
WhileLoading.preload_css(
|
|
169
|
+
".reactive_record_while_loading_container_#{@uniq_id} > :nth-child(1n+#{params.loaded_children.count+1}) {\n"+
|
|
170
|
+
" display: none;\n"+
|
|
171
|
+
"}\n"
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
after_mount do
|
|
176
|
+
@waiting_on_resources = params.loading
|
|
177
|
+
WhileLoading.add_style_sheet
|
|
178
|
+
node = dom_node
|
|
179
|
+
%x{
|
|
180
|
+
var nodes = node.querySelectorAll(':nth-child(-1n+'+#{params.loaded_children.count}+')');
|
|
181
|
+
nodes.forEach(
|
|
182
|
+
function(current_node, current_index, list_obj) {
|
|
183
|
+
if (current_node.className.indexOf('reactive_record_show_when_loaded') === -1) {
|
|
184
|
+
current_node.className = current_node.className + ' reactive_record_show_when_loaded';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
nodes = node.querySelectorAll(':nth-child(1n+'+#{params.loaded_children.count+1}+')');
|
|
189
|
+
nodes.forEach(
|
|
190
|
+
function(current_node, current_index, list_obj) {
|
|
191
|
+
if (current_node.className.indexOf('reactive_record_show_while_loading') === -1) {
|
|
192
|
+
current_node.className = current_node.className + ' reactive_record_show_while_loading';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
after_update do
|
|
200
|
+
@waiting_on_resources = params.loading
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def render
|
|
204
|
+
props = params.element_props.dup
|
|
205
|
+
classes = [props[:class], props[:className], "reactive_record_while_loading_container_#{@uniq_id}"].compact.join(" ")
|
|
206
|
+
props.merge!({
|
|
207
|
+
"data-reactive_record_while_loading_container_id" => @uniq_id,
|
|
208
|
+
"data-reactive_record_enclosing_while_loading_container_id" => @uniq_id,
|
|
209
|
+
class: classes
|
|
210
|
+
})
|
|
211
|
+
React.create_element(params.element_type[0], props) do
|
|
212
|
+
params.loaded_children + params.loading_children
|
|
213
|
+
end.tap { |e| e.waiting_on_resources = params.loading }
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
module React
|
|
223
|
+
|
|
224
|
+
class Element
|
|
225
|
+
|
|
226
|
+
def while_loading(display = "", &loading_display_block)
|
|
227
|
+
loaded_children = []
|
|
228
|
+
loaded_children = block.call.dup if block
|
|
229
|
+
if display.respond_to? :as_node
|
|
230
|
+
display = display.as_node
|
|
231
|
+
loading_display_block = lambda { display.render }
|
|
232
|
+
elsif !loading_display_block
|
|
233
|
+
loading_display_block = lambda { display }
|
|
234
|
+
end
|
|
235
|
+
loading_children = RenderingContext.build do |buffer|
|
|
236
|
+
result = loading_display_block.call
|
|
237
|
+
result = result.to_s if result.try :acts_as_string?
|
|
238
|
+
result.span.tap { |e| e.waiting_on_resources = RenderingContext.waiting_on_resources } if result.is_a? String
|
|
239
|
+
buffer.dup
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
new_element = React.create_element(
|
|
243
|
+
ReactiveRecord::WhileLoading,
|
|
244
|
+
loading: waiting_on_resources,
|
|
245
|
+
loading_children: loading_children,
|
|
246
|
+
loaded_children: loaded_children,
|
|
247
|
+
element_type: [type],
|
|
248
|
+
element_props: properties)
|
|
249
|
+
|
|
250
|
+
RenderingContext.replace(self, new_element)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def hide_while_loading
|
|
254
|
+
while_loading
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
if RUBY_ENGINE == 'opal'
|
|
261
|
+
module Hyperloop
|
|
262
|
+
class Component
|
|
263
|
+
module Mixin
|
|
264
|
+
|
|
265
|
+
alias_method :original_component_did_mount, :component_did_mount
|
|
266
|
+
|
|
267
|
+
def component_did_mount(*args)
|
|
268
|
+
original_component_did_mount(*args)
|
|
269
|
+
reactive_record_link_to_enclosing_while_loading_container
|
|
270
|
+
reactive_record_link_set_while_loading_container_class
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
alias_method :original_component_did_update, :component_did_update
|
|
274
|
+
|
|
275
|
+
def component_did_update(*args)
|
|
276
|
+
original_component_did_update(*args)
|
|
277
|
+
reactive_record_link_set_while_loading_container_class
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def reactive_record_link_to_enclosing_while_loading_container
|
|
281
|
+
# Call after any component mounts - attaches the containers loading id to this component
|
|
282
|
+
# Fyi, the while_loading container is responsible for setting its own link to itself
|
|
283
|
+
node = dom_node
|
|
284
|
+
%x{
|
|
285
|
+
if (typeof node === "undefined" || node === null) return;
|
|
286
|
+
var node_wl_attr = node.getAttribute('data-reactive_record_enclosing_while_loading_container_id');
|
|
287
|
+
if (node_wl_attr === null || node_wl_attr === "") {
|
|
288
|
+
var while_loading_container = node.closest('[data-reactive_record_while_loading_container_id]');
|
|
289
|
+
if (while_loading_container !== null) {
|
|
290
|
+
var container_id = while_loading_container.getAttribute('data-reactive_record_while_loading_container_id');
|
|
291
|
+
node.setAttribute('data-reactive_record_enclosing_while_loading_container_id', container_id);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def reactive_record_link_set_while_loading_container_class
|
|
298
|
+
node = dom_node
|
|
299
|
+
loading = (waiting_on_resources ? `true` : `false`)
|
|
300
|
+
%x{
|
|
301
|
+
if (typeof node === "undefined" || node === null) return;
|
|
302
|
+
var while_loading_container_id = node.getAttribute('data-reactive_record_while_loading_container_id');
|
|
303
|
+
if (#{!self.is_a?(ReactiveRecord::WhileLoading)} && while_loading_container_id !== null && while_loading_container_id !== "") {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
var enc_while_loading_container_id = node.getAttribute('data-reactive_record_enclosing_while_loading_container_id');
|
|
307
|
+
if (enc_while_loading_container_id !== null && enc_while_loading_container_id !== "") {
|
|
308
|
+
var while_loading_container = document.body.querySelector('[data-reactive_record_while_loading_container_id="'+enc_while_loading_container_id+'"]');
|
|
309
|
+
if (loading) {
|
|
310
|
+
node.className = node.className.replace(/reactive_record_is_loaded/g, '').replace(/ /g, ' ');
|
|
311
|
+
if (node.className.indexOf('reactive_record_is_loading') === -1) {
|
|
312
|
+
node.className = node.className + ' reactive_record_is_loading';
|
|
313
|
+
}
|
|
314
|
+
if (while_loading_container !== null) {
|
|
315
|
+
while_loading_container.className = while_loading_container.className.replace(/reactive_record_is_loaded/g, '').replace(/ /g, ' ');
|
|
316
|
+
if (while_loading_container.className.indexOf('reactive_record_is_loading') === -1) {
|
|
317
|
+
while_loading_container.className = while_loading_container.className + ' reactive_record_is_loading';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else if (node.className.indexOf('reactive_record_is_loaded') === -1) {
|
|
321
|
+
if (while_loading_container_id === null || while_loading_container_id === "") {
|
|
322
|
+
node.className = node.className.replace(/reactive_record_is_loading/g, '').replace(/ /g, ' ');
|
|
323
|
+
if (node.className.indexOf('reactive_record_is_loaded') === -1) {
|
|
324
|
+
node.className = node.className + ' reactive_record_is_loaded';
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (while_loading_container.className.indexOf('reactive_record_is_loaded') === -1) {
|
|
328
|
+
var loading_children = while_loading_container.querySelectorAll('[data-reactive_record_enclosing_while_loading_container_id="'+enc_while_loading_container_id+'"].reactive_record_is_loading');
|
|
329
|
+
if (loading_children.length === 0) {
|
|
330
|
+
while_loading_container.className = while_loading_container.className.replace(/reactive_record_is_loading/g, '').replace(/ /g, ' ');
|
|
331
|
+
if (while_loading_container.className.indexOf('reactive_record_is_loaded') === -1) {
|
|
332
|
+
while_loading_container.className = while_loading_container.className + ' reactive_record_is_loaded';
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|