EDIT: Since this writing, Lucky now has a component
method (built-in) you can use in your actions. No need to create a custom macro!
For one of my Lucky apps, I'm using Stimulus which is light-weight. I wanted to avoid doing a ton on the client side so that way I can keep all the nice benefits of Crystal and Lucky.
In a recent instance, I had to load some comments on to a page asynchronously. Normally in this case, you might write some javascript to make an api call, then gather all of your records in a giant JSON object. Then you'd probably iterate over the result, and create some sort of component. Maybe in Vue, or in React, and have those render each comment. With Stimulus, you don't have built-int templating. You're left with writing a lot of document.createElement
, and element.setAttribute
type stuff. Or you just use lots of innerHTML = ''
type calls.
What I wanted was to write my markup in Lucky, then make an API call that returns the markup, and I can just shove that in to an element and be done with it.
By default, the actions in Lucky want you to render an HTML Page. But I didn't want to make a blank layout just for these. So here's what I did:
# src/components/comments_list.cr
class CommentsList < BaseComponent
needs save_comment : SaveComment
needs comments : CommentQuery
# Renders the comments form, and each comment
def render
# This could also move to it's own component!
form_for Comments::Create do
textarea save_comment.body, class: "field"
submit "Post Comment"
end
div class: "comments" do
@comments.each do |comment|
mount Comment.new(comment)
end
end
end
end
Now I just need a handy helper macro in my ApiAction.
# src/actions/api_action.cr
abstract class ApiAction < Lucky::Action
accepted_formats [:json], default: :json
macro render_component(component_class, **assigns)
send_text_response({{ component_class }}.new(
{% for key, value in assigns %}
{{ key }}: {{ value }},
{% end %}
).render_to_string, "text/html")
end
end
Then I can use this new macro in my api actions!
# src/actions/api/comments/index.cr
class Api::Comments::Index < ApiAction
get "/api/comments" do
save_comment = SaveComment.new
comments = CommentQuery.all
render_component CommentList, save_comment: save_comment, comments: comments
end
end
Lastly, my javascript is pretty simple now.
import { Controller } from 'stimulus'
export default class extends Controller {
connect() {
fetch("/api/comments")
.then(res => res.text())
.then(html => element.innerHTML = html)
}
}
Thoughts
I know I left out several bits like, setting up stimulus, or what the main action / page look like, but all of those should be fairly straight forward. I'll probably do a writeup later on integrating stimulus with Lucky. Hopefully these examples should get you close should you need to use this!
Top comments (0)