opalla 0.1.0 → 0.1.1

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,28 @@
1
+ module Opalla
2
+ module ComponentHelper
3
+ def component(name, id: nil, model: nil, collection: nil)
4
+ comp_id = (id || "#{name}-#{cidn_and_increment}")
5
+ html = component_html(name, id: id, model: model, collection: collection)
6
+ output = Nokogiri::HTML.fragment(html).children.attr(id: comp_id).to_s
7
+ output.html_safe
8
+ end
9
+
10
+ protected
11
+
12
+ def component_html(name, id: nil, model: nil, collection: nil)
13
+ options = { partial: "components/#{name}", locals: {} }
14
+ model.nil? || options[:locals][:model] = model
15
+ collection.nil? || options[:locals][:collection] = collection
16
+ render(options)
17
+ end
18
+
19
+ def cidn
20
+ @cidn ||= 0
21
+ end
22
+
23
+ def cidn_and_increment
24
+ @cidn = cidn + 1
25
+ @cidn - 1
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Opalla
2
+ module ControllerAddOn
3
+ def expose(variable_assignments)
4
+ Opalla::Util.add_vars(variable_assignments)
5
+ variable_assignments.each do |key, value|
6
+ define_singleton_method(key){ value }
7
+ self.class.helper_method key
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails'
2
+
3
+ module Opalla
4
+ class Engine < ::Rails::Engine
5
+ config.before_configuration do |app|
6
+ config.app_generators.javascript_engine :opalla
7
+ app.middleware.use OpallaMiddleware
8
+ js_folder = app.root.join(*%w[app assets javascripts]).to_s
9
+ app.config.autoload_paths += ["#{js_folder}/lib"]
10
+ app.config.autoload_paths += ["#{js_folder}/models"]
11
+ app.config.autoload_paths += ["#{js_folder}/collections"]
12
+ Opal.append_path File.expand_path('../../../opal', __FILE__)
13
+ end
14
+
15
+ ActiveSupport.on_load(:action_view) do
16
+ include Opalla::ComponentHelper
17
+ end
18
+
19
+ ActiveSupport.on_load(:action_controller) do
20
+ include Opalla::ControllerAddOn
21
+ end
22
+
23
+ config.after_initialize do |app|
24
+ ActionController::Base.prepend_view_path "app/assets/javascripts/views/"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ class OpallaMiddleware
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ s, h, r = @app.call(env)
8
+ return [s, h, r] unless h['Content-Type'] =~ %r{text/html}
9
+ html = r.body.gsub("<body>", "<body>#{js_routes}")
10
+ [s, h, [html]]
11
+ end
12
+
13
+ def js_routes
14
+ <<~JS
15
+ <script>
16
+ window.opalla_data = #{ Opalla::Util.data_dump.to_json }
17
+ </script>
18
+ JS
19
+ end
20
+ end
@@ -0,0 +1,63 @@
1
+ require 'digest'
2
+
3
+ module Opalla
4
+ module HexRandom
5
+ def self.[]
6
+ x = rand.to_s
7
+ y = Time.new.strftime("%S%L")
8
+ Digest::SHA1.hexdigest("#{y}#{x}")
9
+ end
10
+ end
11
+ module Util
12
+ class JsFormatter
13
+ def initialize
14
+ @buffer = []
15
+ end
16
+
17
+ def result
18
+ @buffer
19
+ end
20
+
21
+ def section(routes)
22
+ @buffer = routes.each_with_object({}) do |r, memo|
23
+ path = r[:path].gsub(/\(.:format\)/, '')
24
+ memo[r[:name]] = {
25
+ verb: r[:verb],
26
+ path: path,
27
+ reqs: r[:reqs]
28
+ }
29
+ end
30
+ end
31
+
32
+ def header(routes); end
33
+ end
34
+
35
+ class << self
36
+ def add_vars(var_assign)
37
+ @vars ||= {}
38
+ @vars.merge!(var_assign)
39
+ end
40
+
41
+ def vars
42
+ @vars || {}
43
+ end
44
+
45
+ def data_dump
46
+ Marshal.dump(data)
47
+ end
48
+
49
+ def data
50
+ {
51
+ routes: routes,
52
+ vars: vars
53
+ }
54
+ end
55
+
56
+ def routes
57
+ all_routes = Rails.application.routes.routes
58
+ inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes)
59
+ inspector.format(JsFormatter.new)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,3 @@
1
1
  module Opalla
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -0,0 +1,23 @@
1
+ require 'rails/generators'
2
+
3
+ module Opalla
4
+ class AssetsGenerator < Rails::Generators::NamedBase
5
+ def create_controller
6
+ create_file js("controllers/#{file_name}_controller.rb"), <<~CONTROLLER
7
+ class #{class_name}Controller < ApplicationController
8
+ # Write your actions here!
9
+ end
10
+ CONTROLLER
11
+ end
12
+
13
+ def create_view_folder
14
+ empty_directory js("views/#{file_name}")
15
+ end
16
+
17
+ protected
18
+
19
+ def js(path)
20
+ "app/assets/javascripts/#{path}"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require 'rails/generators'
2
+
3
+ module Opalla
4
+ class CollectionGenerator < Rails::Generators::NamedBase
5
+ def create_collection
6
+ create_file js("collections/#{file_name}.rb"), <<~CONTROLLER
7
+ class #{class_name} < Opalla::Collection
8
+ # attr_reader :attrs
9
+ end
10
+ CONTROLLER
11
+ end
12
+
13
+ protected
14
+
15
+ def js(path)
16
+ "app/assets/javascripts/#{path}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails/generators'
2
+
3
+ module Opalla
4
+ class ComponentGenerator < Rails::Generators::NamedBase
5
+ def create_component
6
+ create_file js("components/#{file_name}_component.rb"), <<~CONTROLLER
7
+ class #{class_name}Component < ApplicationComponent
8
+ # Feel free to write your component actions, bindings, events
9
+ end
10
+ CONTROLLER
11
+ end
12
+
13
+ def create_views
14
+ ext = defined?(Haml) ? 'haml' : 'erb'
15
+ create_file js("views/components/_#{file_name}.#{ext}"), <<~VIEW
16
+ .#{file_name.dasherize}
17
+ -# Component content
18
+ VIEW
19
+ end
20
+
21
+ protected
22
+
23
+ def js(path)
24
+ "app/assets/javascripts/#{path}"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ require 'rails/generators'
2
+
3
+ module Opalla
4
+ class InstallGenerator < Rails::Generators::Base
5
+ desc 'Creates Opalla files'
6
+
7
+ def create_folders
8
+ %w[
9
+ components
10
+ controllers
11
+ lib
12
+ models
13
+ collections
14
+ views/components
15
+ ].each {|dir| empty_directory js(dir) }
16
+ end
17
+
18
+ def create_basic_files
19
+ create_file js('application.rb'), <<~APPLICATION
20
+ require 'opalla'
21
+
22
+ require_tree './lib'
23
+ require_tree './models'
24
+ require_tree './collections'
25
+ require_tree './components'
26
+ require_tree './controllers'
27
+ require_tree './views'
28
+
29
+ Document.ready? do
30
+ Opalla::Router.start
31
+ end
32
+ APPLICATION
33
+
34
+ delete_appjs = ask %q{
35
+ I've just created the main app file (application.rb)
36
+ Should I just delete your application.js, since you won't need it anymore?
37
+ (If you say no, please be sure to remove it later, ok?)
38
+ [Y/n]
39
+ }
40
+
41
+ remove_file(js('application.js')) if delete_appjs == 'Y'
42
+
43
+ create_file js('components/application_component.rb'), <<~COMPONENT
44
+ class ApplicationComponent < Opalla::Component
45
+ # Code shared between all components go here
46
+ end
47
+ COMPONENT
48
+
49
+ create_file js('controllers/application_controller.rb'), <<~CONTROLLER
50
+ class ApplicationController < Opalla::Controller
51
+ # Code shared between all controllers go here
52
+ end
53
+ CONTROLLER
54
+ end
55
+
56
+ protected
57
+
58
+ def js(path)
59
+ "app/assets/javascripts/#{path}"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ require 'rails/generators'
2
+
3
+ module Opalla
4
+ class ModelGenerator < Rails::Generators::NamedBase
5
+ def create_component
6
+ create_file js("models/#{file_name}.rb"), <<~CONTROLLER
7
+ class #{class_name} < Opalla::Model
8
+ # attr_reader :attrs
9
+ end
10
+ CONTROLLER
11
+ end
12
+
13
+ protected
14
+
15
+ def js(path)
16
+ "app/assets/javascripts/#{path}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ module Opalla
2
+ class Collection
3
+ attr_accessor :models
4
+ alias_method :all, :models
5
+
6
+ def initialize(models=nil)
7
+ @models = models || []
8
+ end
9
+
10
+ def create(options)
11
+ model_class.new(options)
12
+ end
13
+
14
+ def add(*attributes)
15
+ new_model = model_class.new(*attributes)
16
+ bindings.each {|b| new_model.bind(*b[:attributes], b[:callback])}
17
+ models << new_model
18
+ trigger_callbacks
19
+ end
20
+
21
+ def remove(arg)
22
+ case arg
23
+ when Element, Numeric, String then models.delete(find(arg))
24
+ else models.delete(arg)
25
+ end
26
+ trigger_callbacks
27
+ end
28
+
29
+ def find(arg)
30
+ case arg
31
+ when Numeric, String
32
+ models.each do |model|
33
+ return model if model.model_id == arg
34
+ end
35
+ when Element
36
+ sel = '[data-model-id]'
37
+ $console.log arg
38
+ if arg.is(sel)
39
+ find(arg)
40
+ else
41
+ find(arg.closest(sel).first.data('model-id'))
42
+ end
43
+ end
44
+ end
45
+
46
+ def bind(*attributes, callback)
47
+ @bindings ||= []
48
+ @bindings << { attributes: attributes, callback: callback }
49
+ models.each {|m| m.bind(*attributes, callback) }
50
+ end
51
+
52
+ def bindings
53
+ @bindings || []
54
+ end
55
+
56
+ def trigger_callbacks
57
+ callbacks.empty? && return
58
+ callbacks.each(&:call)
59
+ end
60
+
61
+ def callbacks
62
+ @bindings.map { |b| b[:callback] }
63
+ end
64
+
65
+ protected
66
+
67
+ def model_class
68
+ Object::const_get("#{self.class.to_s.singularize}")
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,136 @@
1
+ module Opalla
2
+ class Component
3
+ include ViewHelper
4
+ attr_reader :template, :events, :model, :collection, :components, :id
5
+
6
+ def initialize(template: nil, model: nil, collection: nil, id: nil)
7
+ template ||= "components/_#{component_name}"
8
+ @template = Template["views/#{template}"]
9
+ @components = []
10
+ @id = id
11
+ @resource = @model = model unless model.nil?
12
+ @resource = @collection = collection unless collection.nil?
13
+ register_bindings
14
+ end
15
+
16
+ def render
17
+ if @rendered.nil?
18
+ @rendered = true
19
+ html = Element[template.render(self)]
20
+ @id = id || cid(cidn_and_increment)
21
+ html.attr(:id, id)
22
+ html
23
+ else
24
+ target = Element[template.render(self)]
25
+ el.morph(target).attr(:id, id)
26
+ bind_events
27
+ end
28
+ end
29
+
30
+ def el
31
+ Element["##{id}"] if @rendered
32
+ end
33
+
34
+ def bind_events
35
+ remove_events
36
+ components.each &:bind_events
37
+ events_hash.nil? && return
38
+ events_hash.each do |caller, method|
39
+ event, selector = caller.split(' ', 2)
40
+ el.find(selector).on event do |e|
41
+ e.prevent
42
+ case method
43
+ when Symbol then send(method, e.element)
44
+ when Proc then instance_exec(e.element, &method)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def remove_events
51
+ components.each &:remove_events
52
+ events_hash.nil? && return
53
+ events_hash.each do |caller, method|
54
+ event, selector = caller.split(' ', 2)
55
+ el.find(selector).off event
56
+ end
57
+ end
58
+
59
+ def component(name, options={})
60
+ comp = Object::const_get("#{name.camelize}Component").new(options)
61
+ @components << comp
62
+ comp.render
63
+ end
64
+
65
+ protected
66
+
67
+ def cidn
68
+ n = self.class.instance_variable_get(:"@cidn")
69
+ return(n) unless n.nil?
70
+ self.class.instance_variable_set(:"@cidn", 0)
71
+ end
72
+
73
+ def cid
74
+ "#{component_name}-#{cidn}"
75
+ end
76
+
77
+ def cidn_and_increment
78
+ self.class.instance_variable_set :"@cidn", cidn + 1
79
+ self.class.instance_variable_get(:"@cidn") - 1
80
+ end
81
+
82
+ def get_bindings
83
+ self.class.instance_variable_get :@bindings
84
+ end
85
+
86
+ def register_bindings
87
+ get_bindings.nil? && return
88
+ if !@model.nil?
89
+ bind_model
90
+ elsif !@collection.nil?
91
+ bind_collection
92
+ end
93
+ include_input_bindings
94
+ end
95
+
96
+ def include_input_bindings
97
+ merge_events_hash({
98
+ 'input [data-bind]' => -> target do
99
+ model = @resource.find(target)
100
+ attr = target.data('bind')
101
+ model.public_send(:"#{attr}=", target.value)
102
+ end
103
+ })
104
+ end
105
+
106
+ def component_name
107
+ self.class.to_s.underscore.gsub('_component', '')
108
+ end
109
+
110
+ def events_hash
111
+ self.class.instance_variable_get(:@events) || {}
112
+ end
113
+
114
+ def merge_events_hash(new_hash)
115
+ self.class.instance_variable_set(:@events, events_hash.merge(new_hash))
116
+ end
117
+
118
+ def self.events(events_hash)
119
+ @events = events_hash
120
+ end
121
+
122
+ def self.bind(*attributes)
123
+ @bindings = attributes
124
+ end
125
+
126
+ def bind_model
127
+ model.nil? && return
128
+ model.bind(*get_bindings, -> { render } )
129
+ end
130
+
131
+ def bind_collection
132
+ (collection.nil? || get_bindings.nil?) && return
133
+ collection.bind(*get_bindings, -> { render })
134
+ end
135
+ end
136
+ end