phlex 0.3.1 → 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.

Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +8 -0
  3. data/.rubocop.yml +3 -7
  4. data/Gemfile +2 -1
  5. data/Procfile.dev +3 -0
  6. data/Rakefile +3 -5
  7. data/docs/build.rb +15 -10
  8. data/docs/components/code_span.rb +9 -0
  9. data/docs/components/example.rb +1 -1
  10. data/docs/components/heading.rb +1 -1
  11. data/docs/components/layout.rb +38 -16
  12. data/docs/components/markdown.rb +17 -3
  13. data/docs/components/nav/item.rb +33 -0
  14. data/docs/components/nav.rb +6 -0
  15. data/docs/components/tabs/tab.rb +3 -1
  16. data/docs/components/title.rb +1 -1
  17. data/docs/page_builder.rb +3 -0
  18. data/docs/pages/helpers.rb +97 -0
  19. data/docs/pages/index.rb +6 -17
  20. data/docs/pages/library/collections.rb +101 -0
  21. data/docs/pages/rails/getting_started.rb +53 -0
  22. data/docs/pages/rails/helpers.rb +53 -0
  23. data/docs/pages/rails/layouts.rb +61 -0
  24. data/docs/pages/rails/migrating.rb +37 -0
  25. data/docs/pages/rails/rendering_views.rb +35 -0
  26. data/docs/pages/templates.rb +51 -149
  27. data/docs/pages/views.rb +55 -94
  28. data/fixtures/dummy/app/components/comment_component.html.erb +14 -0
  29. data/fixtures/dummy/app/components/comment_component.rb +8 -0
  30. data/fixtures/dummy/app/components/reaction_component.html.erb +3 -0
  31. data/fixtures/dummy/app/components/reaction_component.rb +7 -0
  32. data/fixtures/dummy/app/controllers/comments_controller.rb +4 -0
  33. data/fixtures/dummy/app/views/articles/form.rb +2 -0
  34. data/fixtures/dummy/app/views/card.rb +3 -1
  35. data/fixtures/dummy/app/views/comments/comment.rb +25 -0
  36. data/fixtures/dummy/app/views/comments/index.html.erb +3 -0
  37. data/fixtures/dummy/app/views/comments/reaction.rb +17 -0
  38. data/fixtures/dummy/app/views/comments/show.html.erb +3 -0
  39. data/fixtures/test_helper.rb +3 -0
  40. data/lib/generators/phlex/collection/USAGE +8 -0
  41. data/lib/generators/phlex/collection/collection_generator.rb +13 -0
  42. data/lib/generators/phlex/collection/templates/collection.rb.erb +15 -0
  43. data/lib/generators/phlex/layout/USAGE +8 -0
  44. data/lib/generators/phlex/layout/layout_generator.rb +13 -0
  45. data/lib/generators/phlex/layout/templates/layout.rb.erb +30 -0
  46. data/lib/generators/phlex/page/USAGE +8 -0
  47. data/lib/generators/phlex/page/page_generator.rb +13 -0
  48. data/lib/generators/phlex/page/templates/page.rb.erb +11 -0
  49. data/lib/generators/phlex/table/USAGE +8 -0
  50. data/lib/generators/phlex/table/table_generator.rb +14 -0
  51. data/lib/generators/phlex/table/templates/table.rb.erb +9 -0
  52. data/lib/phlex/collection.rb +58 -0
  53. data/lib/phlex/engine.rb +0 -3
  54. data/lib/phlex/html.rb +7 -13
  55. data/lib/phlex/rails/helpers.rb +81 -0
  56. data/lib/phlex/rails/layout.rb +15 -0
  57. data/lib/phlex/table.rb +104 -0
  58. data/lib/phlex/version.rb +1 -1
  59. data/lib/phlex/view.rb +10 -4
  60. data/lib/phlex.rb +1 -0
  61. metadata +52 -3
  62. data/lib/phlex/rails/tag_helpers.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c121a23175be64626a96f07d72e4946327bf6c152bfe4492301b26d966013969
4
- data.tar.gz: 12a03aa7e70678c0663741cec8015a8aec7ed13fd0d76cda379708bb17a0662a
3
+ metadata.gz: 5606a60fd7cce19c44c13c26ec1abb42f52252b71208aa77f7cecf49f95910be
4
+ data.tar.gz: 0c55bae86c2f14514108f7d58ab9c6e7d3bc88b5a89cc8cbb7156b86e355041d
5
5
  SHA512:
6
- metadata.gz: dff05a4aea349c9bfc92d6536caff37d8b903a9993ddc1b37e839e6705c7754977dc9bec2315e7b4ebb9c27b450fc93d7b73385dbc5023231117135e27dcf965
7
- data.tar.gz: f3619617c75faccb730653fbdf97bec1ab830079a56c703d4089ea1571f64171cc8552e1699e1c2e1560a0a10cf6b82d13e95c0bd24db4dd658b29476d617cba
6
+ metadata.gz: 60e835cad9721dcf6c353d84963a93bb2518b307df493f9a4c2fcaf737a69bdb9b7c32ed5b9e8a5c0049c1fea6445e77d75988de7998d4b65ac4b9781dc038e0
7
+ data.tar.gz: 664c9e1fb555019b40085ad49de87bc2f99619d19157603445590a7179390a52ba66dee614896d8c5901abce5fce1f34031a3b1a6e03931bcad76ba41b48e035
data/.editorconfig CHANGED
@@ -3,3 +3,11 @@ root = true
3
3
  [*]
4
4
  indent_style = tab
5
5
  indent_size = 2
6
+
7
+ [*.yml]
8
+ indent_style = space
9
+ indent_size = 2
10
+
11
+ [*.erb]
12
+ indent_style = space
13
+ indent_size = 2
data/.rubocop.yml CHANGED
@@ -1,10 +1,6 @@
1
- inherit_from: "https://www.goodcop.style"
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
@@ -0,0 +1,3 @@
1
+ server: ruby -run -e httpd docs/dist
2
+ docs: bundle exec docs/build.rb --watch
3
+ tailwind: npx tailwindcss -i ./docs/assets/application.css -o ./docs/dist/application.css --watch
data/Rakefile CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
 
5
- begin
6
- require "rspec/core/rake_task"
7
- RSpec::Core::RakeTask.new(:spec)
5
+ task :sus do
6
+ sh "bundle exec sus"
8
7
  end
9
-
10
- task default: :spec
8
+ task default: :sus
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.tap do |loader|
11
- loader.push_dir(__dir__)
12
- loader.ignore(__FILE__)
13
- loader.setup
14
- loader.eager_load
15
- end
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
- system "npx tailwindcss -i ./docs/assets/application.css -o ./docs/dist/application.css"
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class CodeSpan < Phlex::View
5
+ def template(&block)
6
+ code(class: "bg-stone-50 inline-block font-medium rounded border px-1 -mt-1", &block)
7
+ end
8
+ end
9
+ end
@@ -24,7 +24,7 @@ module Components
24
24
  def execute(code)
25
25
  output = @sandbox.class_eval(code)
26
26
 
27
- @t.tab("HTML Output") do
27
+ @t.tab("👀 Output") do
28
28
  render CodeBlock.new(HtmlBeautifier.beautify(output), syntax: :html)
29
29
  end
30
30
  end
@@ -3,7 +3,7 @@
3
3
  module Components
4
4
  class Heading < Phlex::View
5
5
  def template(&block)
6
- h2(class: "text-xl font-bold mt-10 mb-5", &block)
6
+ h2(class: "text-2xl font-semibold mt-10 mb-5") { raw(&block) }
7
7
  end
8
8
  end
9
9
  end
@@ -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: "p-12" do
23
- div class: "max-w-screen-lg mx-auto grid grid-cols-4 gap-10" do
24
- header class: "col-span-1" do
25
- a(href: "/", class: "block") { img src: "/assets/logo.png", width: "150" }
26
-
27
- nav do
28
- ul do
29
- li { a "Introduction", href: "/" }
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
- main(class: "col-span-3", &block)
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
- footer class: "text-sm text-right col-span-4 py-10"
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
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Components
4
+ class Nav < Phlex::View
5
+ end
6
+ end
@@ -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 @name, 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"
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)
@@ -3,7 +3,7 @@
3
3
  module Components
4
4
  class Title < Phlex::View
5
5
  def template(&block)
6
- h1(class: "text-2xl font-bold my-5", &block)
6
+ h1(class: "text-3xl font-semibold my-5") { raw(&block) }
7
7
  end
8
8
  end
9
9
  end
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
- 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,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