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.
- 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 +3 -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
- data/lib/phlex.rb +1 -0
- metadata +52 -3
- data/lib/phlex/rails/tag_helpers.rb +0 -29
data/docs/pages/views.rb
CHANGED
@@ -3,107 +3,69 @@
|
|
3
3
|
module Pages
|
4
4
|
class Views < ApplicationPage
|
5
5
|
def template
|
6
|
-
render Layout.new(title: "
|
6
|
+
render Layout.new(title: "Phlex Views") do
|
7
7
|
render Markdown.new(<<~MD)
|
8
8
|
# Views
|
9
9
|
|
10
|
-
|
10
|
+
Phlex Views are Ruby objects that represent your app's user interface — from pages and layouts and nav-bars, to headings and buttons and links.
|
11
11
|
|
12
|
-
|
12
|
+
You can create a view class by subclassing `Phlex::View` and defining a `template` instance method.
|
13
13
|
MD
|
14
14
|
|
15
15
|
render Example.new do |e|
|
16
|
-
e.tab "
|
17
|
-
class
|
18
|
-
def template
|
19
|
-
|
20
|
-
h1 "Amazing content!"
|
21
|
-
yield_content(&)
|
22
|
-
}
|
16
|
+
e.tab "hello.rb", <<~RUBY
|
17
|
+
class Hello < Phlex::View
|
18
|
+
def template
|
19
|
+
h1 { "👋 Hello World!" }
|
23
20
|
end
|
24
21
|
end
|
25
22
|
RUBY
|
26
23
|
|
27
|
-
e.execute "
|
24
|
+
e.execute "Hello.new.call"
|
28
25
|
end
|
29
26
|
|
30
27
|
render Markdown.new(<<~MD)
|
31
|
-
|
28
|
+
The `template` method determines what your view will output when its rendered. The above example will output an `<h1>` tag with the content `👋 Hello world!`. Click on the "Output" tab above to see for yourself.
|
32
29
|
|
33
|
-
|
34
|
-
MD
|
30
|
+
## Accepting arguments
|
35
31
|
|
36
|
-
|
37
|
-
e.tab "card.rb", <<~RUBY
|
38
|
-
class Card < Phlex::View
|
39
|
-
def template(&)
|
40
|
-
article(class: "drop-shadow rounded p-5", &)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
RUBY
|
32
|
+
You can define an initializer for your views just like any other Ruby class. Let's make our `Hello` view take a `name` as a keyword argument, save it in an instance variable and render that variable in the template.
|
44
33
|
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
render Markdown.new(<<~MD)
|
49
|
-
## Nested views
|
50
|
-
|
51
|
-
Components can render other views and optionally pass them content as a block.
|
34
|
+
We'll render this view with the arguments `name: "Joel"` and see what it produces.
|
52
35
|
MD
|
53
36
|
|
54
37
|
render Example.new do |e|
|
55
|
-
e.tab "
|
56
|
-
class
|
57
|
-
def
|
58
|
-
|
59
|
-
h1 "Hello"
|
60
|
-
end
|
38
|
+
e.tab "hello.rb", <<~RUBY
|
39
|
+
class Hello < Phlex::View
|
40
|
+
def initialize(name:)
|
41
|
+
@name = name
|
61
42
|
end
|
62
|
-
end
|
63
|
-
RUBY
|
64
43
|
|
65
|
-
|
66
|
-
|
67
|
-
def template(&)
|
68
|
-
article(class: "drop-shadow rounded p-5", &)
|
44
|
+
def template
|
45
|
+
h1 { "👋 Hello \#{@name}!" }
|
69
46
|
end
|
70
47
|
end
|
71
48
|
RUBY
|
72
49
|
|
73
|
-
e.execute "
|
50
|
+
e.execute "Hello.new(name: 'Joel').call"
|
74
51
|
end
|
75
52
|
|
76
53
|
render Markdown.new(<<~MD)
|
77
|
-
|
54
|
+
## Rendering views
|
55
|
+
|
56
|
+
Views can render other views in their templates using the `render` method. Let's try rendering a couple of instances of this `Hello` view from a new `Example` view and look at the output of the `Example` view.
|
78
57
|
MD
|
79
58
|
|
80
59
|
render Example.new do |e|
|
81
60
|
e.tab "example.rb", <<~RUBY
|
82
61
|
class Example < Phlex::View
|
83
62
|
def template
|
84
|
-
render
|
85
|
-
|
86
|
-
end
|
87
|
-
RUBY
|
88
|
-
|
89
|
-
e.tab "card.rb", <<~RUBY
|
90
|
-
class Card < Phlex::View
|
91
|
-
def template(&)
|
92
|
-
article(class: "drop-shadow rounded p-5", &)
|
63
|
+
render Hello.new(name: "Joel")
|
64
|
+
render Hello.new(name: "Alexandre")
|
93
65
|
end
|
94
66
|
end
|
95
67
|
RUBY
|
96
68
|
|
97
|
-
e.execute "Example.new.call"
|
98
|
-
end
|
99
|
-
|
100
|
-
render Markdown.new(<<~MD)
|
101
|
-
## Component attributes
|
102
|
-
|
103
|
-
Besides content, views can define attributes in an initializer, which can then be rendered in the template.
|
104
|
-
MD
|
105
|
-
|
106
|
-
render Example.new do |e|
|
107
69
|
e.tab "hello.rb", <<~RUBY
|
108
70
|
class Hello < Phlex::View
|
109
71
|
def initialize(name:)
|
@@ -111,15 +73,7 @@ module Pages
|
|
111
73
|
end
|
112
74
|
|
113
75
|
def template
|
114
|
-
h1 "Hello \#{@name}!"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
RUBY
|
118
|
-
|
119
|
-
e.tab "example.rb", <<~RUBY
|
120
|
-
class Example < Phlex::View
|
121
|
-
def template
|
122
|
-
render Hello.new(name: "Joel")
|
76
|
+
h1 { "👋 Hello \#{@name}!" }
|
123
77
|
end
|
124
78
|
end
|
125
79
|
RUBY
|
@@ -128,32 +82,17 @@ module Pages
|
|
128
82
|
end
|
129
83
|
|
130
84
|
render Markdown.new(<<~MD)
|
131
|
-
|
85
|
+
## Passing content blocks
|
132
86
|
|
133
|
-
|
134
|
-
|
135
|
-
Views are just Ruby classes, so you can perform calculations on view attributes by defining your own methods.
|
87
|
+
Views can also yield content blocks, which can be passed in when rendering. Let's make a `Card` component that yields content in an `<article>` element with a `drop-shadow` class on it.
|
136
88
|
MD
|
137
89
|
|
138
90
|
render Example.new do |e|
|
139
|
-
e.tab "
|
140
|
-
class
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
def template
|
146
|
-
span status_emoji
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
def status_emoji
|
152
|
-
case @status
|
153
|
-
when :success
|
154
|
-
"✅"
|
155
|
-
when :failure
|
156
|
-
"❌"
|
91
|
+
e.tab "card.rb", <<~RUBY
|
92
|
+
class Card < Phlex::View
|
93
|
+
def template(&content)
|
94
|
+
article(class: "drop-shadow") do
|
95
|
+
yield_content(&content)
|
157
96
|
end
|
158
97
|
end
|
159
98
|
end
|
@@ -162,13 +101,35 @@ module Pages
|
|
162
101
|
e.tab "example.rb", <<~RUBY
|
163
102
|
class Example < Phlex::View
|
164
103
|
def template
|
165
|
-
render
|
104
|
+
render Card.new do
|
105
|
+
h1 { "👋 Hello!" }
|
106
|
+
end
|
166
107
|
end
|
167
108
|
end
|
168
109
|
RUBY
|
169
110
|
|
170
111
|
e.execute "Example.new.call"
|
171
112
|
end
|
113
|
+
|
114
|
+
render Markdown.new(<<~MD)
|
115
|
+
The template in the `Card` view accepts a block argument `&content` and uses the `yield_content` method to yield it in an `<article>` element.
|
116
|
+
|
117
|
+
The `Example` view renders a `Card` and passes it a block with an `<h1>` element.
|
118
|
+
|
119
|
+
Looking at the output of the `Example` view, we can see the `<h1>` element was rendered inside the `<article>` element from the `Card` view.
|
120
|
+
|
121
|
+
## Delegating content blocks
|
122
|
+
|
123
|
+
Since the block of content was the only thing we need in the `<article>` element, we could have just passed the content block to the element instead.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Card < Phlex::View
|
127
|
+
def template(&content)
|
128
|
+
article(class: "drop-shadow", &content)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
MD
|
172
133
|
end
|
173
134
|
end
|
174
135
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<div>
|
2
|
+
<span>
|
3
|
+
<%= @name %>
|
4
|
+
</span>
|
5
|
+
<span>
|
6
|
+
<%= @body %>
|
7
|
+
</span>
|
8
|
+
|
9
|
+
<%= content %>
|
10
|
+
|
11
|
+
<%= render Views::Comments::Reaction.new(emoji: 'hamburger') do |reaction| %>
|
12
|
+
<p>Emoji reaction for a comment from <%= @name %> with body <%= @body %></p>
|
13
|
+
<% end %>
|
14
|
+
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Views
|
4
|
+
module Comments
|
5
|
+
class Comment < Phlex::View
|
6
|
+
def initialize(name:, body:)
|
7
|
+
@name = name
|
8
|
+
@body = body
|
9
|
+
end
|
10
|
+
|
11
|
+
def template(&block)
|
12
|
+
div {
|
13
|
+
span { @name }
|
14
|
+
span { @body }
|
15
|
+
|
16
|
+
yield_content(&block)
|
17
|
+
|
18
|
+
render(::ReactionComponent.new(emoji: "hamburger")) do
|
19
|
+
p { "Emoji reaction for a comment from #{@name} with body #{@body}" }
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/fixtures/test_helper.rb
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Generators
|
5
|
+
class CollectionGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
def create_view
|
9
|
+
template "collection.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Generators
|
5
|
+
class LayoutGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
def create_view
|
9
|
+
template "layout.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<% module_namespacing do -%>
|
2
|
+
module Views
|
3
|
+
class <%= class_name %> < ApplicationView
|
4
|
+
include Phlex::Rails::Layout
|
5
|
+
|
6
|
+
def initialize(title:)
|
7
|
+
@title = title
|
8
|
+
end
|
9
|
+
|
10
|
+
def template(&)
|
11
|
+
doctype
|
12
|
+
|
13
|
+
html do
|
14
|
+
head do
|
15
|
+
meta charset: "utf-8"
|
16
|
+
csp_meta_tag
|
17
|
+
csrf_meta_tags
|
18
|
+
meta name: "viewport", content: "width=device-width,initial-scale=1"
|
19
|
+
title { @title }
|
20
|
+
stylesheet_link_tag "application"
|
21
|
+
end
|
22
|
+
|
23
|
+
body do
|
24
|
+
main(&)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Generators
|
5
|
+
class PageGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
def create_view
|
9
|
+
template "page.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Generators
|
5
|
+
class TableGenerator < ::Rails::Generators::NamedBase
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
class_option :properties, type: :array, default: []
|
8
|
+
|
9
|
+
def create_view
|
10
|
+
template "table.rb.erb", File.join("app/views", class_path, "#{file_name}.rb")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Phlex
|
4
|
+
module Collection
|
5
|
+
def initialize(collection: nil, item: nil)
|
6
|
+
unless collection || item
|
7
|
+
raise ArgumentError, "You must pass a collection or an item as a keyword argument."
|
8
|
+
end
|
9
|
+
|
10
|
+
@collection = collection
|
11
|
+
@item = item
|
12
|
+
end
|
13
|
+
|
14
|
+
def template
|
15
|
+
if @item
|
16
|
+
item_template
|
17
|
+
else
|
18
|
+
collection_template { yield_items }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def yield_items
|
25
|
+
if @item
|
26
|
+
raise ArgumentError, "You can only yield_items when rendering a collection. You are currently rendering an item."
|
27
|
+
end
|
28
|
+
|
29
|
+
@collection.each_with_index do |item, index|
|
30
|
+
@item = item
|
31
|
+
@index = index
|
32
|
+
@position = (index + 1)
|
33
|
+
@first = (index == 0)
|
34
|
+
@last = (@position == @collection.size)
|
35
|
+
|
36
|
+
item_template
|
37
|
+
end
|
38
|
+
|
39
|
+
@item = nil
|
40
|
+
@index = nil
|
41
|
+
@first = nil
|
42
|
+
@last = nil
|
43
|
+
@position = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def first?
|
47
|
+
raise ArgumentError unless @item
|
48
|
+
|
49
|
+
@first
|
50
|
+
end
|
51
|
+
|
52
|
+
def last?
|
53
|
+
raise ArgumentError unless @item
|
54
|
+
|
55
|
+
@last
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/phlex/engine.rb
CHANGED
data/lib/phlex/html.rb
CHANGED
@@ -113,6 +113,7 @@ module Phlex
|
|
113
113
|
area: "area",
|
114
114
|
br: "br",
|
115
115
|
embed: "embed",
|
116
|
+
hr: "hr",
|
116
117
|
img: "img",
|
117
118
|
input: "input",
|
118
119
|
link: "link",
|
@@ -130,10 +131,12 @@ module Phlex
|
|
130
131
|
# frozen_string_literal: true
|
131
132
|
|
132
133
|
def #{element}(content = nil, **attributes, &block)
|
134
|
+
if content
|
135
|
+
raise ArgumentError, %(👋 You can no longer pass content to #{element} as a positional argument.\n Instead, you can pass it as a block, e.g. #{element} { "Hello" })
|
136
|
+
end
|
137
|
+
|
133
138
|
if attributes.length > 0
|
134
|
-
if
|
135
|
-
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">" << CGI.escape_html(content) << "</#{tag}>"
|
136
|
-
elsif block_given?
|
139
|
+
if block_given?
|
137
140
|
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << ">"
|
138
141
|
yield_content(&block)
|
139
142
|
@_target << "</#{tag}>"
|
@@ -141,16 +144,7 @@ module Phlex
|
|
141
144
|
@_target << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes.hash] || _attributes(attributes)) << "></#{tag}>"
|
142
145
|
end
|
143
146
|
else
|
144
|
-
if
|
145
|
-
case content
|
146
|
-
when String
|
147
|
-
@_target << "<#{tag}>" << CGI.escape_html(content) << "</#{tag}>"
|
148
|
-
when Symbol
|
149
|
-
@_target << "<#{tag}>" << CGI.escape_html(content.name) << "</#{tag}>"
|
150
|
-
else
|
151
|
-
@_target << "<#{tag}>" << CGI.escape_html(content.to_s) << "</#{tag}>"
|
152
|
-
end
|
153
|
-
elsif block_given?
|
147
|
+
if block_given?
|
154
148
|
@_target << "<#{tag}>"
|
155
149
|
yield_content(&block)
|
156
150
|
@_target << "</#{tag}>"
|