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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/fie.rb +5 -0
- data/lib/fie/commander.rb +118 -0
- data/lib/fie/commander_closed.rb +1 -0
- data/lib/fie/manipulator.rb +39 -0
- data/lib/fie/pools.rb +23 -0
- data/lib/fie/railtie.rb +13 -0
- data/lib/fie/state.rb +131 -0
- data/lib/fie/state/changelog.rb +48 -0
- data/lib/fie/state/track.rb +167 -0
- data/lib/fie/version.rb +3 -0
- data/lib/layouts/fie.html.erb +12 -0
- data/lib/opal/fie.rb +21 -0
- data/lib/opal/fie/cable.rb +63 -0
- data/lib/opal/fie/commander.rb +43 -0
- data/lib/opal/fie/listeners.rb +109 -0
- data/lib/opal/fie/native.rb +5 -0
- data/lib/opal/fie/native/action_cable_channel.rb +34 -0
- data/lib/opal/fie/native/element.rb +112 -0
- data/lib/opal/fie/native/event.rb +17 -0
- data/lib/opal/fie/native/timeout.rb +13 -0
- data/lib/opal/fie/pool.rb +9 -0
- data/lib/opal/fie/util.rb +23 -0
- data/vendor/javascript/fie.js +1 -0
- metadata +234 -0
@@ -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
|
data/lib/fie/version.rb
ADDED
@@ -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>
|
data/lib/opal/fie.rb
ADDED
@@ -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,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
|