Giter Club home page Giter Club logo

motion's Introduction

Motion

Gem Version npm version Build Status Maintainability Test Coverage Ruby Code Style JavaScript Code Style

Motion allows you to build reactive, real-time frontend UI components in your Rails application using pure Ruby. Check out some live examples and the code for the examples.

  • Plays nicely with the Rails monolith you have.
  • Peacefully coexists with your existing tech: Strong Parameters, Turbolinks, Trix, React, Vue, etc.
  • Real-time frontend UI updates from frontend user interaction AND server-side updates.
  • Leans on ActionCable and ViewComponent for the heavy lifting.
  • No more frontend models, stores, or syncing; your source of truth is the database you already have.
  • No JavaScript required!

Installation

  1. Install Motion gem and JS package

Motion has Ruby and JavaScript parts, execute both of these commands:

bundle add motion
yarn add @unabridged/motion
  1. Install ViewComponent

You need a view component library to use Motion. Technically, any view component library that implements the render_in interface that landed in Rails 6.1 should be compatible, but Motion is actively developed and tested against Github's ViewComponent.

Installation instructions for ViewComponent are here.

  1. Install ActionCable

Motion communicates over and therefore requires ActionCable.

  1. Run install script

After installing all libraries, run the install script:

bin/rails g motion:install

This will install 2 files, both of which you are free to leave alone.

How does it work?

Motion allows you to mount special DOM elements (henceforth "Motion components") in your standard Rails views that can be real-time updated from frontend interactions, backend state changes, or a combination of both.

  • Websockets Communication - Communication with your Rails backend is performed via ActionCable
  • No Full Page Reload - The current page for a user is updated in place.
  • Fast DOM Diffing - DOM diffing is performed when replacing existing content with new content.
  • Server Triggered Events - Server-side events can trigger updates to arbitrarily many components via WebSocket channels.
  • Partial Page Replacement - Motion does not use full page replacement, but rather replaces only the component on the page with new HTML, DOM diffed for performance.
  • Encapsulated, consistent stateful components - Components have continuous internal state that persists and updates. This means each time a component changes, new rendered HTML is generated and can replace what was there before.
  • Blazing Fast - Communication does not have to go through the full Rails router and controller stack. No changes to your routing or controller are required to get the full functionality of Motion.

Frontend interactions

Frontend interactions can update your Motion components using standard JavaScript events that you're already familiar with: change, blur, form submission, and more. You can invoke Motion actions manually using JavaScript if you need to.

The primary way to handle user interactions on the frontend is by using map_motion:

class MyComponent < ViewComponent::Base
  include Motion::Component

  attr_reader :total

  def initialize(total: 0)
    @total = 0
  end

  map_motion :add

  def add
    @total += 1
  end
end

To invoke this motion on the frontend, add data-motion='add' to your component's template:

<div>
  <span><%= total %></span>
  <%= button_tag "Increment", data: { motion: "add" } %>
</div>

This component can be included on your page the same as always with ViewComponent:

<%= render MyComponent.new(total: 5) %>

Every time the "Increment" button is clicked, MyComponent will call the add method, re-render your component and send it back to the frontend to replace the existing DOM. All invocations of mapped motions will cause the component to re-render, and unchanged rendered HTML will not perform any changes.

Backend interactions

Backend changes can be streamed to your Motion components in 2 steps.

  1. Broadcast changes using ActionCable after an event you care about:
class Todo < ApplicationModel
  after_commit :broadcast_created, on: :create

  def broadcast_created
    ActionCable.server.broadcast("todos:created", name)
  end
end
  1. Configure your Motion component to listen to an ActionCable channel:
class TopTodosComponent < ViewComponent::Base
  include Motion::Component

  stream_from "todos:created", :handle_created

  def initialize(count: 5)
    @count = count
    @todos = Todo.order(created_at: :desc).limit(count).pluck(:name)
  end

  def handle_created(name)
    @todos = [name, *@todos.first(@count - 1)]
  end
end

This will cause any user that has a page open with TopTodosComponent mounted on it to re-render that component's portion of the page.

All invocations of stream_from connected methods will cause the component to re-render everywhere, and unchanged rendered HTML will not perform any changes.

Periodic Timers

Motion can automatically invoke a method on your component at regular intervals:

class ClockComponent < ViewComponent::Base
  include Motion::Component

  def initialize
    @time = Time.now
  end

  every 1.second, :tick

  def tick
    @time = Time.now
  end
end

Motion::Event and Motion::Element

Methods that are mapped using map_motion accept an event parameter which is a Motion::Event. This object has a target attribute which is a Motion::Element, the element in the DOM that triggered the motion. Useful state and attributes can be extracted from these objects, including value, selected, checked, form state, data attributes, and more.

  map_motion :example

  def example(event)
    event.type # => "change"
    event.name # alias for type

    # Motion::Element instance, the element that received the event.
    event.target

    # Motion::Element instance, the element with the event handler and the `data-motion` attribute
    event.element


    # Element API examples
    element.tag_name # => "input"
    element.value # => "5"
    element.attributes # { class: "col-xs-12", ... }

    # DOM element with aria-label="..."
    element[:aria_label]

    # DOM element with data-extra-info="..."
    element.data[:extra_info]

    # ActionController::Parameters instance with all form params. Also
    # available on Motion::Event objects for convenience.
    element.form_data
  end

See the code for full API for Event and Element.

Callbacks

Motion has callbacks which will let you pass data from a child component back up to the parent. Callbacks are created by calling bind with the name of a method on the parent component which will act as a handler. It returns a new callback which can be passed to child components like any other state. To invoke the handler from the callback, use call. If the handler accepts an argument, it will receive anything that is passed to call.

parent_component.rb

  # this will be called from the child component
  def do_something(message)
    puts "Colonel Sandurz says: "
    puts message
  end

parent_component.html.erb

  <%= render ChildComponent.new(on_click: bind(:do_something)) %>

child_component.rb

  attr_reader :on_click

  map_motion :click

  def initialize(on_click:)
    @on_click = on_click
  end

  def click
    on_click.call("Do something!") # an argument can be passed into `call`
  end

child_component.html.erb

  <%= button_tag "You gotta help me, I can't make decisions!", data: { motion: "click" } %>

Limitations

  • Due to the way that Motion components are replaced on the page, component HTML templates are limited to a single top-level DOM element. If you have multiple DOM elements in your template at the top level, you must wrap them in a single element. This is a similar limitation that React enforced until React.Fragment appeared and is for a very similar reason.

  • Information about the request to the server is lost after the first re-render. This may cause things like _url helpers to fail to find the correct domain automatically. A workaround is to provide the domain to the helpers, and cache as a local variable any information from the request object that you need.

Roadmap

Broadly speaking, these initiatives are on our roadmap:

  • Enhanced documentation and usage examples
  • Support more ViewComponent-like libraries
  • Support for AnyCable
  • Support for server-side state to reduce over-the-wire HTML size
  • Support communication via AJAX instead of (or in addition to) websockets

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/unabridged/motion.

License

The gem is available as open source under the terms of the MIT License.

motion's People

Contributors

adrienpoly avatar alecdotninja avatar alexandra-love avatar atrox avatar awolfson avatar carlzulauf avatar clottman avatar dependabot[bot] avatar gmfholley avatar hashnotadam avatar latortuga avatar leastbad avatar lujanfernaud avatar mdaubs avatar mrrooijen avatar olleolleolle avatar r3trofitted avatar sosodev avatar zog avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

motion's Issues

Using user timezone on each render/motion process (around_ callback?)

Feature Request

TL;DR I think we're missing an around_ callback around rendering.

Description

I have a component that displays time based on the user's timezone. To achieve this on initial request, I use the user's timezone for that request:

class ApplicationController < ActionController::Base
  around_action :use_user_timezone, if: :current_user

  private

  def use_user_timezone(&block)
    Time.use_zone(current_user.timezone, &block)
  end
end

However, when Motion re-renders my component, it does so with UTC time.

I have tried:

  • Using Time.use_zone(current_user.timezone) do around the component mapped motion so that everything database based is done with the user's timezone. However, this only affects backend stuff and not the actual re-rendering. Also, it is very cumbersome to wrap every method by hand.
class TaskComponent < ViewComponent::Base
  include Motion::Component

  map_motion :do_something

  def do_something
    Time.use_zone(current_user.timezone) do
      object.perform
    end
  end
end
  • Setting timezone on before_render. This works but it will not unset it after rendering is done.
class TaskComponent < ViewComponent::Base
  include Motion::Component

  map_motion :do_something

  def do_something
    object.perform
  end

  def before_render
    Time.zone = current_user.zone
  end
end
  • Setting timezone on an ActionCable callback. Couldn't make this work.

I think what we're missing here is something akin to an around_ callback that we can hook into to set these kind of things. I'm thinking this could be added to process_motion but I'm not really familiar with the codebase so I'm open to ideas.

Reproduce the issue

I have created a demo repo here, it has a sqlite db so I think it should be ready to go. Else, just create a post. Then, click the "Update" button. You can see the timezone changing to UTC from Guadalajara on every re-render.

img

It doesn't seem that Motion supports passing in a form builder object.

I was trying to pass a form builder object into a ViewComponent that is using motion. Doing something like:

<%= form_with model: my_model, local: true do |form| %>
  <%= 
     render MyComponent.new(
       data: @data,
       form_object: form
     ) 
   %>
<% end %>

Is throwing Motion::UnrepresentableStateError.

This seems to be because Motion is trying to Marshal.dump the component state and that is conflicting with the form builder object.

If I can't pass the form builder object in this way, how would I be able to use Motion to render some code within the component such as: <%= form_object.fields_for :association %>? Is there a workaround to this or am I simply doing something wrong?

Consider changing the name?

Hello! Looks like you have a great project here. I'm looking forward to using it!

I just wanted to make you aware, if you didn't already know, that the name "motion" is already very popular in the Ruby world. There is a tool called RubyMotion and its command line tool is called motion and most of the gems related to RubyMotion are either prefixed with motion- (RubyGems search) and/or are under the Motion namespace.

I bring this up simply to help you to consider avoiding potential confusion or naming conflicts in the future. Keep up the great work!

using render? in component causes issues

Hey,

when using render? and it evaluates to false in a component the following error occurs on render undefined method `[]=' for nil:NilClass. It works fine in a normal view component.

Example:

class TestComponent < ViewComponent::Base
  include Motion::Component

  def initialize(test:)
    @test = test
  end

  def render?
    false
  end
end
<%= render(TestComponent.new(test: 'test')) %>
ActionView::Template::Error (undefined method `[]=' for **nil:NilClass):**

While I have the chance, I just wanna say thank you for building this. I'm just playing with it for now but it feels really good :).

Compatibility issue with Rails 6.1 ?

Hi, first of all thank you for your work on Motion, your gem is really helpful and it is a pleasure to use it.

I am using the 0.4.4 version of the gem and everything is fine on Rails 6.0.3.

I tried to upgrade to Rails 6.1, but now my components are no longer updated after an interaction and sometimes the component disappears from the page.

I have investigated few hours without success, there is no error message in the logs...

I was able to reproduce my problem on a cloned repository of motion-demos : https://github.com/mmagn/motion-demos/

Am I missing something?

Thanks for your help.

[Solved] Using Devise with motion

I'm trying Motion and View components for the first time, so I just copied and pasted a partial inside a component. This partial used inner partials that used Devise's current_user. When re-rendering it using Motion, it failed with:

21:00:24 web.1       |  Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
21:00:24 web.1       |  If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.

I see this is happening because current_user defined by Devise uses Warden's env.

I noticed you had a (really helpful!) custom proc on the config file so I fixed this by doing the following:

  config.renderer_for_connection_proc = ->(websocket_connection) do
    ActionController::Renderer::RACK_KEY_TRANSLATION['warden'] ||= 'warden'

    proxy = Warden::Proxy.new({}, Warden::Manager.new({})).tap do |proxy|
      proxy.set_user(
        websocket_connection.current_user,
        scope: :user,
        run_callbacks: false
      )
    end

    ApplicationController.renderer.new(
      websocket_connection.env.slice(
        Rack::HTTP_COOKIE,
        Rack::RACK_SESSION,
      ).merge('warden' => proxy)
    )
  end

I adapted the solution from here. I understand why I need it but not sure how this could be cleaned up a little so any pointers here are welcome.

It's worth mentioning that my ApplicationCable::Connection is identified_by :current_user. So that's why I could use websocket_connection.current_user.

My question is: Do you think this is something worth being added to the docs / wiki? If so, let me know and I'll be glad to open a PR. If not, maybe this issue would be enough for future Motion users.

Motion component is unable to have a handle to a form object

I'm seeing this error:

Some state prevented `ChildComponent` from being serialized into a string. Motion components must be serializable using `Marshal.dump`. Many types of objects are not serializable including procs, references to anonymous classes, and more. See the documentation for `Marshal.dump` for more information.

The specific error from `Marshal.dump` was: can't dump anonymous class #<Class:0x00007f8761603a70>

Hint: Ensure that any exotic state variables in `ChildComponent` are removed or replaced.

It would be nice if we could use rails form objects in motion components.

parent_component.html.erb

  <%= form_with model: @work do |f| %>
    Parent <%= @work.title %>
    <%= render ChildComponent.new(form: f, update: bind(:update_model)) %>
  <% end %>

parent_component.rb

class ParentComponent < ViewComponent::Base
  include Motion::Component

  attr_reader :work

  def initialize(work:)
    @work = work
  end

  def update_model(opts)
    @work.attributes = opts
    true
  end
end

child_component.html.erb

       <input id="title" data-motion="add" class="form-control">

child_component.rb

class ChildComponent < ViewComponent::Base
  include Motion::Component

  def initialize(form:, update:)
    @form = form
    @update = update
  end

  map_motion :add

  attr_reader :form

  def add(event)
    element = event.current_target
    @update.call({title: element.value})
  end
end

Custom element attributes are lost on component re-render

Hi! We're looking into using Motion, and noticed an issue when we were trying to render a Trix editor inside a motion component.

We've noticed that when using a custom element, upon re-render of the component, attributes on that component are lost. You can see a simple example if you had an element which was initialized this way:

class FooBarElement extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    const testElement = document.createElement('span');
    this.setAttribute('data-foo-bar', '123');
    this.shadowRoot.append(testElement);
  }
}

window.customElements.define('foo-bar', FooBarElement);

which is attached in a component:

.container
  %foo-bar{ data: { motion: 'click->update' } }

(with a component.rb file that has a mapped #update method)

Upon initial page load, the element looks like this:

<foo-bar data-motion="click->update" data-foo-bar="123"></foo-bar>

But after the element is clicked and the component is re-rendered:

<foo-bar data-motion="click->update"></foo-bar>

This is actually preventing us from being able to have a form with a rich text field using Actiontext/Trix editor since Trix uses custom elements to build the editor on render.
We were wondering if there was a missing step to get custom elements working or if that is a limitation/issue with motion at this time. Thanks so much for taking a look!

Domain lost after page change

When working with links that require the current domain, the domain is correctly loaded on the first load but will be replaced with http://example.org upon any page change.

This can be reproduced simply by adding <%= root_url %> to a page and using a periodic timer to re-render the page.

While absolute links are uncommon, they are used by ActiveStorage's direct upload. I have been able to avoid this issue by storing the URL to an instance variable in the controller and passing that value into the component so that it isn't being regenerated on page change.

Motion does not work on older IOS versions

Issue: Incompatibility on iOS: 12.4

Reproduction:
In a mac with Xcode, download the iOS 12.4 Simulator by going to Xcode -> Preferences -> Components.
Open the simulator. Xcode -> Open Developer Tool -> Simulator
Choose an iPad using iOs 12.4: File -> Open Simulator -> iOS 12.4 -> iPad 6th generation(I don't know that the type of device matters)
Open Safari and navigate to the Calculator Demo app, https://motion-demos.herokuapp.com/calculator
Notice that the buttons are non-responsive.

It appears to work on iOS: 13.0, so I'm not sure if this issue needs a fix, considering iOS 12.x is only receiving Security Updates.

`session`, `cookies`, and `current_user` are only accessible while rendering

Hey guys :)

If i understand this correctly then i need to uncomment this to get access to session / cookies?

Screenshot 2020-09-12 at 01 24 16

But no matter what, i cant seem to be able to access them. Get an error about undefined method 'controller'

Screenshot 2020-09-12 at 01 26 35

Am i doing something wrong or?

just trying to execute this bit of logic here, update Current user.

Screenshot 2020-09-12 at 01 27 58

Thanks

[Question] Preventing unauthorized access to a resource's stream

Normally with ActionCable, as I understand it, if you want to prevent unauthorized access to a stream when someone attempts to subscribe to it, you simply check if the user has access to that particular resource, and if so, explicitly call stream_for so that they can stream it.

For example:

class ChatMessageChannel < ApplicationCable::Channel
  def subscribed
    if chat = current_user.chats.find_by(id: params[:id])
      # current_user has access to the chat,
      # therefore we allow it to stream its data.
      stream_for chat
    else
      # current_user doesn't have access to the chat,
      # so we don't call `stream_for chat`, thus
      # preventing the user from accessing the stream.
    end
  end
end

I'd like to know whether the following accomplishes the same thing in Motion:

class ChatComponent < ApplicationComponent
  include Motion::Component

  def initialize(id:, current_user:)
    @messages = []

    if chat = current_user.chats.find_by(id: id)
      stream_for chat, :new_message
    end
  end

  def new_message(msg)
    @messages << msg
  end
end

With this in place, is there any way that a user who doesn't have access to the chat resource can gain access to the stream regardless? I assume not, but perhaps I'm missing something. Any insight is much appreciated.

Really appreciate this library, thanks for making it open source!

Add test helpers for generating events for testing

I find myself wanting to test a motion that pulls data from the event and I think it would be useful if the test helpers included some new stuff:

# current API
def run_motion(component, motion)
# proposed change
def run_motion(component, motion, event = motion_event)

# does not currently exist, build a fake (or real?) Motion::Event
def motion_event(attributes = {})
  Motion::Event.new(attributes)
end

This will be useful for when you take business logic based on what's in the event object (e.g. data attributes from the DOM).

[question] broadcast to multiple clients

In trying to define where to use Motion/streamline our application, we're trying to change our custom ActionCable adoption to a motion one. We've run into this possible out-of-scope use case:

I have a "slide" that gets updated centrally and is broadcasted from there to all clients in the "room". As of now, we render the html to string and broadcast it to the clients. We also cache the html to be able to render later (ex. for a client upon page reload).

I see three options to do the same with Motion, but each seemingly having severe drawbacks:

  • use motion to always read the cache key directly: that would mean we need to fetch the cache for each client when a new slide is rendered.
  • broadcast the rendered html: that would solve the cache fetching problem, but would put all the html in the state
  • keep the state small (something like a slide id for example) and rerender for each client: that would exhaust server resources

Is Motion just not the solution here? Or did I miss a solution that may perform better?

Component with collection not re-rendering when one element changes

Bug report

Given a component with a collection of objects, when you update one of them, the component is not rendered again.

Description

For example, given a post list component that renders posts:

class PostListComponent < ViewComponent::Base
  include Motion::Component
  attr_reader :posts

  def initialize(posts:)
    @posts = posts
  end

  map_motion :post_list_update

  def post_list_update(event)
    post = posts.find_by(id: event.target.data[:post_id])
    post.randomize_date_within!(5.days)

    # Only by changing posts in some way do we get a re-render, even though a
    # post was changed.
    #
    posts.each(&:reload)
  end
end
<div>
  <% posts.each do |post| %>
    <button data-motion="post_list_update" data-post-id="<%= post.id %>">PostListComponent#update</button>
    <%= render(PostComponent.new(post: post)) %>
  <% end %>
</div>

When you update one of those posts, the component will not reflect the update.

I'm just trying out View Component and Motion, so please let me know if this is the expected behavior and what the actual behavior should be. I'd be happy to document this.

Reproduce the issue

I have created a demo repo here, it has a sqlite db so I think it should be ready to go. Else, just create a post. Then:

  • Click the "PostListComponent#update" button. You can see how the component re-renders.
  • Uncomment the posts.each(&:reload) line in PostListComponent and see how the component won't re-render even though a post was changed.

img

[Question] What's the purpose of data-motion-key and -state?

I love the simplicity of this Gem, but I was surprised by the amount of data that is generated.
What's the exact purpose of the data-motion-key and data-motion-state attributes? I didn't find anything in the readme nor was I able to find a clear answer in the source code.

Intermittent exceptions when disconnecting a component and leaking memory

We started using ViewComponents in combination with Motion to render orders in our application. We now somehow have the feeling (by diving into it with get_process_mem and rack_mini_profiler) that the memory allocated by these components is not released when the page is closed, so that our Rails application keeps on growing for days, to eventually be thrown out by Heroku.

Question: can this indeed be the case? And if yes: what can we do to somehow trigger cleanup?

Add better support for custom logging

The only easy way I could figure out to customize the logger was to monkeypatch Motion::Channel#log_helper method. There should be an easy config to set the logger.

Slow application startup times in some projects

Hey, awesome work with motion. After spending so much time on React and the 'front end rendering revolution' i think it's about time for a 'back end rendering revolution' :)

Front end code via back end ruby, that's just pure magic :D

Anyway, I'm not sure if this is normal or something wrong with my setup, but it takes at least 5 minutes to start the rails server locally.

Before I installed motion it was about 5 seconds or so.

Wondering this is so long and if normal, will there be speed improvement in this in the future?

Thanks.

Updating a parent component within a child

Question

Side note: Sorry for opening an issue around this. Not sure if StackOverflow is monitored yet or what the best way to ask questions could be. Perhaps you could open a chat?

How would you re-render a component and the necessary children through a child action?

Description

Given a parent/child component like this:

class PostListComponent < ViewComponent::Base
  include Motion::Component
  attr_reader :posts

  def initialize(posts:)
    @posts = posts
  end
end
class PostComponent < ViewComponent::Base
  include Motion::Component

  attr_reader :post

  def initialize(post:)
    @post = post
  end

  map_motion :update

  def update
    post.randomize_date_within!(5.days)
  end
end
<!-- post_list_component.html.erb -->
<div>
  <% posts.each do |post| %>
    <button data-motion="post_list_update" data-post-id="<%= post.id %>">PostListComponent#update</button>
    <%= render(PostComponent.new(post: post)) %>
  <% end %>
</div>
<!-- post_component.html.erb -->
<div>
  <div><%= post.title %></div>
  <div><%= post.date %></div>
  <div><%= content_tag :button, 'PostComponent#update', data: { motion: 'update' } %></div>
</div>

How would you setup your Motion components so that clicking the child's PostComponent#update button would re-render it's parent and thus the necessary (changed) children?

You can imagine that, for example, the child's button is marking a todo as "completed" and the task list would refresh to show the count of completed todo's.

Reproduce the issue

I have created a demo repo here, it has a sqlite db so I think it should be ready to go. Else, just create a post. Clicking the PostComponent#update button should somehow trigger a PostListComponent re-render.

img

[Discussion] Debouncing input and the mystery of event targets

First, I'd like to say that this Gem is awesome. I built my first reactive view component yesterday with little issue. ๐Ÿ‘

Is there an easy way to debounce input events?

For example I have a motion event for search
<input class="input" type="text" placeholder="Search" data-motion="input->search">

Which works great but creates a flood of events whenever the user types.

Transpiled Event Serialization Issue

The serializeEventDetails function attempts to perform destructuring assignment on event objects:

const { key, keyCode, altKey, ctrlKey, metaKey, shiftKey } = event

However, object destructuring ends up using hasOwnProperty when transpiled, as can be seen here:

function serializeEventDetails(event) {
  var details = {};

  var _iterator = _createForOfIteratorHelper(detailProperties),
      _step;

  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var property = _step.value;

      if (Object.prototype.hasOwnProperty.call(event, property)) {
        details[property] = event[property];
      }
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }

  return details;
}

This is an issue since hasOwnProperty will always return false on Event objects.
See this StackOverflow for more details

const { key } = new KeyboardEvent('keydown', { key: 'test' })
key
// => 'test'

new KeyboardEvent('keydown', { key: 'test' }).hasOwnProperty('key')
// => false

I came across this while using keyboard events, it is not limited to only keyboard events.

Events capture

Hi,

First thanks for bringing reactivity to Rails with such a powerful simplicity. I have one question.
How would you trigger a JS callback after DOM update ?

Does Motion provides event capture such as StimulusReflex ?

<div data-action="cable-ready:after-morph@document->chat#scroll">

Or would you use Stimulus on top of Motion ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.