unabridged / motion Goto Github PK
View Code? Open in Web Editor NEWReactive frontend UI components for Rails in pure Ruby
Home Page: https://github.com/unabridged/motion
License: MIT License
Reactive frontend UI components for Rails in pure Ruby
Home Page: https://github.com/unabridged/motion
License: MIT License
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.
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.
TL;DR I think we're missing an around_ callback around rendering.
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:
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
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
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.
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.
Related to #75
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
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.
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!
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.
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 ?
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).
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!
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 :).
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?
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?
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.
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.
The serializeEventDetails
function attempts to perform destructuring assignment on event objects:
motion/javascript/serializeEvent.js
Line 28 in 7464c99
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.
https://motion-demos.herokuapp.com/
Heroku reports:
Application error
An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details. You can do this from the Heroku CLI with the command
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!
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?
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.
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:
Is Motion just not the solution here? Or did I miss a solution that may perform better?
Motion fails to serialize the component due to ViewComponent v2.35.0 changing some instance variable names:
ViewComponent/view_component#967
This means @controller
and @helpers
are now @__vc_controller
and @__vc_helpers
, which are not excluded here:
https://github.com/unabridged/motion/blob/v0.5.0/lib/motion/component/rendering.rb#L8-L29
As far as I can tell, this only happens when you have a form in the view component's HTML.
Given a component with a collection of objects, when you update one of them, the component is not rendered again.
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.
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:
posts.each(&:reload)
line in PostListComponent
and see how the component won't re-render even though a post was changed.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.
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.
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.
Hey guys :)
If i understand this correctly then i need to uncomment this to get access to session / cookies?
But no matter what, i cant seem to be able to access them. Get an error about undefined method 'controller'
Am i doing something wrong or?
just trying to execute this bit of logic here, update Current user.
Thanks
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.