stimulus_reflex 0.1.5 → 0.1.6

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.

Potentially problematic release.


This version of stimulus_reflex might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c37059ad37807d6c742c59bf9e3c6d52a57326d88041f12c5d49eacbe6721e63
4
- data.tar.gz: 9bf0943ec2bb6d987a05d33f85417db38b6ffa1b766c458b9f90b7bda226b59b
3
+ metadata.gz: aa1b3385c6fc339dd85cff34e9fbddc6321dfb44dc2921db48e6d54ec41e12ab
4
+ data.tar.gz: 51fae31f79cc65a44d052980d051d50470b39c9805dc143cc9c62cb60dec521b
5
5
  SHA512:
6
- metadata.gz: eb356c984aca6b7729af7b4d2097e80fbb961ce2f9e77b35ca088a40f48ca35417d1527a86a56edd2d06013eac3bac4d8e3f975657b073988191a0d90733f796
7
- data.tar.gz: 4532896ca95f7263e1853c486c981d5e1ea398016cd52e6b80d255c385788ab86ca3a587395f8deaf1578a82cd322b4f789442b135322fa3bce6e3cbbc4f69ea
6
+ metadata.gz: 5a756d9b78d8e4be44720a81c3506716d8a42d532a001fc84d266449a15f26833c489a4f99b0f2960a978e2ac976fe27fd970f3bdd5061db72af71176e4ff6f0
7
+ data.tar.gz: 06cf961977708a6375eb85a7532ee4e406eca085a925c7060d5d190b1fd4622cba29214e068e60ab40fbdd34c1b95d71dd1944bfd4af559f31943bcf5f326316
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stimulus_reflex (0.1.5)
4
+ stimulus_reflex (0.1.6)
5
5
  actioncable (>= 5.2.1)
6
6
  actionpack (>= 5.2.1)
7
7
  cable_ready (>= 2.0.5)
data/README.md CHANGED
@@ -1,22 +1,24 @@
1
- [![Lines of Code](http://img.shields.io/badge/lines_of_code-123-brightgreen.svg?style=flat)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
1
+ [![Lines of Code](http://img.shields.io/badge/lines_of_code-131-brightgreen.svg?style=flat)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
2
+ [![Maintainability](https://img.shields.io/codeclimate/maintainability/hopsoft/stimulus_reflex.svg)](https://codeclimate.com/github/hopsoft/stimulus_reflex)
2
3
 
3
4
  # StimulusReflex
4
5
 
5
6
  #### Server side reactive behavior for Stimulus controllers
6
7
 
7
- ## TODO
8
+ Add the benefits of single page apps (SPA) to server rendered Rails/Stimulus projects with a minimal investment of time, resources, and complexity.
9
+ _The goal is to provide 80% of the benefits of SPAs with 20% of the typical effort._
8
10
 
9
- - [ ] Allow Ruby channels to override the stream_name
10
- - [ ] Support send without render
11
+ > This library provides functionality similar to [Phoenix LiveView](https://youtu.be/Z2DU0qLfPIY?t=670) for Rails applications.
11
12
 
12
13
  ## Usage
13
14
 
14
15
  ```ruby
15
16
  # Gemfile
17
+ gem "cable_ready"
16
18
  gem "stimulus_reflex"
17
19
  ```
18
20
 
19
- ```
21
+ ```javascript
20
22
  // app/assets/javascripts/cable.js
21
23
  //= require cable_ready
22
24
  //= require stimulus_reflex
@@ -37,6 +39,59 @@ export default class extends Controller {
37
39
  }
38
40
  ```
39
41
 
42
+ ```ruby
43
+ # app/stimulus_controllers/example_stimulus_controller.rb
44
+ class ExampleStimulusController < StimulusReflex::Controller
45
+ def do_stuff(arg1, arg2, ...)
46
+ # hard work...
47
+ # 1. the page that triggered this call will rererender
48
+ # 2. the HTML will be sent over the ActionCable socket
49
+ # 3. client side JavaScript will DOM diff and mutate only the changed nodes
50
+ end
51
+ end
52
+ ```
53
+
54
+ ## Advanced Usage
55
+
56
+ ### Page Rerender
57
+
58
+ The page is always rerendered after triggering a `StimulusReflex`.
59
+ The client side JavaScript debounces this render via `setTimeout` to prevent a jarring user experience.
60
+ The default delay of `400ms` can be overriddend with the following JavaScript.
61
+
62
+ ```javascript
63
+ StimulusReflex.renderDelay = 200;
64
+ ```
65
+
66
+ ## Instrumentation
67
+
68
+ SEE: https://guides.rubyonrails.org/active_support_instrumentation.html
69
+
70
+ ```ruby
71
+ # wraps the stimulus controller method invocation
72
+ ActiveSupport::Notifications.subscribe "delegate_call.stimulus_reflex" do |*args|
73
+ event = ActiveSupport::Notifications::Event.new(*args)
74
+ Rails.logger.debug "#{event.name} #{event.duration} #{event.payload.inspect}"
75
+ end
76
+
77
+ # instruments the page rerender
78
+ ActiveSupport::Notifications.subscribe "render_page.stimulus_reflex" do |*args|
79
+ event = ActiveSupport::Notifications::Event.new(*args)
80
+ Rails.logger.debug "#{event.name} #{event.duration} #{event.payload.inspect}"
81
+ end
82
+
83
+ # wraps the web socket broadcast
84
+ ActiveSupport::Notifications.subscribe "broadcast.stimulus_reflex" do |*args|
85
+ event = ActiveSupport::Notifications::Event.new(*args)
86
+ Rails.logger.debug "#{event.name} #{event.duration} #{event.payload.inspect}"
87
+ end
88
+
89
+ # wraps the entire receive operation which includes everything above
90
+ ActiveSupport::Notifications.subscribe "receive.stimulus_reflex" do |*args|
91
+ event = ActiveSupport::Notifications::Event.new(*args)
92
+ Rails.logger.debug "#{event.name} #{event.duration} #{event.payload.inspect}"
93
+ end
94
+ ```
40
95
 
41
96
  ## JavaScript Development
42
97
 
@@ -5,7 +5,6 @@ require "active_support/all"
5
5
  require "action_dispatch"
6
6
  require "cable_ready"
7
7
 
8
- #class StimulusReflex::Channel < ApplicationCable::Channel
9
8
  class StimulusReflex::Channel < ActionCable::Channel::Base
10
9
  include CableReady::Broadcaster
11
10
 
@@ -21,70 +20,75 @@ class StimulusReflex::Channel < ActionCable::Channel::Base
21
20
  end
22
21
 
23
22
  def receive(data)
24
- logger.debug "StimulusReflex::Channel#receive #{data.inspect}"
25
- start = Time.current
26
- url = data["url"].to_s
27
- target = data["target"].to_s
28
- stimulus_controller_name, stimulus_method_name = target.split("#")
29
- stimulus_controller_name = "#{stimulus_controller_name.classify}StimulusController"
30
- stimulus_controller = nil
31
- arguments = data["args"]
32
-
33
- begin
34
- ActiveSupport::Notifications.instrument "delegate_call.stimulus_reflex", url: url, target: target, arguments: arguments do
35
- stimulus_controller = stimulus_controller_name.constantize.new(self)
36
- if arguments.present?
37
- stimulus_controller.send stimulus_method_name, *arguments
38
- else
39
- stimulus_controller.send stimulus_method_name
40
- end
41
- end
23
+ ActiveSupport::Notifications.instrument "receive.stimulus_reflex", data do
24
+ start = Time.current
25
+ url = data["url"].to_s
26
+ target = data["target"].to_s
27
+ stimulus_controller_name, method_name = target.split("#")
28
+ stimulus_controller_name = "#{stimulus_controller_name.classify}StimulusController"
29
+ arguments = data["args"] || []
42
30
 
43
31
  begin
44
- html = render_page(url, stimulus_controller)
45
- broadcast_morph extract_body_html(html)
46
- rescue StandardError => render_error
47
- logger.error "StimulusReflex::Channel: #{url} Failed to rerender #{params} after invoking #{target}! #{render_error} #{render_error.backtrace}"
32
+ stimulus_controller = stimulus_controller_name.constantize.new(self)
33
+ delegate_call_to_stimulus_controller stimulus_controller, method_name, arguments
34
+ render_page_and_broadcast_morph url
35
+ rescue StandardError => invoke_error
36
+ logger.error "StimulusReflex::Channel Failed to invoke #{target}! #{url} #{invoke_error}"
48
37
  end
49
- rescue StandardError => invoke_error
50
- logger.error "StimulusReflex::Channel: #{url} Failed to invoke #{target}! #{invoke_error}"
51
38
  end
52
39
  end
53
40
 
54
41
  private
55
42
 
56
- def render_page(url, stimulus_controller)
57
- params = Rails.application.routes.recognize_path(url)
58
- controller_class = "#{params[:controller]}_controller".classify.constantize
59
- controller = controller_class.new
60
- controller.instance_variable_set :"@stimulus_reflex", true
61
- stimulus_controller.instance_variables.each do |instance_variable_name|
62
- controller.instance_variable_set instance_variable_name, stimulus_controller.instance_variable_get(instance_variable_name)
43
+ def delegate_call_to_stimulus_controller(stimulus_controller, method_name, arguments = [])
44
+ instrument_payload = { stimulus_controller: stimulus_controller.class.name, method_name: method_name, arguments: arguments.inspect }
45
+ ActiveSupport::Notifications.instrument "delegate_call.stimulus_reflex", instrument_payload do
46
+ if stimulus_controller.method(method_name).arity > 0
47
+ stimulus_controller.send method_name, *arguments
48
+ else
49
+ stimulus_controller.send method_name
50
+ end
63
51
  end
52
+ end
64
53
 
65
- uri = URI.parse(url)
66
- env = {
67
- Rack::SCRIPT_NAME => uri.path,
68
- Rack::QUERY_STRING => uri.query,
69
- Rack::PATH_INFO => ""
70
- }
71
- request = ActionDispatch::Request.new(connection.env.merge(env))
72
- controller.request = request
73
- controller.response = ActionDispatch::Response.new
54
+ def render_page_and_broadcast_morph(url)
55
+ html = render_page(url)
56
+ broadcast_morph url, html if html.present?
57
+ end
74
58
 
75
- ActiveSupport::Notifications.instrument "process_controller_action.stimulus_reflex", url: url do
76
- controller.process params[:action]
59
+ def render_page(url)
60
+ html = nil
61
+ ActiveSupport::Notifications.instrument "render_page.stimulus_reflex", url: url do
62
+ uri = URI.parse(url)
63
+ url_params = Rails.application.routes.recognize_path(url)
64
+ controller_class = "#{url_params[:controller]}_controller".classify.constantize
65
+ controller = controller_class.new
66
+ controller.instance_variable_set :"@stimulus_reflex", true
67
+
68
+ env = {
69
+ Rack::SCRIPT_NAME => uri.path,
70
+ Rack::QUERY_STRING => uri.query,
71
+ Rack::PATH_INFO => "",
72
+ }
73
+ request = ActionDispatch::Request.new(connection.env.merge(env))
74
+ controller.request = request
75
+ controller.response = ActionDispatch::Response.new
76
+ controller.process url_params[:action]
77
+ html = controller.response.body
78
+ end
79
+ html
80
+ end
81
+
82
+ def broadcast_morph(url, html)
83
+ ActiveSupport::Notifications.instrument "broadcast.stimulus_reflex", url: url, cable_ready: :morph do
84
+ html = extract_body_html(html)
85
+ cable_ready[stream_name].morph selector: "body", html: html, children_only: true
86
+ cable_ready.broadcast
77
87
  end
78
- controller.response.body
79
88
  end
80
89
 
81
90
  def extract_body_html(html)
82
91
  doc = Nokogiri::HTML(html)
83
92
  doc.css("body").to_s
84
93
  end
85
-
86
- def broadcast_morph(html)
87
- cable_ready[stream_name].morph selector: "body", html: html, children_only: true
88
- cable_ready.broadcast
89
- end
90
94
  end
@@ -1,3 +1,3 @@
1
1
  module StimulusReflex
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.6"
3
3
  end
@@ -1 +1 @@
1
- window.StimulusReflex=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([function(t,e,n){(function(e){var n="Expected a function",r=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,u=/^[-+]0x[0-9a-f]+$/i,c=/^0b[01]+$/i,f=/^0o[0-7]+$/i,a=parseInt,l="object"==typeof e&&e&&e.Object===Object&&e,s="object"==typeof self&&self&&self.Object===Object&&self,p=l||s||Function("return this")(),d=Object.prototype.toString,b=Math.max,v=Math.min,y=function(){return p.Date.now()};function m(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function j(t){if("number"==typeof t)return t;if(function(t){return"symbol"==typeof t||function(t){return!!t&&"object"==typeof t}(t)&&d.call(t)==o}(t))return r;if(m(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=m(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(i,"");var n=c.test(t);return n||f.test(t)?a(t.slice(2),n?2:8):u.test(t)?r:+t}t.exports=function(t,e,r){var o,i,u,c,f,a,l=0,s=!1,p=!1,d=!0;if("function"!=typeof t)throw new TypeError(n);function x(e){var n=o,r=i;return o=i=void 0,l=e,c=t.apply(r,n)}function g(t){var n=t-a;return void 0===a||n>=e||n<0||p&&t-l>=u}function h(){var t=y();if(g(t))return w(t);f=setTimeout(h,function(t){var n=e-(t-a);return p?v(n,u-(t-l)):n}(t))}function w(t){return f=void 0,d&&o?x(t):(o=i=void 0,c)}function O(){var t=y(),n=g(t);if(o=arguments,i=this,a=t,n){if(void 0===f)return function(t){return l=t,f=setTimeout(h,e),s?x(t):c}(a);if(p)return f=setTimeout(h,e),x(a)}return void 0===f&&(f=setTimeout(h,e)),c}return e=j(e)||0,m(r)&&(s=!!r.leading,u=(p="maxWait"in r)?b(j(r.maxWait)||0,e):u,d="trailing"in r?!!r.trailing:d),O.cancel=function(){void 0!==f&&clearTimeout(f),l=0,o=a=i=f=void 0},O.flush=function(){return void 0===f?c:w(y())},O}}).call(this,n(2))},function(t,e,n){"use strict";n.r(e),n.d(e,"register",function(){return f});var r=n(0),o=n.n(r);window.App=window.App||{},App.cable=App.cable||ActionCable.createConsumer(),App.stimulusReflex=App.stimulusReflex||App.cable.subscriptions.create("StimulusReflex::Channel",{received:function(t){t.cableReady&&u(t.operations)}});var i=o()(function(t){return App.stimulusReflex.send(t)},250,{}),u=o()(function(t){CableReady.perform(t),document.dispatchEvent(new Event("turbolinks:load"))},200,{}),c={send:function(){var t=Array.prototype.slice.call(arguments),e=t.shift();i({url:location.href,target:e,args:t})}},f=function(t){return Object.assign(t,c),t}},function(t,e){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(t){"object"==typeof window&&(n=window)}t.exports=n}]);
1
+ window.StimulusReflex=function(e){var t={};function r(n){if(t[n])return t[n].exports;var u=t[n]={i:n,l:!1,exports:{}};return e[n].call(u.exports,u,u.exports,r),u.l=!0,u.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var u in e)r.d(n,u,function(t){return e[t]}.bind(null,u));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";var n;r.r(t),r.d(t,"register",function(){return o});window.App=window.App||{},App.cable=App.cable||ActionCable.createConsumer(),App.stimulusReflex=App.stimulusReflex||App.cable.subscriptions.create("StimulusReflex::Channel",{received:function(e){e.cableReady&&(clearTimeout(n),n=setTimeout(function(){CableReady.perform(e.operations),document.dispatchEvent(new Event("turbolinks:load"))},StimulusReflex.renderDelay||400))}});var u={send:function(){clearTimeout(n);var e=Array.prototype.slice.call(arguments),t=e.shift();App.stimulusReflex.send({url:location.href,target:t,args:e})}},o=function(e){return Object.assign(e,u),e}}]);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimulus_reflex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Hopkins
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-10-20 00:00:00.000000000 Z
12
+ date: 2018-10-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack