phlex 0.3.2 → 0.4.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.
- checksums.yaml +4 -4
- data/.editorconfig +8 -0
- data/.rubocop.yml +3 -7
- data/Gemfile +2 -1
- data/Procfile.dev +3 -0
- data/Rakefile +3 -5
- data/docs/build.rb +15 -10
- data/docs/components/code_span.rb +9 -0
- data/docs/components/example.rb +1 -1
- data/docs/components/heading.rb +1 -1
- data/docs/components/layout.rb +38 -16
- data/docs/components/markdown.rb +17 -3
- data/docs/components/nav/item.rb +33 -0
- data/docs/components/nav.rb +6 -0
- data/docs/components/tabs/tab.rb +3 -1
- data/docs/components/title.rb +1 -1
- data/docs/page_builder.rb +3 -0
- data/docs/pages/helpers.rb +97 -0
- data/docs/pages/index.rb +6 -17
- data/docs/pages/library/collections.rb +101 -0
- data/docs/pages/rails/getting_started.rb +53 -0
- data/docs/pages/rails/helpers.rb +53 -0
- data/docs/pages/rails/layouts.rb +61 -0
- data/docs/pages/rails/migrating.rb +37 -0
- data/docs/pages/rails/rendering_views.rb +35 -0
- data/docs/pages/templates.rb +51 -149
- data/docs/pages/views.rb +55 -94
- data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
- data/fixtures/dummy/app/components/comment_component.rb +8 -0
- data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
- data/fixtures/dummy/app/components/reaction_component.rb +7 -0
- data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
- data/fixtures/dummy/app/views/articles/form.rb +2 -0
- data/fixtures/dummy/app/views/card.rb +3 -1
- data/fixtures/dummy/app/views/comments/comment.rb +25 -0
- data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
- data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
- data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
- data/fixtures/test_helper.rb +1 -0
- data/lib/generators/phlex/collection/USAGE +8 -0
- data/lib/generators/phlex/collection/collection_generator.rb +13 -0
- data/lib/generators/phlex/collection/templates/collection.rb.erb +15 -0
- data/lib/generators/phlex/layout/USAGE +8 -0
- data/lib/generators/phlex/layout/layout_generator.rb +13 -0
- data/lib/generators/phlex/layout/templates/layout.rb.erb +30 -0
- data/lib/generators/phlex/page/USAGE +8 -0
- data/lib/generators/phlex/page/page_generator.rb +13 -0
- data/lib/generators/phlex/page/templates/page.rb.erb +11 -0
- data/lib/generators/phlex/table/USAGE +8 -0
- data/lib/generators/phlex/table/table_generator.rb +14 -0
- data/lib/generators/phlex/table/templates/table.rb.erb +9 -0
- data/lib/phlex/collection.rb +58 -0
- data/lib/phlex/engine.rb +0 -3
- data/lib/phlex/html.rb +7 -13
- data/lib/phlex/rails/helpers.rb +81 -0
- data/lib/phlex/rails/layout.rb +15 -0
- data/lib/phlex/table.rb +104 -0
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex/view.rb +10 -4
- metadata +52 -3
- data/lib/phlex/rails/tag_helpers.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5606a60fd7cce19c44c13c26ec1abb42f52252b71208aa77f7cecf49f95910be
|
4
|
+
data.tar.gz: 0c55bae86c2f14514108f7d58ab9c6e7d3bc88b5a89cc8cbb7156b86e355041d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60e835cad9721dcf6c353d84963a93bb2518b307df493f9a4c2fcaf737a69bdb9b7c32ed5b9e8a5c0049c1fea6445e77d75988de7998d4b65ac4b9781dc038e0
|
7
|
+
data.tar.gz: 664c9e1fb555019b40085ad49de87bc2f99619d19157603445590a7179390a52ba66dee614896d8c5901abce5fce1f34031a3b1a6e03931bcad76ba41b48e035
|
data/.editorconfig
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
-
inherit_from:
|
1
|
+
inherit_from:
|
2
|
+
- "https://www.goodcop.style"
|
3
|
+
- "https://www.goodcop.style/tabs.yml"
|
2
4
|
|
3
5
|
AllCops:
|
4
6
|
TargetRubyVersion: 2.7
|
5
|
-
|
6
|
-
Layout/IndentationStyle:
|
7
|
-
EnforcedStyle: tabs
|
8
|
-
|
9
|
-
Layout/IndentationWidth:
|
10
|
-
Width: 1
|
data/Gemfile
CHANGED
@@ -10,7 +10,6 @@ gem "rake"
|
|
10
10
|
gem "sus", group: [:test]
|
11
11
|
gem "rails", group: [:test]
|
12
12
|
gem "rouge", group: [:docs]
|
13
|
-
gem "listen", group: [:docs]
|
14
13
|
gem "webrick", group: [:docs]
|
15
14
|
gem "zeitwerk", group: [:docs]
|
16
15
|
gem "redcarpet", group: [:docs]
|
@@ -20,3 +19,5 @@ gem "htmlbeautifier", group: [:docs]
|
|
20
19
|
gem "benchmark-memory"
|
21
20
|
gem "rubocop", require: false, github: "joeldrapper/rubocop", branch: "rubocop-user-agent"
|
22
21
|
gem "syntax_suggest"
|
22
|
+
gem "foreman"
|
23
|
+
gem "filewatcher", group: [:docs]
|
data/Procfile.dev
ADDED
data/Rakefile
CHANGED
data/docs/build.rb
CHANGED
@@ -1,22 +1,27 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
$stdout.sync = true
|
5
|
+
|
4
6
|
require "phlex"
|
5
7
|
require "bundler"
|
6
8
|
require "fileutils"
|
7
9
|
|
8
10
|
Bundler.require :docs
|
9
11
|
|
10
|
-
Zeitwerk::Loader.new
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
FileUtils.mkdir_p("#{__dir__}/dist")
|
18
|
-
FileUtils.cp_r("#{__dir__}/assets", "#{__dir__}/dist")
|
12
|
+
loader = Zeitwerk::Loader.new
|
13
|
+
loader.push_dir(__dir__)
|
14
|
+
loader.ignore(__FILE__)
|
15
|
+
loader.enable_reloading
|
16
|
+
loader.setup
|
17
|
+
loader.eager_load
|
19
18
|
|
20
19
|
PageBuilder.build_all
|
21
20
|
|
22
|
-
|
21
|
+
if ARGV.include? "--watch"
|
22
|
+
Filewatcher.new("#{__dir__}/**/*rb").watch do |_changes|
|
23
|
+
loader.reload
|
24
|
+
loader.eager_load
|
25
|
+
PageBuilder.build_all
|
26
|
+
end
|
27
|
+
end
|
data/docs/components/example.rb
CHANGED
data/docs/components/heading.rb
CHANGED
data/docs/components/layout.rb
CHANGED
@@ -14,30 +14,52 @@ module Components
|
|
14
14
|
html do
|
15
15
|
head do
|
16
16
|
meta charset: "utf-8"
|
17
|
-
title @title
|
17
|
+
title { @title }
|
18
18
|
link href: "/application.css", rel: "stylesheet"
|
19
19
|
style { raw Rouge::Theme.find("github").render(scope: ".highlight") }
|
20
20
|
end
|
21
21
|
|
22
|
-
body class: "
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
li { a "Templates", href: "/templates" }
|
31
|
-
li { a "Views", href: "/views" }
|
32
|
-
li { a "Rails integration", href: "/rails-integration" }
|
33
|
-
li { a "Source code", href: "https://github.com/joeldrapper/phlex" }
|
34
|
-
end
|
22
|
+
body class: "text-stone-700" do
|
23
|
+
header class: "border-b py-4 px-10 flex justify-between items-center" do
|
24
|
+
a(href: "/", class: "block") { img src: "/assets/logo.png", width: "100" }
|
25
|
+
|
26
|
+
nav(class: "text-stone-500 font-medium") do
|
27
|
+
ul(class: "flex space-x-8") do
|
28
|
+
li { a(href: "https://github.com/sponsors/joeldrapper") { "💖️ Sponsor" } }
|
29
|
+
li { a(href: "https://github.com/joeldrapper/phlex") { "GitHub" } }
|
35
30
|
end
|
36
31
|
end
|
32
|
+
end
|
33
|
+
|
34
|
+
div class: "grid grid-cols-4 divide-x" do
|
35
|
+
nav class: "col-span-1 px-10 py-5" do
|
36
|
+
h2(class: "text-lg font-semibold pt-5") { "Guide" }
|
37
|
+
|
38
|
+
ul do
|
39
|
+
render Nav::Item.new("Introduction", to: Pages::Index, active_page: @_parent)
|
40
|
+
render Nav::Item.new("Views", to: Pages::Views, active_page: @_parent)
|
41
|
+
render Nav::Item.new("Templates", to: Pages::Templates, active_page: @_parent)
|
42
|
+
render Nav::Item.new("Helpers", to: Pages::Helpers, active_page: @_parent)
|
43
|
+
end
|
44
|
+
|
45
|
+
h2(class: "text-lg font-semibold pt-5") { "Rails" }
|
37
46
|
|
38
|
-
|
47
|
+
ul do
|
48
|
+
render Nav::Item.new("Getting started", to: Pages::Rails::GettingStarted, active_page: @_parent)
|
49
|
+
render Nav::Item.new("Rendering views", to: Pages::Rails::RenderingViews, active_page: @_parent)
|
50
|
+
render Nav::Item.new("Laouts", to: Pages::Rails::Layouts, active_page: @_parent)
|
51
|
+
render Nav::Item.new("Helpers", to: Pages::Rails::Helpers, active_page: @_parent)
|
52
|
+
render Nav::Item.new("Migrating to Phlex", to: Pages::Rails::Migrating, active_page: @_parent)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
main class: "col-span-3 px-20 px-10 py-5" do
|
57
|
+
div(class: "max-w-prose prose", &block)
|
58
|
+
end
|
59
|
+
end
|
39
60
|
|
40
|
-
|
61
|
+
footer class: "border-t p-20 flex justify-center text-stone-500 text-lg font-medium" do
|
62
|
+
a(href: "https://github.com/sponsors/joeldrapper") { "Sponsor this project 💖" }
|
41
63
|
end
|
42
64
|
end
|
43
65
|
end
|
data/docs/components/markdown.rb
CHANGED
@@ -3,17 +3,31 @@
|
|
3
3
|
module Components
|
4
4
|
class Markdown < Phlex::View
|
5
5
|
class Render < Redcarpet::Render::HTML
|
6
|
+
include Redcarpet::Render::SmartyPants
|
7
|
+
|
6
8
|
def header(text, level)
|
7
9
|
case level
|
8
10
|
when 1
|
9
|
-
Title.new.call { text }
|
11
|
+
Title.new.call { CGI.unescapeHTML(text) }
|
10
12
|
else
|
11
|
-
Heading.new.call { text }
|
13
|
+
Heading.new.call { CGI.unescapeHTML(text) }
|
12
14
|
end
|
13
15
|
end
|
16
|
+
|
17
|
+
def codespan(code)
|
18
|
+
CodeSpan.new.call { code }
|
19
|
+
end
|
20
|
+
|
21
|
+
def block_code(code, language)
|
22
|
+
CodeBlock.new(code.gsub(/(?:^|\G) {4}/m, " "), syntax: language).call
|
23
|
+
end
|
24
|
+
|
25
|
+
def html_escape(input)
|
26
|
+
input
|
27
|
+
end
|
14
28
|
end
|
15
29
|
|
16
|
-
MARKDOWN = Redcarpet::Markdown.new(Render.new, autolink: true, tables: true)
|
30
|
+
MARKDOWN = Redcarpet::Markdown.new(Render.new, filter_html: false, autolink: true, fenced_code_blocks: true, tables: true, highlight: true, escape_html: false)
|
17
31
|
|
18
32
|
def initialize(content)
|
19
33
|
@content = content
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Components
|
4
|
+
class Nav::Item < Phlex::View
|
5
|
+
def initialize(text, to:, active_page:)
|
6
|
+
@text = text
|
7
|
+
@to = to
|
8
|
+
@active_page = active_page
|
9
|
+
end
|
10
|
+
|
11
|
+
def template
|
12
|
+
li do
|
13
|
+
a(**link_classes, href: "/#{link}") { @text }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def link_classes
|
18
|
+
classes("pb-1 block font-medium text-stone-500", active?: "text-red-600 font-bold")
|
19
|
+
end
|
20
|
+
|
21
|
+
def link
|
22
|
+
path == "index" ? "" : path
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
@to.name.split("::")[1..].map { _1.gsub(/(.)([A-Z])/, '\1-\2') }.map(&:downcase).join("/")
|
27
|
+
end
|
28
|
+
|
29
|
+
def active?
|
30
|
+
@active_page.instance_of?(@to)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/docs/components/tabs/tab.rb
CHANGED
@@ -11,7 +11,9 @@ module Components
|
|
11
11
|
def template(&block)
|
12
12
|
input class: "opacity-0 fixed peer", type: "radio", name: @_parent.unique_identifier, id: unique_identifier, checked: @checked
|
13
13
|
|
14
|
-
label
|
14
|
+
label id: "#{unique_identifier}-label", for: unique_identifier, role: "tab", aria_controls: "#{unique_identifier}-panel", class: "order-1 py-2 px-5 bg-white text-sm border border-b-0 border-l-0 font-medium first-of-type:border-l first-of-type:rounded-tl last-of-type:rounded-tr before:absolute before:pointer-events-none before:w-full before:ring before:h-full before:left-0 before:top-0 before:hidden before:rounded peer-focus:before:block cursor-pointer" do
|
15
|
+
@name
|
16
|
+
end
|
15
17
|
|
16
18
|
div id: "#{unique_identifier}-panel", role: "tabpanel", aria_labelledby: "#{unique_identifier}-label", class: "tab hidden order-2 w-full border rounded-b rounded-tr overflow-hidden" do
|
17
19
|
@_parent.instance_exec(&block)
|
data/docs/components/title.rb
CHANGED
data/docs/page_builder.rb
CHANGED
@@ -4,6 +4,8 @@ class PageBuilder
|
|
4
4
|
ROOT = Pages::ApplicationPage
|
5
5
|
|
6
6
|
def self.build_all
|
7
|
+
FileUtils.mkdir_p("#{__dir__}/dist")
|
8
|
+
FileUtils.cp_r("#{__dir__}/assets", "#{__dir__}/dist")
|
7
9
|
ROOT.subclasses.each { |page| new(page).call }
|
8
10
|
end
|
9
11
|
|
@@ -12,6 +14,7 @@ class PageBuilder
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def call
|
17
|
+
puts "Building #{@page.name}"
|
15
18
|
FileUtils.mkdir_p(directory)
|
16
19
|
File.write(file, @page.new.call)
|
17
20
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pages
|
4
|
+
class Helpers < ApplicationPage
|
5
|
+
def template
|
6
|
+
render Layout.new(title: "Templates in Phlex") do
|
7
|
+
render Markdown.new(<<~MD)
|
8
|
+
# Helpers
|
9
|
+
|
10
|
+
## Conditional tokens and classes
|
11
|
+
|
12
|
+
The `tokens` method helps you define conditional HTML attribute tokens (such as CSS classes). It accepts a splat of tokens that should always be output as well as optional keyword arguments for conditional tokens.
|
13
|
+
|
14
|
+
The keyword arguments allow you to specify under which conditions certain tokens are applicable. The keys are the conditions and the values are the tokens. Conditions can be Procs which are evaluated, or Symbols that map to an instance method. The `:active?` Symbol, for example, maps to the `active?` instance method.
|
15
|
+
|
16
|
+
Here we have a `Link` view that produces an `<a>` tag with the CSS class `nav-item`. If the link is _active_, we also apply the CSS class `nav-item-active`.
|
17
|
+
MD
|
18
|
+
|
19
|
+
render Example.new do |e|
|
20
|
+
e.tab "link.rb", <<~RUBY
|
21
|
+
class Link < Phlex::View
|
22
|
+
def initialize(text, to:, active:)
|
23
|
+
@text = text
|
24
|
+
@to = to
|
25
|
+
@active = active
|
26
|
+
end
|
27
|
+
|
28
|
+
def template
|
29
|
+
a(href: @to, class: tokens("nav-item",
|
30
|
+
active?: "nav-item-active")) { @text }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def active? = @active
|
36
|
+
end
|
37
|
+
RUBY
|
38
|
+
|
39
|
+
e.tab "example.rb", <<~RUBY
|
40
|
+
class Example < Phlex::View
|
41
|
+
def template
|
42
|
+
nav do
|
43
|
+
ul do
|
44
|
+
li { render Link.new("Home", to: "/", active: true) }
|
45
|
+
li { render Link.new("About", to: "/about", active: false) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
RUBY
|
51
|
+
|
52
|
+
e.execute "Example.new.call"
|
53
|
+
end
|
54
|
+
|
55
|
+
render Markdown.new(<<~MD)
|
56
|
+
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.
|
57
|
+
MD
|
58
|
+
|
59
|
+
render Example.new do |e|
|
60
|
+
e.tab "link.rb", <<~RUBY
|
61
|
+
class Link < Phlex::View
|
62
|
+
def initialize(text, to:, active:)
|
63
|
+
@text = text
|
64
|
+
@to = to
|
65
|
+
@active = active
|
66
|
+
end
|
67
|
+
|
68
|
+
def template
|
69
|
+
a(href: @to, **classes("nav-item",
|
70
|
+
active?: "nav-item-active")) { @text }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def active? = @active
|
76
|
+
end
|
77
|
+
RUBY
|
78
|
+
|
79
|
+
e.tab "example.rb", <<~RUBY
|
80
|
+
class Example < Phlex::View
|
81
|
+
def template
|
82
|
+
nav do
|
83
|
+
ul do
|
84
|
+
li { render Link.new("Home", to: "/", active: true) }
|
85
|
+
li { render Link.new("About", to: "/about", active: false) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
RUBY
|
91
|
+
|
92
|
+
e.execute "Example.new.call"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
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
|
-
|
12
|
+
## Better developer experience 💃
|
13
13
|
|
14
|
-
|
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
|
-
|
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
|
-
|
20
|
+
## Better performance 🔥
|
32
21
|
|
33
|
-
|
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,101 @@
|
|
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::View`, 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
|
+
You can also access the predicates `first?` and `last?` and the instance variables `@index`, `@position` and `@collection`.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
def item_template
|
39
|
+
li **classes(first?: "border-t") do
|
40
|
+
"\#{@position}/\#{@collection.size} \#{@item}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Rendering a collection
|
46
|
+
|
47
|
+
Putting it all together, we can create a `List` view that renders each item in an `<li>`, all wrapped up in an outer `<ul>`.
|
48
|
+
|
49
|
+
We can render the list by passing any Enumerable as the `collection` keyword argument. Here, we pass the array `["A", "B", "C"]`.
|
50
|
+
MD
|
51
|
+
|
52
|
+
render Example.new do |e|
|
53
|
+
e.tab "list.rb", <<~RUBY
|
54
|
+
class List < Phlex::View
|
55
|
+
include Phlex::Collection
|
56
|
+
|
57
|
+
def collection_template(&content)
|
58
|
+
ul(&content)
|
59
|
+
end
|
60
|
+
|
61
|
+
def item_template
|
62
|
+
li **classes(first?: "font-bold") do
|
63
|
+
"(\#{@position}/\#{@collection.size}) \#{@item}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
RUBY
|
68
|
+
|
69
|
+
e.tab "example.rb", <<~RUBY
|
70
|
+
class Example < Phlex::View
|
71
|
+
def template
|
72
|
+
render List.new(
|
73
|
+
collection: ["A", "B", "C"]
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
RUBY
|
78
|
+
|
79
|
+
e.execute "Example.new.call"
|
80
|
+
end
|
81
|
+
|
82
|
+
render Markdown.new <<~MD
|
83
|
+
## Rendering a single item
|
84
|
+
|
85
|
+
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.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
render List.new(item: "A")
|
89
|
+
```
|
90
|
+
|
91
|
+
If you've used any of the iteration variables and predicates — `first?`, `last?`, `@position`, `@index`, etc. you'll need to pass these manually to simulate this item's position.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
render List.new(item: "A", first: true, position: 1)
|
95
|
+
```
|
96
|
+
MD
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
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
|