jensljungblad / elemental_components Goto Github PK
View Code? Open in Web Editor NEWSimple view components for Rails 5.1+
License: MIT License
Simple view components for Rails 5.1+
License: MIT License
This issue outlines some problems about how we currently define our components. It also compares this to how it is usually done in React.
<div class="card card--success">
<div class="card__header card__header--large">
Title of card
</div>
<div class="card__section">
<h5 class="card__title">Title of section</h5>
<p class="card__text">Lorem</p>
</div>
<div class="card__section card__section--large">
<p class="card__text">Lorem</p>
</div>
<div class="card__section">
<p class="card__text">Lorem</p>
</div>
<div class="card__footer">
Footer of card
</div>
</div>
<Card color="success">
<CardHeader size="large">Title of card</CardHeader>
<CardSection>
<CardTitle>Title of section</CardTitle>
<CardText>Lorem</CardText>
</CardSection>
<CardSection size="large">
</CardSection>
...
</Card>
Number of components: 5, of which 4 "private".
Problems (also occurs when you don't use components):
Card
component.Footer
above Header
etc.class CardComponent
attribute :color, Components::Types::Enum
attribute :header, Components::Types::String
attribute :sections, Components::Types::Array
attribute :footer, Components::Types::String
end
<%= component :card, color: :success do |attrs| %>
<% attrs[:header] = capture do %>
Title of card
<% end %>
<% attrs[:sections] << capture do %>
<h5 class="card__title">Title of section</h5>
<p class="card__text">Lorem</p>
<% end %>
<% attrs[:sections] << capture do %>
<p class="card__text">Lorem</p>
<% end %>
<% attrs[:footer] = capture do %>
Footer of card
<% end %>
<% end %>
<div class="card card--<%= color %>">
<div class="card__header">
<%= header %>
</div>
</div>
Our current problems:
card__header
, card__section
etc. In fact, these are typed as strings.card__title
, card__text
I think the only reason for the delegation craziness when rendering is because of helpers. Attributes and elements could have their values passed to the template as locals, no need for them to be functions. Only helpers need that. But if helpers are getters only, and calculated at render time, there is no need for them to be passed as functions either.
Without thinking further, either there could be a DSL, or perhaps even simpler:
class FooComponent < Component
attribute :id
attribute :data
has_one :header
has_one :footer
def css_classes
...
end
protected
def locals
super.merge(
css_classes: css_classes
)
end
end
Currently the whole style guide page, including examples are run in an isolated engine, using the Components::PagesController
. In order for the examples to be able to access the application's helpers and routes, it does the following:
It would probably be better if the actual example (more specifically, the contents of the iframe) could be executed in the main application's context. That would mean that the component would also have to be rendered within the iframe, instead of being injected into it. Sounds complicated...
It also causes some kind of issue with the layout, forcing us to specify the layout explicitly in the very same PagesController
.
Attributes and elements should probably override existing methods on the view, if we choose to mix them in (delegate) as we do now. There are a lot of methods on the view, so there are a lot of "reserved" attribute names now. Normal helpers do override existing methods, so it seems reasonable.
Seems like the github wokflow build action is not completing properly. PR incoming ;)
I see you've made some updates recently! That's awesome!
Unfortunately the project I was on got scrapped so even though we heavily used Elemental and ViewComponent, we're not using it anymore.
I have another project I wanted to play with, but wanted to start with a fresh Rails 6 install which uses Webpacker by default.
Have you gotten it working with Webpacker instead of Sprockets?
Currently the content of the block of a component or element is rendered using to_s
, like this:
<div class="alert">
<%= alert %>
</div>
<%= component "alert" do %>
Hello
<% end %>
Upsides:
Downsides:
Something like block
, or content
.
Upsides:
Downsides:
content_for
Rails has a content_for
(and a content_for?
) helper that perhaps could be leveraged:
<div class="alert">
<%= content_for(alert) %>
</div>
<%= component "alert" do %>
Hello
<% end %>
class Element
def initialize
…
@view.content_for(self, &block)
end
end
Upsides:
Downsides:
This is something I tried out but later discarded because it felt like I was over-engineering things, and muddied the concepts. Basically the user could choose an attribute name for the block to be captured to, and then you could either populate it like an attribute or by passing a block. Something like:
<div class="alert">
<%= alert.message %>
</div>
<%= component "alert" do %>
Hello
<% end %>
class AlertComponent < Components::Component
attribute :message
capture_block_to :message
end
Upsides:
Downsides
This is a suggestion for a slightly different API that would allow 1) capture HTML even when you have no elements and b) support any number of nested elements.
<%= component "card" do %>
foo
<% end %>
component.value == "foo"
<%= component "card" do |c| %>
<% c.header "foo" %>
<% end %>
component.value == ""
component.header.value == "foo"
<%= component "card" do |c| %>
<% c.header do |cc| %>
<% cc.left "foo" %>
<% end %>
<% end %>
component.value == ""
component.header.value == ""
component.header.left.value == "foo"
<%= component "card" do |c| %>
<% c.header do |cc| %>
<% cc.left "foo" %>
bar
<% end %>
<% end %>
component.value == ""
component.header.value == "bar"
component.header.left.value == "foo"
Check this out: rails/rails#36388
The element (and component) value might as well be treated as an attribute:
class Element
include Attributes
include Elements
attribute :value
end
This way the value
method becomes less magical. This would also mean we don't have to check value and block args and switch the position of value and attributes. The block should simply always override the value, if set as an argument. It also makes it possible to pass a value to the component itself, which is not possible right now.
<% # sets value to foo %>
<%= component value: "foo" %>
<% # sets value to foo, then sets it again to bar %>
<%= component value: "foo" do %>
bar
<% end %>
Should also document better that <%= element.value %>
is the same as <%= element %>
.
Sometimes the JS based iframe height detection fails, which means the example isn't shown. Also the whole detection thing is pretty slow.
Since the name "components" is already used/blocked on rubygems we should probably find a new name, rename the project/files and work towards an offical release on rubygems?
@jensljungblad do you have any names on your mind already?
It would be nice to store component specific locales in the components directory (with
the components CSS and JS files) and enhance I18ns lazy lookup to support a "components" namespace.
Extending the i18n load path can be quite easy:
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('app', 'components', '**', '*.yml')]
I have not yet dug up how to define a "components" namespace for the lazy lookup.
Probably it would be nice to have a hierarchy like this:
# components/alert/alert_en.yml
en:
components:
alert:
title: 'foo'
I tried giving this (seemingly very nice) gem a spin but the component generation command failed since we don't use the asset pipeline (anymore, go webpack!). Are there any other parts of components that are dependent on the asset pipeline or is it just the install command?
In case you're wondering, this is where it failed: https://github.com/jensljungblad/components/blob/master/lib/components/engine.rb#L6
As a side note: what are your plans for this project? Is it considered "finished" and will mostly just receive minimal maintenance, or do you have any immediate plans for new features/more compatibility etc.?
Other than that it looks well-made! :)
Hi there!
My company is looking at potentially using components
for a large project and we were wondering if you would be open to Pull Requests from us and if you had the time to maintain the project.
If so, then we'll start sending them your way as soon as we find things we'd like to add/change.
If not, would you be willing to add some of our developers as maintainers? Or would you prefer if we just did a fork?
We're still debating what framework we will use, but I wanted to start the conversation now so we have some knowledge ahead of time.
Thanks so much!
The README describes that elements can also be initialized via the components attributes Hash.
Seems like this feature is not working properly (or missing?).
see README
Another good use case is a navigation component:
# app/components/navigation_component.rb %>
class NavigationComponent < ElementalComponents::Component
element :items, multiple: true do
attribute :label
attribute :url
attribute :active, default: false
end
end
An alternative here is to pass a data structure to the component as an attribute, if no HTML needs to be injected when rendering the component:
<%= component "navigation", items: items %>
I already prepared a PR to make it work - or am I overseening sth. here?
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.