phlex 0.3.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of phlex might be problematic. Click here for more details.

Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +21 -5
  4. data/Gemfile +26 -12
  5. data/Procfile.dev +3 -0
  6. data/README.md +1 -1
  7. data/Rakefile +3 -5
  8. data/SECURITY.md +1 -1
  9. data/bench.rb +7 -0
  10. data/config/sus.rb +15 -0
  11. data/docs/assets/application.css +6 -0
  12. data/docs/build.rb +17 -10
  13. data/docs/components/callout.rb +1 -1
  14. data/docs/components/code_block.rb +2 -2
  15. data/docs/components/code_span.rb +9 -0
  16. data/docs/components/example.rb +5 -5
  17. data/docs/components/heading.rb +2 -2
  18. data/docs/components/layout.rb +62 -17
  19. data/docs/components/markdown.rb +14 -15
  20. data/docs/components/nav/item.rb +33 -0
  21. data/docs/components/nav.rb +6 -0
  22. data/docs/components/tabs/tab.rb +4 -2
  23. data/docs/components/tabs.rb +1 -1
  24. data/docs/components/title.rb +2 -2
  25. data/docs/page_builder.rb +3 -0
  26. data/docs/pages/application_page.rb +1 -1
  27. data/docs/pages/helpers.rb +97 -0
  28. data/docs/pages/index.rb +6 -17
  29. data/docs/pages/library/collections.rb +83 -0
  30. data/docs/pages/rails/getting_started.rb +53 -0
  31. data/docs/pages/rails/helpers.rb +55 -0
  32. data/docs/pages/rails/layouts.rb +61 -0
  33. data/docs/pages/rails/migrating.rb +37 -0
  34. data/docs/pages/rails/rendering_views.rb +35 -0
  35. data/docs/pages/templates.rb +53 -151
  36. data/docs/pages/testing/capybara.rb +48 -0
  37. data/docs/pages/testing/getting_started.rb +44 -0
  38. data/docs/pages/testing/nokogiri.rb +83 -0
  39. data/docs/pages/testing/rails.rb +17 -0
  40. data/docs/pages/translations.rb +81 -0
  41. data/docs/pages/views.rb +87 -78
  42. data/fixtures/compiler_test_helpers.rb +19 -0
  43. data/fixtures/content.rb +60 -0
  44. data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
  45. data/fixtures/dummy/app/components/comment_component.rb +8 -0
  46. data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
  47. data/fixtures/dummy/app/components/reaction_component.rb +7 -0
  48. data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
  49. data/fixtures/dummy/app/views/application_view.rb +8 -0
  50. data/fixtures/dummy/app/views/articles/form.rb +3 -1
  51. data/fixtures/dummy/app/views/card.rb +4 -2
  52. data/fixtures/dummy/app/views/comments/comment.rb +25 -0
  53. data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
  54. data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
  55. data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
  56. data/fixtures/dummy/app/views/heading.rb +1 -1
  57. data/fixtures/layout.rb +5 -5
  58. data/fixtures/page.rb +18 -24
  59. data/fixtures/{test_helper.rb → rails_helper.rb} +3 -7
  60. data/fixtures/standard_element.rb +87 -0
  61. data/fixtures/view_helper.rb +1 -1
  62. data/fixtures/void_element.rb +31 -0
  63. data/lib/generators/phlex/collection/USAGE +8 -0
  64. data/lib/generators/phlex/collection/collection_generator.rb +13 -0
  65. data/lib/generators/phlex/collection/templates/collection.rb.erb +16 -0
  66. data/lib/generators/phlex/controller/USAGE +10 -0
  67. data/lib/generators/phlex/controller/controller_generator.rb +54 -0
  68. data/lib/generators/phlex/controller/templates/controller.rb.erb +10 -0
  69. data/lib/generators/phlex/controller/templates/view.rb.erb +14 -0
  70. data/lib/generators/phlex/layout/USAGE +8 -0
  71. data/lib/generators/phlex/layout/layout_generator.rb +13 -0
  72. data/lib/generators/phlex/layout/templates/layout.rb.erb +31 -0
  73. data/lib/generators/phlex/page/USAGE +8 -0
  74. data/lib/generators/phlex/page/page_generator.rb +13 -0
  75. data/lib/generators/phlex/page/templates/page.rb.erb +13 -0
  76. data/lib/generators/phlex/table/USAGE +8 -0
  77. data/lib/generators/phlex/table/table_generator.rb +14 -0
  78. data/lib/generators/phlex/table/templates/table.rb.erb +11 -0
  79. data/lib/generators/phlex/view/templates/view.rb.erb +7 -1
  80. data/lib/generators/phlex/view/view_generator.rb +9 -1
  81. data/lib/install/phlex.rb +10 -1
  82. data/lib/phlex/block.rb +2 -4
  83. data/lib/phlex/buffered.rb +6 -8
  84. data/lib/phlex/callable.rb +9 -0
  85. data/lib/phlex/collection.rb +33 -0
  86. data/lib/phlex/compiler/elements.rb +49 -0
  87. data/lib/phlex/compiler/generators/content.rb +103 -0
  88. data/lib/phlex/compiler/generators/element.rb +61 -0
  89. data/lib/phlex/compiler/nodes/base.rb +19 -0
  90. data/lib/phlex/compiler/nodes/call.rb +9 -0
  91. data/lib/phlex/compiler/nodes/command.rb +13 -0
  92. data/lib/phlex/compiler/nodes/fcall.rb +18 -0
  93. data/lib/phlex/compiler/nodes/method_add_block.rb +33 -0
  94. data/lib/phlex/compiler/nodes/vcall.rb +9 -0
  95. data/lib/phlex/compiler/optimizer.rb +66 -0
  96. data/lib/phlex/compiler/visitors/base.rb +15 -0
  97. data/lib/phlex/compiler/visitors/file.rb +23 -11
  98. data/lib/phlex/compiler/visitors/stable_scope.rb +28 -0
  99. data/lib/phlex/compiler/visitors/statements.rb +36 -0
  100. data/lib/phlex/compiler/visitors/view.rb +19 -0
  101. data/lib/phlex/compiler/visitors/view_method.rb +59 -0
  102. data/lib/phlex/compiler.rb +23 -3
  103. data/lib/phlex/elements.rb +57 -0
  104. data/lib/phlex/engine.rb +0 -3
  105. data/lib/phlex/helpers.rb +59 -0
  106. data/lib/phlex/html/callbacks.rb +11 -0
  107. data/lib/phlex/html.rb +209 -54
  108. data/lib/phlex/markdown.rb +76 -0
  109. data/lib/phlex/rails/form.rb +67 -0
  110. data/lib/phlex/rails/helpers.rb +118 -0
  111. data/lib/phlex/rails/layout.rb +15 -0
  112. data/lib/phlex/rails.rb +10 -0
  113. data/lib/phlex/renderable.rb +9 -3
  114. data/lib/phlex/table.rb +104 -0
  115. data/lib/phlex/testing/capybara.rb +25 -0
  116. data/lib/phlex/testing/nokogiri.rb +24 -0
  117. data/lib/phlex/testing/rails.rb +19 -0
  118. data/lib/phlex/testing/view_helper.rb +15 -0
  119. data/lib/phlex/translation.rb +23 -0
  120. data/lib/phlex/turbo/frame.rb +21 -0
  121. data/lib/phlex/turbo/stream.rb +18 -0
  122. data/lib/phlex/version.rb +1 -1
  123. data/lib/phlex.rb +22 -24
  124. metadata +112 -15
  125. data/.rspec +0 -1
  126. data/fixtures/compilation/vcall.rb +0 -38
  127. data/lib/phlex/compiler/generators/standard_element.rb +0 -30
  128. data/lib/phlex/compiler/generators/void_element.rb +0 -29
  129. data/lib/phlex/compiler/optimizers/base_optimizer.rb +0 -34
  130. data/lib/phlex/compiler/optimizers/vcall.rb +0 -29
  131. data/lib/phlex/compiler/visitors/base_visitor.rb +0 -19
  132. data/lib/phlex/compiler/visitors/component.rb +0 -28
  133. data/lib/phlex/compiler/visitors/component_method.rb +0 -28
  134. data/lib/phlex/rails/tag_helpers.rb +0 -29
  135. data/lib/phlex/view.rb +0 -223
data/docs/pages/index.rb CHANGED
@@ -3,34 +3,23 @@
3
3
  module Pages
4
4
  class Index < ApplicationPage
5
5
  def template
6
- render Layout.new(title: "Introduction to Phlex") do
6
+ render Layout.new(title: "Introduction to Phlex, a fast, object-oriented view framework for Ruby") do
7
7
  render Markdown.new(<<~MD)
8
8
  # Introduction
9
9
 
10
10
  Phlex is a framework for building fast, reusable, testable views in pure Ruby.
11
11
 
12
- Each view object is an instance of a specific class of view. The nav-bar, for example, might contain three different nav-bar-items, but they’re all instances of the nav-bar-item class. This class, then, manifests everything there is to know about nav bar items in general. It models:
12
+ ## Better developer experience 💃
13
13
 
14
- 1. the **data** attributes being represented perhaps url and label;
15
- 2. a **template**, which dictates how the data should be represented with HTML markup and CSS classes; and
16
- 3. **logic**, conditions, or calculations on the data — perhaps a predicate method to determine whether the link is active or not.
17
-
18
- # Why use Phlex?
19
-
20
- ## Better developer experience 🎉
21
-
22
- You don’t need to introduce a new language like Slim, HAML, or ERB. Phlex views are plain old Ruby objects: view classes are just Ruby classes, templates are just methods, and HTML tags are just method calls. If you know how to define a method that calls another method, you pretty much already know how to use Phlex.
14
+ Phlex views are plain old Ruby objects. View classes are just Ruby classes, templates are just methods, and HTML tags are just method calls. If you know how to define a method that calls another method, you pretty much already know how to use Phlex.
23
15
 
24
16
  ## Better safety 🥽
25
- Rails partials can implicitly depend on instance variables from a controller or view. This happens more often than you might think when copying code into a new partial extraction. If the partial is then rendered in a different context or the instance variable’s meaning changes, things can break quite severely without warning.
26
-
27
- Conversely, Phlex view templates render in an isolated execution context where only the instance variables and methods for the specific view are exposed.
28
17
 
29
- ## Better performance 🚀
18
+ Phlex view templates render in an isolated execution context where only the instance variables and methods for the specific view are exposed.
30
19
 
31
- Phlex is ~4.35× faster than ActionView and ~2× faster than ViewComponent. Phlex views are also streamable.
20
+ ## Better performance 🔥
32
21
 
33
- Rails apps typically spend 40-80% of the response time rendering views, so this could be a significant factor in overall app performance.
22
+ Rendering a Phlex view is ~4.35× faster than an ActionView partial and ~2× faster than ViewComponent component.
34
23
  MD
35
24
  end
36
25
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ module Library
5
+ class Collections < ApplicationPage
6
+ def template
7
+ render Layout.new(title: "Getting started with Rails") do
8
+ render Markdown.new <<~MD
9
+ # Collections
10
+
11
+ Phlex comes with an abstract pattern for views that represent collections of resources — lists, grids, tables, etc. Collections have two parts: one part wraps the whole collection, the other part is repeated once for each item in that collection.
12
+
13
+ When you include `Phlex::Collection` in a `Phlex::HTML`, the `template` and `initialize` methods are defined for you. You don't need to define these. Instead, you define a `collection_template` and `item_template`.
14
+
15
+ ## Collection template
16
+
17
+ The `collection_template` method should accept a content block which is used to yield the items. We can yield this block or pass it to another element, such as `<ul>`.
18
+
19
+ ```ruby
20
+ def collection_template(&)
21
+ ul(&)
22
+ end
23
+ ```
24
+
25
+ ## Item template
26
+
27
+ From the `item_template` method, you can access a single item with the `@item` instance variable.
28
+
29
+ ```ruby
30
+ def item_template
31
+ li { @item }
32
+ end
33
+ ```
34
+
35
+ ## Rendering a collection
36
+
37
+ Putting it all together, we can create a `List` view that renders each item in an `<li>`, all wrapped up in an outer `<ul>`.
38
+
39
+ We can render the list by passing any Enumerable as the `collection` keyword argument. Here, we pass the array `["A", "B", "C"]`.
40
+ MD
41
+
42
+ render Example.new do |e|
43
+ e.tab "list.rb", <<~RUBY
44
+ class List < Phlex::HTML
45
+ include Phlex::Collection
46
+
47
+ def collection_template(&content)
48
+ ul(&content)
49
+ end
50
+
51
+ def item_template
52
+ li { @item }
53
+ end
54
+ end
55
+ RUBY
56
+
57
+ e.tab "example.rb", <<~RUBY
58
+ class Example < Phlex::HTML
59
+ def template
60
+ render List.new(
61
+ collection: ["A", "B", "C"]
62
+ )
63
+ end
64
+ end
65
+ RUBY
66
+
67
+ e.execute "Example.new.call"
68
+ end
69
+
70
+ render Markdown.new <<~MD
71
+ ## Rendering a single item
72
+
73
+ Sometimes you need to render one item of a collection on its own. This is especially handy if you're using Hotwire to append an item to the end of an existing collection. You can render an individual item without the collection wrapper by passing an `item` keyword argument to the collection view.
74
+
75
+ ```ruby
76
+ render List.new(item: "A")
77
+ ```
78
+ MD
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ module Rails
5
+ class GettingStarted < ApplicationPage
6
+ def template
7
+ render Layout.new(title: "Getting started with Rails") do
8
+ render Markdown.new <<~MD
9
+ # Getting started with Phlex on Rails
10
+
11
+ While Phlex can be used in any Ruby project, it's especially great with [Rails](https://rubyonrails.org). But before we get into the details, it's important to understand that Phlex is very different from [ActionView](https://guides.rubyonrails.org/action_view_overview.html) and [ViewComponent](https://viewcomponent.org).
12
+
13
+ In Phlex, _layouts_, _pages_ and _components_ (or "partials") are the same thing. Phlex Views are Ruby objects that represent every piece of your app's user interface, from pages and layouts and nav-bars, to headings and buttons and links. They're not templates like ERB files in ActionView / ViewComponent; they are just Ruby objects.
14
+
15
+ It might feel a bit weird at first, but you'll soon realise how weird it was writing procedural templates in ERB while every other part of your app was object-oriented Ruby.
16
+
17
+ ## Setup
18
+
19
+ If you haven't installed Phlex already, you'll need to add it to your Gemfile. The easiest way to do this is to run `bundle add phlex`.
20
+
21
+ Once that's finished, you'll want to run the setup script: `bin/rails phlex:install`.
22
+
23
+ This script will:
24
+
25
+ 1. update `config/application.rb` to include `/app` in your auto-load paths;
26
+ 2. generate `views/application_view.rb`.
27
+
28
+ Like `ApplicationRecord`, `ApplicationView` is your base view which all your other views should inherit from.
29
+
30
+ ## Naming conventions
31
+
32
+ We recommend using the Zeitwerk conventions for naming. For example, your Articles index page, would be called `Views::Articles::Index` and live in `app/views/articles/index.rb`.
33
+
34
+ ## View generator
35
+
36
+ You can generate new views with the `rails g phlex:view` command.
37
+
38
+ For example, running `rails g phlex:view Articles::Index` will create `app/views/articles/index.rb` with the following:
39
+
40
+ ```ruby
41
+ module Views
42
+ class Articles::Index < ApplicationView
43
+ def template
44
+ end
45
+ end
46
+ end
47
+ ```
48
+ MD
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex/rails"
4
+
5
+ module Pages
6
+ module Rails
7
+ class Helpers < ApplicationPage
8
+ def template
9
+ render Layout.new(title: "Using Rails helpers in Phlex views") do
10
+ render Markdown.new <<~MD
11
+ # Using Rails helpers in Phlex views
12
+
13
+ ## The `helpers` proxy
14
+
15
+ You can use the `helpers` proxy to access any Rails helper from a Phlex view. For example, you can use the `#t` helper for translations:
16
+
17
+ ```ruby
18
+ module Views
19
+ class Hello < ApplicationView
20
+ delegate :t, to: :helpers
21
+
22
+ def template
23
+ h1 do
24
+ t "hello"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ ```
30
+
31
+ ## Layout helpers
32
+
33
+ Rails tag helpers return strings which makes them less than ideal to use from Phlex. Including `Phlex::Rails::Layout` gives you access to the following Rails helper proxies which immediately output to the buffer:
34
+
35
+ #{(Phlex::Rails::Layout.instance_methods - Module.methods).sort.map { "1. `#{_1}`" }.join("\n")}
36
+
37
+ Using these is equvalent to passing the output of the original Rails helpers to `raw`, e.g:
38
+
39
+ ```ruby
40
+ unsafe_raw helpers.javascript_include_tag
41
+ ```
42
+
43
+ ## Including proxies
44
+
45
+ The following modules can be included for direct access to these helpers:
46
+
47
+ #{Phlex::Rails::Helpers.constants.sort.map { "1. `Phlex::Rails::Helpers::#{_1}`" }.join("\n")}
48
+
49
+ Note, helpers that produce HTML are adapted to output to the buffer directly.
50
+ MD
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ module Rails
5
+ class Layouts < ApplicationPage
6
+ def template
7
+ render Layout.new(title: "Getting started with Rails") do
8
+ render Markdown.new <<~MD
9
+ # Layouts in Rails
10
+
11
+ Rails has an implicit layouts feature that automatically wraps views in a layout template, usually `views/layouts/application.html.erb`. When using Phlex, you'll probably want to by-pass this feature completely. Here's why:
12
+
13
+ Sometimes you need to pass some argument to the layout, such as a page title that needs to be rendered in a `<title>` tag in the HTML `<head>`. Rails lets you do this from another part of the view using the `content_for` feature, but this pattern precludes defining an explicit interface for your layout through its initializer.
14
+
15
+ If layouts are Phlex views, they can be rendered just like any other view and can require that `title` argument from their initializer. The trick is the page view renders its content into the layout view.
16
+ MD
17
+
18
+ render Example.new do |e|
19
+ e.tab "layout.rb", <<~RUBY
20
+ module Views
21
+ class Layout < Phlex::HTML
22
+ def initialize(title:)
23
+ @title = title
24
+ end
25
+
26
+ def template(&)
27
+ html do
28
+ head do
29
+ title { @title }
30
+ end
31
+
32
+ body(&)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ RUBY
38
+
39
+ e.tab "index.rb", <<~RUBY
40
+ module Views
41
+ class Index < Phlex::HTML
42
+ def template
43
+ render Layout.new(title: "Hello") do
44
+ h1 { "👋" }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ RUBY
50
+
51
+ e.execute "Views::Index.new.call"
52
+ end
53
+
54
+ render Markdown.new <<~MD
55
+ If you're using a Phlex view as a layout, you'll want to disable layouts from your Rails controller. You can do this by adding `layout false` to your controller. In a new app, you'll probably want to add this to the `ApplicationController`.
56
+ MD
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class Rails::Migrating < ApplicationPage
5
+ def template
6
+ render Layout.new(title: "Migrating to Phlex in Rails") do
7
+ render Markdown.new <<~MD
8
+ # Migrating an existing Rails app to Phlex
9
+
10
+ Whether you currently use ActionView or ViewComponent with ERB, HAML or Slim, you can start using Phlex in your Rails app today without a big rewrite.
11
+
12
+ ## You can render Phlex views into existing templates
13
+
14
+ Phlex views implement the _renderable_ interface for Rails, which means they can be rendered from a controller or another view template — even ViewComponent templates. This means you can gradually migrate specific pages and components to Phlex without having to change anything else.
15
+
16
+ ## You can render ActionView partials and ViewComponent components in Phlex views
17
+
18
+ That's right, the `render` method in Phlex doesn't only work with Phlex views. You can use it to render ActionView partials and ViewComponent components too.
19
+
20
+ Say you have an `articles/index.html.erb` template that renders a list of articles using the `articles/_article.html.erb` partial. You can convert the index template to an `Articles::Index` Phlex view while continuing to render the same ActionView partial inside it.
21
+
22
+ ## Use an ERB → Phlex converter
23
+
24
+ The ERB → Phlex converter, [Phlexing](https://www.phlexing.fun), can do the heavy-lifting but it won't help you architect your components / design system. Take your time, converting things piece-by-piece.
25
+
26
+ If you're using ViewComponent, you might find you can convert components to Phlex views without even changing any call-sites.
27
+
28
+ ## Save the layout 'til last
29
+
30
+ After everything I said about layouts in the previous section, I'll let you in on a little secret: Phlex actually does support `content_for` in one direction. You can't yield a `content_for` block in Phlex, but you can assign one.
31
+
32
+ If you're working on an established Rails app, the layout is probably the last thing you should convert. Just include `Phlex::Rails::Helpers::ContentFor` in your `ApplicationView` and you'll be able to render Phlex views into existing ActionView layouts and assign `content_for` blocks too.
33
+ MD
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pages
4
+ class Rails::RenderingViews < ApplicationPage
5
+ def template
6
+ render Layout.new(title: "Getting started with Rails") do
7
+ render Markdown.new <<~MD
8
+ # Rendering Phlex views in Rails
9
+
10
+ You can render a `Phlex::HTML` from your Rails controller actions as well as other views, and even from ActionView / ViewComponent templates.
11
+
12
+ Instead of implicitly rendering an ERB template with automatic access to all your controller instance variables, you need to explicitly render Phlex views from your controller action methods.
13
+
14
+ Doing this allows you to design views without implicit dependencies on controller instance variables, making them much easier to test and reuse and reason about.
15
+
16
+ ```ruby
17
+ class ArticlesController < ApplicationController
18
+ def index
19
+ render Views::Articles::Index.new(
20
+ articles: Article.all.load_async
21
+ )
22
+ end
23
+
24
+ def show
25
+ render Views::Articles::Show.new(
26
+ article: Article.find(params[:id])
27
+ )
28
+ end
29
+ end
30
+ ```
31
+ MD
32
+ end
33
+ end
34
+ end
35
+ end
@@ -5,101 +5,111 @@ module Pages
5
5
  def template
6
6
  render Layout.new(title: "Templates in Phlex") do
7
7
  render Markdown.new(<<~MD)
8
- # Templates
8
+ # Working with templates
9
9
 
10
- Rather than use another language like ERB, HAML or Slim, Phlex provides a Ruby DSL for defining HTML templates.
10
+ In Phlex, templates are just methods that call other methods that add things to the output buffer. When you call the method `h1`, an `<h1>` tag is buffered for output.
11
11
 
12
- You can create a view class by subclassing `Phlex::View` and defining a method called `template`. Within the `template` method, you can compose HTML markup by calling the name of any [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
12
+ ## Tag attributes
13
13
 
14
- The first argument to an HTML element method is the _text content_ for that element. For example, here’s a view with an `<h1>` element that says “Hello World!”
14
+ You can add attributes to HTML tags by passing keyword arguments to the tag methods.
15
15
  MD
16
16
 
17
17
  render Example.new do |e|
18
- e.tab "heading.rb", <<~RUBY
19
- class Heading < Phlex::View
18
+ e.tab "hello.rb", <<~RUBY
19
+ class Hello < Phlex::HTML
20
20
  def template
21
- h1 "Hello World!"
21
+ h1(class: "text-xl font-bold") { "👋 Hello World!" }
22
22
  end
23
23
  end
24
24
  RUBY
25
25
 
26
- e.execute "Heading.new.call"
26
+ e.execute "Hello.new.call"
27
27
  end
28
28
 
29
29
  render Markdown.new(<<~MD)
30
- The text content is always HTML-escaped, so it’s safe to use with user input.
31
-
32
- ## Attributes
30
+ ## Hash attributes
33
31
 
34
- You can add attributes to HTML elements by passing keyword arguments to the tag method. Underscores (`_`) in attribute names are automatically converted to dashes (`-`).
32
+ If you pass a `Hash` as an attribute value, the hash will be flattened with a dash between each section.
35
33
  MD
36
34
 
37
35
  render Example.new do |e|
38
- e.tab "heading.rb", <<~RUBY
39
- class Heading < Phlex::View
36
+ e.tab "hello.rb", <<~RUBY
37
+ class Hello < Phlex::HTML
40
38
  def template
41
- h1 "Hello World!",
42
- class: "text-xl font-bold",
43
- aria_details: "details"
39
+ div(data: { controller: "hello" }) do
40
+ # ...
41
+ end
44
42
  end
45
43
  end
46
44
  RUBY
47
45
 
48
- e.execute "Heading.new.call"
46
+ e.execute "Hello.new.call"
49
47
  end
50
48
 
51
49
  render Markdown.new(<<~MD)
52
- You can also use *boolean* attributes. When set to `true`, the attribute will be rendered without a value, when _falsy_, the attribute isn’t rendered at all.
50
+ ## Boolean attributes
51
+
52
+ When `true`, the attribute will be rendered without a value; when _falsy_, the attribute isn’t rendered at all. You can still use the strings `"true"` and `"false"` as values for non-boolean attributes.
53
53
  MD
54
54
 
55
55
  render Example.new do |e|
56
- e.tab "example.rb", <<~RUBY
57
- class Example < Phlex::View
56
+ e.tab "channel_controls.rb", <<~RUBY
57
+ class ChannelControls < Phlex::HTML
58
58
  def template
59
- input type: "radio", name: "channel", id: "1", checked: true
60
- input type: "radio", name: "channel", id: "2", checked: false
59
+ input(
60
+ value: "1",
61
+ name: "channel",
62
+ type: "radio",
63
+ checked: true
64
+ )
65
+
66
+ input(
67
+ value: "2",
68
+ name: "channel",
69
+ type: "radio",
70
+ checked: false
71
+ )
61
72
  end
62
73
  end
63
74
  RUBY
64
75
 
65
- e.execute "Example.new.call"
76
+ e.execute "ChannelControls.new.call"
66
77
  end
67
78
 
68
79
  render Markdown.new(<<~MD)
69
- ## Nesting
80
+ ## The template tag
70
81
 
71
- Pass a block to an element method to nest other elements inside it.
82
+ Because the `template` method is used to define the view template itself, you'll need to use the method `template_tag` if you want to to render an HTML `<template>` tag.
72
83
  MD
73
84
 
74
85
  render Example.new do |e|
75
- e.tab "nav.rb", <<~RUBY
76
- class Nav < Phlex::View
86
+ e.tab "example.rb", <<~RUBY
87
+ class Example < Phlex::HTML
77
88
  def template
78
- nav do
79
- ul do
80
- li { a "Home", href: "/" }
81
- li { a "About", href: "/about" }
82
- li { a "Contact", href: "/contact" }
83
- end
89
+ template_tag do
90
+ img src: "hidden.jpg", alt: "A hidden image."
84
91
  end
85
92
  end
86
93
  end
87
94
  RUBY
88
95
 
89
- e.execute "Nav.new.call"
96
+ e.execute "Example.new.call"
90
97
  end
91
98
 
92
99
  render Markdown.new(<<~MD)
93
100
  ## Stand-alone text
94
101
 
95
- You can also output text without wrapping it in an element by using the `text` method. All text content is HTML-escaped, so it’s safe to use with user input.
102
+ You can output text content without wrapping it in an element by using the `text` helper method.
96
103
  MD
97
104
 
98
105
  render Example.new do |e|
99
106
  e.tab "heading.rb", <<~RUBY
100
- class Heading < Phlex::View
107
+ class Heading < Phlex::HTML
101
108
  def template
102
- h1 { strong "Hello "; text "World!" }
109
+ h1 do
110
+ strong { "Hello " }
111
+ text "World!"
112
+ end
103
113
  end
104
114
  end
105
115
  RUBY
@@ -110,132 +120,24 @@ module Pages
110
120
  render Markdown.new(<<~MD)
111
121
  ## Whitespace
112
122
 
113
- While the examples on this page have been formatted for readability, there is usually no whitespace between HTML tags. If you need to add some whitespace, you can use the `whitespace` method. This is useful for adding space between _inline_ elements to allow them to wrap.
123
+ The example output on this site is formatted for readability, but there is usually no whitespace between HTML tags in the output. If you need to add some whitespace, you can use the `whitespace` helper. This is useful for adding space between _inline_ elements to allow them to wrap.
114
124
  MD
115
125
 
116
126
  render Example.new do |e|
117
127
  e.tab "links.rb", <<~RUBY
118
- class Links < Phlex::View
128
+ class Links < Phlex::HTML
119
129
  def template
120
- a "Home", href: "/"
130
+ a(href: "/") { "Home" }
121
131
  whitespace
122
- a "About", href: "/about"
132
+ a(href: "/about") { "About" }
123
133
  whitespace
124
- a "Contact", href: "/contact"
134
+ a(href: "/contact") { "Contact" }
125
135
  end
126
136
  end
127
137
  RUBY
128
138
 
129
139
  e.execute "Links.new.call"
130
140
  end
131
-
132
- render Markdown.new(<<~MD)
133
- ## Tokens and classes
134
-
135
- The `tokens` method helps you define conditional HTML attribute tokens (such as CSS classes).
136
-
137
- The `tokens` method accepts a splat of tokens that should always be output, and accepts keyword arguments for conditional tokens.
138
-
139
- The keyword arguments allow you to specify under which conditions certain tokens are applicable. The keyword argument keys are the conditions and the values are the tokens. Conditions can be Procs or Symbols that map to a relevant method. The `:active?` Symbol, for example, maps to the `active?` instance method.
140
-
141
- Here we have a `Link` view that produces an `<a>` tag with the CSS class `nav-item`. And if the link is _active_, we also apply the CSS class `nav-item-active`.
142
- MD
143
-
144
- render Example.new do |e|
145
- e.tab "link.rb", <<~RUBY
146
- class Link < Phlex::View
147
- def initialize(text, to:, active:)
148
- @text = text
149
- @to = to
150
- @active = active
151
- end
152
-
153
- def template
154
- a @text, href: @to, class: tokens("nav-item",
155
- active?: "nav-item-active")
156
- end
157
-
158
- private
159
-
160
- def active? = @active
161
- end
162
- RUBY
163
-
164
- e.tab "example.rb", <<~RUBY
165
- class Example < Phlex::View
166
- def template
167
- nav do
168
- ul do
169
- li { render Link.new("Home", to: "/", active: true) }
170
- li { render Link.new("About", to: "/about", active: false) }
171
- end
172
- end
173
- end
174
- end
175
- RUBY
176
-
177
- e.execute "Example.new.call"
178
- end
179
-
180
- render Markdown.new(<<~MD)
181
- You can also use the `classes` helper method to create a token list of classes. Since this method returns a hash (e.g. `{ class: "your CSS classes here" }`), you can destructure it into a `class:` keyword argument using the `**` prefix operator.
182
- MD
183
-
184
- render Example.new do |e|
185
- e.tab "link.rb", <<~RUBY
186
- class Link < Phlex::View
187
- def initialize(text, to:, active:)
188
- @text = text
189
- @to = to
190
- @active = active
191
- end
192
-
193
- def template
194
- a @text, href: @to, **classes("nav-item",
195
- active?: "nav-item-active")
196
- end
197
-
198
- private
199
-
200
- def active? = @active
201
- end
202
- RUBY
203
-
204
- e.tab "example.rb", <<~RUBY
205
- class Example < Phlex::View
206
- def template
207
- nav do
208
- ul do
209
- li { render Link.new("Home", to: "/", active: true) }
210
- li { render Link.new("About", to: "/about", active: false) }
211
- end
212
- end
213
- end
214
- end
215
- RUBY
216
-
217
- e.execute "Example.new.call"
218
- end
219
-
220
- render Markdown.new(<<~MD)
221
- ## The template element
222
-
223
- Because the `template` method is used to define the view template itself, you need to use the method `template_tag` if you want to to render an HTML `<template>` tag.
224
- MD
225
-
226
- render Example.new do |e|
227
- e.tab "example.rb", <<~RUBY
228
- class Example < Phlex::View
229
- def template
230
- template_tag do
231
- img src: "hidden.jpg", alt: "A hidden image."
232
- end
233
- end
234
- end
235
- RUBY
236
-
237
- e.execute "Example.new.call"
238
- end
239
141
  end
240
142
  end
241
143
  end