stimulus_reflex 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.

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