Giter VIP home page Giter VIP logo

komponent's Introduction

Komponent

Tests GitHub release Maintainability Coveralls github

Komponent implements an opinionated way of organizing front-end code in Ruby on Rails, based on components.


👋 HEADS UP! The future of this gem is currently being discussed. Please have your say!


Each component has its own folder, containing a Ruby module, a partial, a stylesheet and a JavaScript file.

Komponent relies heavily on webpacker to manage dependencies and generate the production JS and CSS files.

This README examples are written in Slim, but Komponent is compatible with:

  • your preferred templating language (Slim, Haml, erb)
  • your stylesheet language of choice (Sass, SCSS, CSS, PostCSS)

This gem has been inspired by our Rails development practices at Ouvrages and Etamin Studio, and the (excellent) Modern Front-end in Rails article from Evil Martians.

Table of Contents

Compatibility

  • Ruby 2.5+
  • Rails 5.0+
  • Webpacker 3.0.0+

Getting started

# Gemfile

gem 'komponent'

Run the following command to set up your project instantly:

rails generate komponent:install

This command will:

  • check that the dependencies (currently, webpacker) are installed
  • rename the app/javascript folder to frontend and modify webpacker config accordingly
  • create the frontend/components folder where you will put your component
  • create the frontend/components/index.js file that will list your components and import it in frontend/packs/application.js

Usage

Generate a new component with the component generator:

rails generate component button

Then, render it in your views with the component helper (or its alias c).

/ app/views/pages/home.html.slim

= component 'button'
= c 'button'

Or, directly from your controllers:

# app/controllers/pages_controller.rb

def home
  render html: helpers.component('home')
end

Make sure to include javascript pack tag and stylesheet pack tag in your application layout file, for instance:

/ app/views/layouts/application.html.slim

doctype html
html
  head
    = stylesheet_pack_tag 'application'

  body
    = yield
    = javascript_pack_tag 'application'

Check Webpacker documentation for further information.

Passing variables

You can pass locals to the helper. They are accessible within the component partial, as instance variables. Additionally, the entire locals hash is made available through a properties helper method.

/ app/views/pages/home.html.slim

= component 'button', text: 'My button'
/ frontend/components/button/_button.html.slim

.button
  = @text

Passing options

Component caching

Komponent relies on Rails Low-level caching.

You can cache the component by passing the cached: true option. The cache will expire when the locals, options or block change. If you want better control of the cache expiration, you can provide a custom cache_key. When the cache_key changes, the cache will be cleared.

/ app/views/pages/home.html.slim

/ Cache the component based on its locals
= component "button", { text: 'Click here' }, cached: true

/ or cache the component with a specific key, such as the last update of a model
= component "button", { text: 'Click here' }, cached: true, cache_key: @product.updated_at

Passing a block

The component also accepts a block. To render the block, just use the standard yield.

/ app/views/pages/home.html.slim

= component 'button'
  span= 'My button'
/ frontend/components/button/_button.html.slim

.button
  = yield

You can check if the component has been called with a block using the block_given_to_component? helper from within the component.

Properties

Each component comes with a Ruby module. You can use it to set properties:

# frontend/components/button/button_component.rb

module ButtonComponent
  extend ComponentHelper
  
  property :href, required: true
  property :text, default: 'My button'
end
/ frontend/components/button/_button.html.slim

a.button(href=@href)
  = @text

Helpers

If your partial becomes too complex and you want to extract logic from it, you may want to define custom helpers in the ButtonComponent module:

# frontend/components/button/button_component.rb

module ButtonComponent
  extend ComponentHelper
  
  property :href, required: true
  property :text, default: 'My button'

  def external_link?
    @href.starts_with? 'http'
  end
end
/ frontend/components/button/_button.html.slim

a.button(href=@href)
  = @text
  = ' (external link)' if external_link?
/ app/views/pages/home.html.slim

= component "button", text: "My button", href: "http://github.com"

Component partials

You can also choose to split your component into partials. In this case, we can use the default render helper to render a partial, stored inside the component directory.

/ frontend/components/button/_button.html.slim

a.button(href=@href)
  = @text
  - if external_link?
    = render 'suffix', text: 'external link'
/ frontend/components/button/_suffix.html.slim

= " (#{text})"

Namespacing components

To organize different types of components, you can group them in namespaces when you use the generator:

rails generate component admin/header

This will create the component in an admin folder, and name its Ruby module AdminHeaderComponent.

Stimulus integration

Komponent supports Stimulus >= 1.0.

You can pass --stimulus to both generators to use Stimulus in your components.

rails generate komponent:install --stimulus

This will yarn add stimulus and create a stimulus_application.js in the frontend folder.

rails generate component button --stimulus

This will create a component with an additional button_controller.js file, and define a data-controller in the generated view.

Internationalization

In case your component will contain text strings you want to localize, you can pass the --locale option to generate localization files in your component directory.

rails generate component button --locale

This will create a yml file for each locale (using I18n.available_locales). In your component, the t helper will use the same "lazy" lookup as Rails.

/ frontend/components/button/_button.html.slim

= a.button(href=@href)
  = @text
  = render('suffix', text: t(".external_link")) if external_link?
# frontend/components/button/button.en.yml

en:
  button_component:
    external_link: external link
# frontend/components/button/button.fr.yml

fr:
  button_component:
    external_link: lien externe

Available locales configuration

You can whitelist the locales you use by setting this into an initializer, as explained in the "official guide":

I18n.available_locales = [:en, :fr]

If you have the rails-i18n gem in your Gemfile, you should whitelist locales to prevent creating a lot of locale files when you generate a new component.

Styleguide

Komponent includes a basic styleguide engine that you can use in your project to document your components.

Komponent styleguide UI

To set it up, you can use the generator:

rails generate komponent:styleguide

This command will:

  • copy the styleguide components (komponent/container, komponent/footer, komponent/header and komponent/sidebar) to your components folder, so you can customize them
  • add a new komponent.js pack to your packs folder
  • mount the engine in your routes

Then, for each component, you can describe it and render examples for each state in the _example.html.slim file from the component folder. The engine will then render it on the component page.

If you have existing components, you can generate all their example files at once with:

rails generate komponent:examples

Finally, visit http://localhost:3000/styleguide to access your styleguide.

Configuration

Change default root path

You can change the default root path (frontend) to another path where Komponent should be installed and components generated. You need to change komponent.root in an initializer.

Rails.application.config.komponent.root = Rails.root.join('app/frontend')

Default options for the generators

You can configure the generators in an initializer or in application.rb, so you don't have to add --locale and/or --stimulus flags every time you generate a fresh component.

config.generators do |g|
  g.komponent stimulus: true, locale: true # both are false by default
end

Change default stylesheet engine

You can configure the stylesheet engine used for generate stylesheet file, allowed values are :css, :scss, :sass.

Rails.application.config.komponent.stylesheet_engine = :css # default value is :css

Force default templating engine

If for some reason your preferred templating engine is not detected by Komponent, you can force it by manually defining it in your config:

Rails.application.config.generators.template_engine = :haml

Additional paths

You may want to use components in a gem, or a Rails engine, and expose them to the main app. In order to do that, you just have to configure the paths where Komponent will look for components.

From a gem:

module MyGem
  class Railtie < Rails::Railtie
    config.after_initialize do |app|
      app.config.komponent.component_paths.append(MyGem.root.join("frontend/components"))
    end

    initializer "my_gem.action_dispatch" do |app|
      ActiveSupport.on_load :action_controller do
        ActionController::Base.prepend_view_path MyGem.root.join("frontend")
      end
    end

    initializer 'my_gem.autoload', before: :set_autoload_paths do |app|
      app.config.autoload_paths << MyGem.root.join("frontend")
    end
  end

  private

  def self.root
    Pathname.new(File.dirname(__dir__))
  end
end

or from an engine:

module MyEngine
  class Engine < Rails::Engine
    isolate_namespace MyEngine

    config.after_initialize do |app|
      app.config.komponent.component_paths.append(MyEngine::Engine.root.join("frontend/components"))
    end

    initializer 'my_engine.action_dispatch' do |app|
      ActiveSupport.on_load :action_controller do
        ActionController::Base.prepend_view_path MyEngine::Engine.root.join("frontend")
      end
    end

    initializer 'my_engine.autoload', before: :set_autoload_paths do |app|
      app.config.autoload_paths << MyEngine::Engine.root.join('frontend')
    end
  end
end

Make sure you add komponent to the runtime dependencies in your gemspec.

In order to compile packs from engine, and to use javascript_pack_tag 'engine', you need to:

  • Create a pack file in main app
// frontend/packs/engine.js

import 'packs/engine';
  • Append engine frontend folder to resolved_paths in config/webpacker.yml from your main app
resolved_paths:
  - engine/frontend

Running tests

Run all Cucumber features and unit tests with bundle exec appraisal rake test

Run the full test matrix with bundle exec appraisal rake test

Contributing

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

Releasing a new version

  • Update the CHANGELOG (add a title and a date for the new version)
  • Update the version number in lib/komponent/version
  • Install the gem-release gem if you haven't already
  • Run gem release --tag --push
  • Create or update the release on Github with the same version number and copy-paste the description from the CHANGELOG

Please note:

If you're releasing a patch version (eg. from 2.0.1 to 2.0.2) you can run gem bump patch --release --tag --push --sign so you don't have to manually change the version number.

If you want to release a specific version (eg. beta, RC...), you can run gem bump 3.0.0.beta1 --release --tag --push --sign

License

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

komponent's People

Contributors

alecrust avatar andrzejsliwa avatar apauly avatar descovi avatar florentferry avatar gkemmey avatar mittchobaroco avatar nicolas-brousse avatar olimart avatar otimo avatar robink avatar sigmike avatar spone avatar stevschmid 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  avatar  avatar  avatar  avatar

komponent's Issues

Rubocop not target the right ruby version

Since I changed the gemspec to require ruby version 2.2 and the travis matrix targeting 2.2 until ruby-head, the rubocop configuration no longer targeting the right version. We need to change RubyTargetVersion option of rubocop from 2.4 to 2.2.

Wrong import css in component generator

When we use sass or scss stylesheet engine, and we generate component rails g component button, the import './button.css' is always asking css file then than in folder we have scss or sass file.

We have to change that line in both templates/js.erb and templates/stimulus_controller_js.erb.

import "./<%= name_with_namespace %>.css";

to:

import "./<%= name_with_namespace %>.<%= stylesheet_engine %>";

We have to update features in features/component_generator/stylesheet_engine.feature for all scenarios:

    Then the file named "awesome_button/awesome_button.js" should contain:
    """
    import "./awesome_button.[stylesheet_engine]";
    """

And update features/component_generator/stimulus.feature with a check to scss at least:

    Then the file named "awesome_button/awesome_button.js" should contain:
    """
    import "./awesome_button.scss";
    """
    Then the file named "awesome_button/awesome_button_controller.js" should contain:
    """
    import "./awesome_button.scss";
    """

Sass & HAML

Would it be possible to just set the template language in an initializer? I use the hamlit gem (not haml-rails or something) but now komponent generates .erb files.

And for sass/scss, could it be possible to import one index file with all sass component partials? Since variables can't be used globally when each file is imported by the javascript.

Gem example

Do you have a working example of a gem that leverages Komponent ready for a Rails app.?

How access the given block inside the helper

I'm looking for using the given block to my component inside the helper. But I didn't found how to retrieve the block.

I used block_given_to_component? and that's works, but if I use @block_given_to_component.call but it returns me the content of my parent component.

Is it a missing feature? Or a mistake of usage?

I do some test by extended the ComponentHelper module like following:

module ComponentHelper
  def yield_or_property(property_name)
    if block_given_to_component?
      block_given_to_component
    elsif properties.key? property_name
      properties[property_name]
    else
      raise ArgumentError, "Missing required block component or parameter: #{property_name}"
    end
  end

  def property_or_yield(property_name)
    if properties.key? property_name
      properties[property_name]
    elsif block_given_to_component?
      block_given_to_component
    else
      raise ArgumentError, "Missing required block component or parameter: #{property_name}"
    end
  end

  private

  def block_given_to_component
    @_block_given_to_component = @block_given_to_component.yield if block_given_to_component?
  end
end

And by using this method yield_or_property(:text) into the components views.

But it returns me a LocalJumpError.

Standardize the component names

I can currently pass AwesomeButton, awesome_button or awesome-button to the generator, and create 3 different components.

We should probably enforce a specific case for component naming.

I think Rails partial files expect _awesome_button.html.slim

On the contrary, I usually name CSS classes with dashes: .awesome-button

Maybe stick to an existing convention? BEM, SmaCSS...

Add an example of generic javascript files

In the documentation, we don't have an example on how to handle generic javascript file, only javascript file specific to component. We need to show where to set that kind of files in our component-based hierarchy, in frontend ?

A lot of people shoulda use turbolinks or rails-ujs so let's show how to handle that kind of modules in our structure.

Generate scss files without sass-rails

Hi there!

In #28 you've added generation of scss files by using to the Rails.application.config.generators.stylesheet_engine variable.

I think (correct me if I'm wrong!) this only works because a default Rails app comes with sass-rails. When using webpacker + komponent this library seems obsolete as webpacker will compile my scss into css and the whole assets pipeline is skipped.

Without sass-rails however Rails will not generate scss files, even if the generator is correctly set, i.e. Rails.application.config.generators.stylesheet_engine = :sass

Wouldn't it be better to also give the possibility to generate .scss files in a different way? Or would this be an issue that Rails itself has to solve? I.e. generators should be able to generate scss files without sass-rails if there is a proper loader installed for Webpack?

Having components present on bin/rails stats command

I guess it could be nice to have stats who come from components into bin/rails stats command.

https://github.com/rails/rails/blob/20c91119903f70eb19aed33fe78417789dbf070f/railties/lib/rails/tasks/statistics.rake

This is how rspec-rails do it.
https://github.com/rspec/rspec-rails/blob/e8054a1cd03044f725030fe8315952cf3799a395/lib/rspec/rails/tasks/rspec.rake#L8

May be we could have something similar to push components in Rails stats.

What do you think about?

Generate component when no available locale is defined

When the available_locales config key is not defined and you run rails g component button --locales, by default Rails considers that all locales are available and the generator creates dozens of files.

How can we handle this graciously? Maybe a friendly reminder to define the available_locales before running the generator? But how can we determine if it has been defined or not?

Configuration via initializer and components path

Found this gem through the Evil Martians blog, nice idea to make a gem out of it.

How does the configuration via initializer work exactly, like so?

Komponent.configure = {
  stimulus: true,
  locale: true
}

Also is it possible to change the default component path from frontend to say app/frontend?

Component generator error with single quotes string

On a project we use single quotes on js files. So our frontend/components/index.js has list of components with single quotes.

The problem with when we generate a new component, the import_to_packs method seems to remove all components import and adding the new one.

- import "components/alert/alert";
- import "components/button/button";
- import "components/container/container";
- import "components/footer/footer";
- import "components/navigation/navigation";
- import "components/pagination/pagination";
+ import "components/test/test";

Not sure what could be done. May be using the Komponent::Component.all method a created on #95, could help us to do the import list directly by getting the list of present components.

Problem with haml generated.

Hi! Thanks for your great work and ideas.

When I generate a component with haml i get this

schermata 2018-07-20 alle 12 45 36

When with haml can be enough

.print-btn

or (if you want to be more verbose)

div{class: 'print-btn'}

encapsulation problem

Hi

I see small problem with encapsulation, original idea was based on passing only locals,
but you are converting them on the fly to instance variables so there is couple issues with it:

  • I have global scope, which is breaking encapsulation (I can call @var from child component), which is breaking the original concept of component isolation
  • I can overwrite instance variables passed from controller, by passing them with same name, on any level to any component

If i got it correctly you did it to have ability of defining helpers methods with same name in component module.
I would go back to using locals, and wrap them not with modules but with instances of class (similar to decorators/presenters). You can keep properties helper on class level, which will lets you generate default constructor with kwargs (with base object + properties, then you can make properties to be instance variables in such auto-constructor).

What do you think about it?

Make install generator idempotent

I should be able to run the rails generate komponent:install command several times, for instance if I want to run it again with other options.

My use case was that I wanted to setup Stimulus after running the install.

Dot prefix for locales

With this change the locale prefix becomes longer. It's good because it prevents conflicts but it makes the prefix even longer, so more painful to use.

I think we should :

  1. either override translate (and t) to make keys starting with a dot look into the component locales (like rails does)
  2. or use the Rails "dot look up", i.e. generate a locale file with a default hierarchy including the full namespace, for example for an "admin/button" component: <locale>.components.admin.button.admin_button (to match the partial path).

I think 1. is better because the hierarchy is simpler and if you refactor your component by moving things into partial you don't have to change your keys.

throw an exception when false or nil is passed into a component property

Expected Behavior

When passing a false or nil it should not crash

Actual Behavior

throwing a NameError: undefined local variable or method options' for`

Steps to Reproduce the Problem

passing a false or nil on any value of a component like this <%= c "test", value: false %>

Context

I have a component that's taking in a boolean and display a check mark or a cross depending if it's a true or nil

Specifications

Ruby version: 2.5.0
Rails version: 5.2.0.rc1
Komponent version: 1.1.1
Platform: Mac OS 10.13.2

Style not being loaded

Salut,
très bonne idée ce gem.

J'ai suivi l'exemple du readme avec Stimulus mais il semble que le CSS dans le fichier button.scss ne soit pas chargé/compilé.

J'ai une application de test à disposition si besoin.

Stimulus integration

We need an option to integrate stimulus into Komponent.

Installer generator changes

Append some extra lines to frontend/packs/application.js and run yarn add stimulus.

import {Application} from 'stimulus';
import {autoload} from 'stimulus/webpack-helpers';

const application = Application.start();
const controllers = require.context('../components', true, /\.js$/);
autoload(controllers, application);

Component generator changes

Due to autoloading from stimulus, import in frontend/components/index.js is unecessary, and naming convention require to change JS filename from xxx.js to xxx_controller.js.

We have the need to append some extra lines to the JS file too.

import { Controller } from "stimulus"
export default class extends Controller {
...
}

Naming changes

https://github.com/stimulusjs/stimulus/blob/aa075344f2b6806124c560bee67485382bb07007/packages/%40stimulus/webpack-helpers/index.ts#L28.

frontend/components/admin/notice/_admin_notice.html.slim
frontend/components/admin/notice/admin_notice_component.rb
frontend/components/admin/notice/admin_notice.css
frontend/components/admin/notice/admin_notice.js

#18

Generator to rename a component

It would be great to have a generator to rename a component:

  • change the folder name
  • change the file names
  • replace occurrences of the old name with the new name in all the files

block_given? from component module

When calling block_given? from a method in a component module, it returns false, even if the component is called with a block.

If I call block_given? from the partial, it works as expected.

Is there a way to improve this?

Module:

module TestComponent
  extend ComponentHelper

  def modifiers
    @modifiers ||= ""
    @modifiers += " has-block" if block_given? # this doesn't work
    @modifiers
  end

Partial:

.test(class=modifiers)
  - if block_given?
    .test-content= yield

Rendering the component with a block:

= c "test"
  p This is a test

Component helper methods aren't included in remote renders

A component that uses a helper method defined in its associated helper module returns a NoMethodError, when a controller action is called via ajax with a remote: true call.

# /components/foo_component/foo_component.html.haml 
=button_to foo_helper("foo"), foo_path, remote: true
# /components/foo_component/foo_component.rb
Module FooComponent
    extend ComponentHelper
    def foo_helper(text)
       return text
    end
end
# /app/controllers/foos_controller.rbs
...
def foo
    render partial: 'components/foo_component/foo_component'
end

If the helper method is included in the app/helpers/foo.rb it works just fine.

App is on komponent 1.1.4 & Rails 5.1.

component helper using relative paths

Hi, first of all congratulations for the job done, I really enjoy working with the gem. I was wondering if are there any plans to incorporate the possibility of rendering components using a relative path.
I know that's not railish enough, but I think its kind of a elementary feature for frontend frameworks, most of all, due to rehusability.

A case example would be a component which needs two children components. There should not be necessary to write the full path of the component, since both child components have no sense alone.

A solution, in order to avoid simulating the javascript imports (like './component_name'), would be to start the lookup of a component from the current directory. So in this example:

one_component/
  _one_component.html.erb
  other_component/
    _other_component.html.erb
other_component/
  _other_component.html.erb

Calling

component('other_component')

from _one_component.html.erb would consider first the partial under one_component/other_component/_other_component.html.erb

Could you please let me know if that makes sense for you, or if you think that it is not a necessary feature? In any case, I could also work on it.

Thanks

Can't access content_for defined in component

I'm trying to set a content_for in a component, and then access it in my layout:

/ frontend/components/admin/form/items/_items.html.slim

.admin-form-items
  / [...]
  - content_for :after_wrapper do
    .test Hello World
/ app/views/layouts/admin.html.slim

doctype html
html
  head
    / [...]

  body.is-admin
    = c "admin/header"
    = yield
    = yield :after_wrapper

But nothing gets yielded in the layout. Any idea?

application.scss silently breaks javascript

versions & Setup

pachages.json

"dependencies": {
    "@rails/webpacker": "^3.2.2",
    "rails-ujs": "^5.1.5",
    "stimulus": "^1.0.1"
  },
  "devDependencies": {
    "webpack-dev-server": "^2.11.1"
  }

Gemfile.lock

komponent (1.1.3)
      webpacker (>= 3.0.0)

application.html.erb

<%= stylesheet_pack_tag    'application' %>

frontent/packs/application.js

import "rails-ujs";
import 'components';
console.log("Hello world");

Steps to Reproduce:

A)

  1. Create an empty file: frontent/packs/application.scss
  2. Launch servers

Expected: "Hello World" to be displayed in console
Actual: No "Hello World" message even tho compilation finishes with no error.

B)

  1. Rename file frontent/packs/application.scss to frontent/packs/application.css
  2. Launch servers

Expected: "Hello World" to be displayed in console
Actual: Success!

Note

webpack-dev-server has to be restarted between changes to see the issue.

Return values from component with yield

I'm trying to create a component to have a form container. And so use yield to pass the form helper to the view from the component.
So I did the following.

# app/views/post/_form.html.slim

= c "form", model: @post, local: true do |f|
  div
    = f.label :name
    = f.text_field :name
# frontend/components/form/_form.html.slim

= form_with(model: @model, local: @local) do |form|
  .form(data-controller="form")
    = yield(form)

But I got this error : ActionView::Template::Error (undefined method label' for nil:NilClass)`.

I tried to print the content of form or f. I have the form helper object inside the container, but in the view f is nil.

Does the block inside the view is rendered before the component?

how to import styles using an engine

Hello,

I'm trying import styles from components that are hosted in an engine. I followed the directions for using engine.js, but receiving a webpacker can't find engine.css in /Users/jonathanbernesser/native/kompsetup/public/packs/manifest.json when doing the same instructions for stylesheet pack_tag.

If i create an engine.scssfile next to the engine.js file, and import it into the engine.js, the error disappears, but no styles from the engine are compiled. Does anyone have an example setup to make this a reality?

Thanks!

Caching implementation

I'm wondering how we could implement fragment caching for the components.

(First, if you're not familiar with the topic, I suggest you read the Rails guide about caching)

Actually, we can currently wrap manually the whole component partial in a cache helper, in order to cache it. But maybe Komponent should have a built-in way to do that.

I think we could have an API similar to the render partial one, where you can pass cached: true. Something like: = component "button", cached: true
It would then rely on Rails to do the work.

The main question is: how can we define the key for the cache entry? It could be a method within the Ruby module of the component (cache_key?)

Feel free to comment with your ideas below!

Rendering partial into component

Problem

We need the ability to render partial inside a component. If that partial is inside the component, we need a way to prevent to give full path.

Example:

  components/
    button/
       _button.html.slim
       _icon.html.slim
/ frontend/components/button/_button.html.slim

= render("components/button/icon")

Solution 1


Add components folder and all subfolders to lookup. But the drawback is the addition of a lot of directories to lookup.

ActionController::Base.prepend_vew_path Dir["#{app.config.root}/frontend/**/*"]
/ frontend/components/button/_button.html.slim

= render("button/icon")

Solution 2


Add a render_partial helper to render partial inside component. We inject current component path to lookup in order to render the component, and restore default behavior after rendering.

def render_partial(partial_name, locals = {}, &block)
  benchmark("Rendered partial #{partial_name}") do
    context = controller.view_context
    view_paths = context.lookup_context.view_paths.dup
    components_path = Rails.root.join "frontend/components"

    capture_block = proc { capture(&block) } if block

    current_dir = Pathname.new(@virtual_path).dirname

    context.lookup_context.prefixes.prepend current_dir
    context.lookup_context.view_paths.unshift components_path

    partial_rendered = capture do 
      context.render partial_name, locals, &capture_block
    end

    context.lookup_context.prefixes.delete current_dir
    context.lookup_context.view_paths = view_paths

    partial_rendered
  end
end
/ frontend/components/button/_button.html.slim

= render_partial("icon")

Solution 3


Allow to have nested components inside component.

  components/
    button/
      _button
      ...
      title/
        _title
        ...

Can't append to inexistant file

Running generator fails due to inexistant file (append frontend/components/index.js).

We need to provide some extra informations in readme.

mv app/javascript ./frontend
mkdir frontend/components
touch frontend/components/index.js

# frontend/packs/application.js
import '../components/index'

Unable to use component helper

As per the README, component helper module should(?) be wired up to the component 'view' right out of the box:

Helpers

If your partial becomes too complex and you want to extract logic from it, you may want to define custom helpers in the ButtonComponent module:

# frontend/components/button/button_component.rb

module ButtonComponent
  property :href, required: true
  property :text, default: "My button"

  def external_link?
    @href.starts_with? "http"
  end
end

However, none of the methods defined on my helpers are accessible from the component 'view':

#frontend/components/direct_attachment_field/direct_attachment_field_component.rb
module DirectAttachmentFieldComponent
  extend ComponentHelper

  def something
    raw('something')
  end
end
#frontend/components/direct_attachment_field/_direct_attachment_field-html.erb
<div data-controller="direct-attachment-field" data-target="report-form.fileFieldGroup" data-index="0" class="direct-attachment-field input-group">
  <%= @attacheable.file_field :document, direct_upload: true,
                              class: 'form-control-file',
                              data: { action: 'report-form#loadFile', target: 'report-form.fileField'} %>
  <label class="file-field-label">Seleccionar archivo...</label>
  <%= something %>
</div>

Always returns:
undefined local variable or method 'something' for #<#<Class:0x007fab224dbca8>:0x007fab21b09b58>

Is there any extra configuration I might be missing or it is not behaving as expected?

Thanks

import to namespace get subtracted even if there is other component

Expected Behavior

When I remove a component on a namespace using the rails destroy command, the import does not get remove from components/index.js.

Actual Behavior

When I am using rails d component admin/test import "components/admin"; get subtracted even if there is more than one component into the admin folder.

Steps to Reproduce the Problem

  • rails generate component admin/test1
  • rails generate component admin/test2
  • rails destroy component admin/test2

Specifications

  • Ruby version: 2.5.0
  • Rails version: 5.2.0.rc1
  • Komponent version: 1.1.1
  • Platform: Mac OS 10.13.2

Style guide view

Do you think the gem could contain a style guide generator like mountain_view does? (Or kss)

Could be nice to have this kind of page, in development environment, that list all the components with some documentation and example(s).

Add a generator for namespaced components

rails g component admin/header should create a header component in frontent/components/admin/header

Open to discussion:

  • how should we name the Ruby module? AdminHeaderComponent? Admin::HeaderComponent?
  • how should we name the CSS class? admin_header?
  • how should we name the CSS and JS files? admin_header.js|css? just header.js|css?

Create a file for (S)CSS variables on install

When we run rails g komponent:install, let's create a file in frontend/base/variables.(s)css

When we generate a component, we should @import the variables file by default in the generated (S)CSS file. Make sure that the path is right (especially for nested components).

Or maybe we could add some configuration to be able to always import with @import "base/variables";
It's possible with https://github.com/postcss/postcss-import but we have to check with Sass.

Add a method to return the list of components

If I want to create a styleguide / pattern library in my app, I'll need access to the list of available components.

I think Komponent should make a helper available to my views and/or add a class that I can use in my controllers.

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.