fie 0.1.9

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.
@@ -0,0 +1,167 @@
1
+ module Fie
2
+ module Track
3
+ def track_changes_in_objects(object)
4
+ if object.is_a?(Array)
5
+ track_changes_in_array(object)
6
+ elsif object.is_a?(Hash)
7
+ track_changes_in_hash(object)
8
+ elsif !object.duplicable? || object.is_a?(String) || object.is_a?(Time)
9
+ nil
10
+ else
11
+ track_changes_in_object(object)
12
+ end
13
+ end
14
+
15
+ def untrack_changes_in_objects(object)
16
+ if object.is_a? Hash
17
+ untrack_changes_in_hash(object)
18
+ elsif object.is_a? Array
19
+ untrack_changes_in_array(object)
20
+ elsif !object.duplicable? || object.is_a?(String) || object.is_a?(Time)
21
+ nil
22
+ else
23
+ untrack_changes_in_object(object)
24
+ end
25
+ end
26
+
27
+ private
28
+ def track_changes_in_array(object)
29
+ state = self
30
+
31
+ unless object.frozen?
32
+ object.class_eval do
33
+ alias_method('previous_[]=', '[]=')
34
+ alias_method('previous_<<', '<<')
35
+ alias_method('previous_push', 'push')
36
+
37
+ define_method('[]=') do |key, value|
38
+ send('previous_[]=', key, value)
39
+ state.permeate
40
+ end
41
+
42
+ define_method('<<') do |value|
43
+ send('previous_<<', value)
44
+ state.permeate
45
+ end
46
+
47
+
48
+ define_method('push') do |value|
49
+ send('previous_push', value)
50
+ state.permeate
51
+ end
52
+ end
53
+ end
54
+
55
+ object.each do |value|
56
+ track_changes_in_objects(value)
57
+ end
58
+ end
59
+
60
+ def track_changes_in_hash(object)
61
+ state = self
62
+
63
+ unless object.frozen?
64
+ object.class_eval do
65
+ alias_method('previous_[]=', '[]=')
66
+ define_method('[]=') do |key, value|
67
+ send('previous_[]=', key, value)
68
+ state.permeate
69
+ end
70
+ end
71
+ end
72
+
73
+ object.each do |value|
74
+ track_changes_in_objects(value)
75
+ end
76
+ end
77
+
78
+ def track_changes_in_object(object)
79
+ state = self
80
+
81
+ object.methods.each do |attribute_name, attribute_value|
82
+ is_setter = attribute_name.to_s.ends_with?('=') && attribute_name.to_s.match(/[A-Za-z]/)
83
+
84
+ if is_setter
85
+ unless object.frozen?
86
+ object.class_eval do
87
+ alias_method("previous_#{attribute_name}", attribute_name)
88
+ define_method(attribute_name) do |setter_value|
89
+ send("previous_#{attribute_name}", setter_value)
90
+ state.permeate
91
+ end
92
+ end
93
+ end
94
+
95
+ getter_name = attribute_name.to_s.chomp('=').to_sym
96
+ object_has_getter = object.methods.include?(getter_name)
97
+ if object_has_getter
98
+ track_changes_in_objects object.send(getter_name)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def untrack_changes_in_hash(hash)
105
+ unless hash.frozen?
106
+ hash.class_eval do
107
+ begin
108
+ remove_method :'previous_[]='
109
+ remove_method :'[]='
110
+ rescue
111
+ end
112
+ end
113
+ end
114
+
115
+ hash.each do |key, value|
116
+ untrack_changes_in_objects(value)
117
+ end
118
+ end
119
+
120
+ def untrack_changes_in_array(array)
121
+ unless array.frozen?
122
+ array.class_eval do
123
+ begin
124
+ remove_method :'previous_[]='
125
+ remove_method :'previous_<<'
126
+ remove_method :previous_push
127
+ remove_method :'[]='
128
+ remove_method :'<<'
129
+ remove_method :push
130
+ rescue
131
+ end
132
+ end
133
+ end
134
+
135
+ array.each do |value|
136
+ untrack_changes_in_objects(value)
137
+ end
138
+ end
139
+
140
+ def untrack_changes_in_object(object)
141
+ object.methods.each do |attribute_name, attribute_value|
142
+ is_setter =
143
+ attribute_name.to_s.ends_with?('=') &&
144
+ attribute_name.to_s.match(/[A-Za-z]/) &&
145
+ !attribute_name.to_s.start_with?('previous_')
146
+
147
+ if is_setter
148
+ unless object.frozen?
149
+ object.class_eval do
150
+ begin
151
+ remove_method :"previous_#{attribute_name}"
152
+ remove_method :"#{attribute_name}"
153
+ rescue
154
+ end
155
+ end
156
+ end
157
+
158
+ getter_name = attribute_name.to_s.chomp('=').to_sym
159
+ object_has_getter = object.methods.include?(getter_name)
160
+ if object_has_getter
161
+ untrack_changes_in_objects object.send(getter_name)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,3 @@
1
+ module Fie
2
+ VERSION = "0.1.9"
3
+ end
@@ -0,0 +1,12 @@
1
+ <span fie-body='true'>
2
+ <%= yield %>
3
+
4
+ <% assigns.symbolize_keys.map do |assigns_name, assigns_value| %>
5
+ <% unless assigns_value.blank? || assigns_name == :marked_for_same_origin_verification %>
6
+ <% encryptor = ActiveSupport::MessageEncryptor.new Rails.application.credentials[:secret_key_base] %>
7
+ <span fie-variable='<%= assigns_name %>' fie-value='<%= encryptor.encrypt_and_sign Marshal.dump(assigns_value) %>'></span>
8
+ <% end %>
9
+ <% end %>
10
+
11
+ <span fie-controller='<%= params[:controller] || @fie_controller_name %>' fie-action='<%= params[:action] || @fie_action_name %>'></span>
12
+ </span>
@@ -0,0 +1,21 @@
1
+ require 'opal'
2
+
3
+ module Fie
4
+ require_tree './fie'
5
+
6
+ include Fie::Native
7
+
8
+ Native(`window.Fie = {}`)
9
+ Native \
10
+ `window.Fie.addEventListener =
11
+ #{
12
+ -> (event_name, selector, block) do
13
+ Element.fie_body.add_event_listener(event_name, selector, &block)
14
+ end
15
+ }`
16
+
17
+ Element.document.add_event_listener('DOMContentLoaded') do
18
+ cable = Cable.new
19
+ Listeners.new(cable)
20
+ end
21
+ end
@@ -0,0 +1,63 @@
1
+ require 'securerandom'
2
+ require 'fie/native'
3
+ require 'json'
4
+
5
+ module Fie
6
+ class Cable
7
+ include Fie::Native
8
+
9
+ def initialize
10
+ connection_uuid = SecureRandom.uuid
11
+ commander_name = "#{ camelize(controller_name) }Commander"
12
+
13
+ @commander = Commander.new(channel_name: commander_name, identifier: connection_uuid, cable: self)
14
+ @pools = {}
15
+ end
16
+
17
+ def call_remote_function(element:, event_name:, function_name:, parameters:)
18
+ log_event(element: element, event_name: event_name, function_name: function_name, parameters: parameters)
19
+
20
+ function_parameters = {
21
+ caller: {
22
+ id: element.id,
23
+ class: element.class_name,
24
+ value: element.value
25
+ },
26
+ controller_name: controller_name,
27
+ action_name: action_name
28
+ }.merge(parameters)
29
+
30
+ @commander.perform(function_name, function_parameters)
31
+ end
32
+
33
+ def subscribe_to_pool(subject)
34
+ @pools['subject'] = Pool.new(channel_name: 'Fie::Pools', identifier: subject, cable: self)
35
+ end
36
+
37
+ def commander
38
+ @commander
39
+ end
40
+
41
+ private
42
+ def log_event(element:, event_name:, function_name:, parameters:)
43
+ parameters = parameters.to_json
44
+ puts "Event #{ event_name } triggered by element #{ element.descriptor } is calling function #{ function_name } with parameters #{ parameters }"
45
+ end
46
+
47
+ def action_name
48
+ view_name_element['fie-action']
49
+ end
50
+
51
+ def controller_name
52
+ view_name_element['fie-controller']
53
+ end
54
+
55
+ def view_name_element
56
+ Element.body.query_selector('[fie-controller]:not([fie-controller=""])');
57
+ end
58
+
59
+ def camelize(string)
60
+ string.split('_').collect(&:capitalize).join
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ require 'fie/native'
2
+ require 'diffhtml'
3
+
4
+ module Fie
5
+ class Commander < Fie::Native::ActionCableChannel
6
+ include Fie::Native
7
+
8
+ def connected
9
+ super
10
+
11
+ @cable.call_remote_function \
12
+ element: Element.body,
13
+ function_name: 'initialize_state',
14
+ event_name: 'Upload State',
15
+ parameters: { view_variables: Util.view_variables }
16
+ end
17
+
18
+ def received(data)
19
+ process_command(data['command'], data['parameters'])
20
+ end
21
+
22
+ def process_command(command, parameters = {})
23
+ case command
24
+ when 'refresh_view'
25
+ $$.diff.innerHTML(Element.fie_body.unwrapped_element, parameters['html'])
26
+ @event.dispatch
27
+ when 'subscribe_to_pools'
28
+ parameters['subjects'].each do |subject|
29
+ @cable.subscribe_to_pool(subject)
30
+ end
31
+ when 'publish_to_pools'
32
+ subject = parameters['subject']
33
+ object = parameters['object']
34
+
35
+ perform("pool_#{ subject }_callback", { object: object })
36
+ when 'execute_function'
37
+ Util.exec_js(parameters['name'], parameters['arguments'])
38
+ else
39
+ console.log("Command: #{ command }, Parameters: #{ parameters }")
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,109 @@
1
+ require 'fie/native'
2
+
3
+ module Fie
4
+ class Listeners
5
+ include Fie::Native
6
+
7
+ def initialize(cable)
8
+ @cable = cable
9
+
10
+ initialize_input_elements
11
+ initialize_fie_events [:click, :submit, :scroll, :keyup, :keydown, :enter]
12
+ end
13
+
14
+ private
15
+ def initialize_fie_events(event_names)
16
+ event_names.each do |fie_event_name|
17
+ event_name = fie_event_name
18
+ event_name = :keydown if event_name == :enter
19
+
20
+ Element.fie_body.add_event_listener(event_name, "[fie-#{ fie_event_name }]:not([fie-#{ fie_event_name }=''])") do |event|
21
+ event_is_valid = (fie_event_name == :enter && event.keyCode == 13) || fie_event_name != :enter
22
+
23
+ if event_is_valid
24
+ element = Element.new(element: event.target)
25
+ remote_function_name = element["fie-#{ fie_event_name }"]
26
+ function_parameters = JSON.parse(element['fie-parameters'] || {})
27
+
28
+ @cable.call_remote_function \
29
+ element: element,
30
+ function_name: remote_function_name,
31
+ event_name: event_name,
32
+ parameters: function_parameters
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def initialize_input_elements
39
+ timer = Timeout.new(0) { }
40
+
41
+ typing_input_types = ['text', 'password', 'search', 'tel', 'url']
42
+
43
+ typing_input_selector = (['textarea'] + typing_input_types).reduce do |selector, input_type|
44
+ selector += ", input[type=#{ input_type }]"
45
+ end
46
+
47
+ non_typing_input_selector = (['input'] + typing_input_types).reduce do |selector, input_type|
48
+ selector += ":not([type=#{ input_type }])"
49
+ end
50
+
51
+ Element.fie_body.add_event_listener('keydown', typing_input_selector) do |event|
52
+ timer.clear
53
+
54
+ input_element = Element.new(element: event.target)
55
+
56
+ timer = Timeout.new(500) do
57
+ update_state_using_changelog(input_element)
58
+ end
59
+ end
60
+
61
+ Element.fie_body.add_event_listener('change', non_typing_input_selector) do |event|
62
+ input_element = Element.new(element: event.target)
63
+ update_state_using_changelog(input_element)
64
+ end
65
+ end
66
+
67
+ def update_state_using_changelog(input_element)
68
+ objects_changelog = {}
69
+
70
+ changed_object_name = Regexp.new('(?:^|])([^[\\]]+)(?:\\[|$)').match(input_element.name)[0][0..-2]
71
+ changed_object_key_chain = input_element.name.scan(Regexp.new '(?<=\\[).+?(?=\\])')
72
+
73
+ is_form_object = !changed_object_key_chain.empty? && !changed_object_name.nil?
74
+ is_fie_nested_object = Util.view_variables.include? changed_object_name
75
+ is_fie_form_object = is_form_object && is_fie_nested_object
76
+
77
+ is_fie_non_nested_object = Util.view_variables.include? input_element.name
78
+
79
+ if is_fie_form_object
80
+ build_changelog(changed_object_key_chain, changed_object_name, objects_changelog, input_element)
81
+ elsif is_fie_non_nested_object
82
+ objects_changelog[input_element.name] = input_element.value;
83
+ end
84
+
85
+ @cable.call_remote_function \
86
+ element: input_element,
87
+ function_name: 'modify_state_using_changelog',
88
+ event_name: 'Input Element Change',
89
+ parameters: { objects_changelog: objects_changelog }
90
+ end
91
+
92
+ def build_changelog(object_key_chain, object_name, changelog, input_element)
93
+ is_final_key = -> (key) { key == object_key_chain[-1] }
94
+ object_final_key_value = input_element.value
95
+
96
+ changelog[object_name] = {}
97
+ changelog = changelog[object_name]
98
+
99
+ object_key_chain.each do |key|
100
+ if is_final_key.call(key)
101
+ changelog[key] = object_final_key_value
102
+ else
103
+ changelog[key] = {}
104
+ changelog = changelog[key]
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,5 @@
1
+ module Fie
2
+ module Native
3
+ require_tree './native'
4
+ end
5
+ end
@@ -0,0 +1,34 @@
1
+ require 'fie/native'
2
+
3
+ module Fie
4
+ module Native
5
+ class ActionCableChannel
6
+ def initialize(channel_name:, identifier:, cable:)
7
+ @channel_name = channel_name
8
+ @identifier = identifier
9
+ @cable = cable
10
+ @event = Event.new('fieChanged')
11
+
12
+ @subscription = $$.App.cable.subscriptions.create(
13
+ { channel: @channel_name, identifier: @identifier },
14
+ {
15
+ connected: -> { connected },
16
+ received: -> (data) { received Native(`#{data}`) }
17
+ }
18
+ )
19
+ end
20
+
21
+ def responds_to?(t)
22
+ true
23
+ end
24
+
25
+ def connected
26
+ puts "Connected to #{ @channel_name } with identifier #{ @identifier }"
27
+ end
28
+
29
+ def perform(function_name, parameters = {})
30
+ @subscription.perform(function_name, parameters);
31
+ end
32
+ end
33
+ end
34
+ end