rblade 0.4.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/README.md +115 -517
- data/lib/rblade/compiler/compiles_components.rb +29 -11
- data/lib/rblade/compiler/compiles_echos.rb +4 -4
- data/lib/rblade/compiler/compiles_ruby.rb +1 -1
- data/lib/rblade/compiler/compiles_statements.rb +21 -13
- data/lib/rblade/compiler/statements/compiles_conditionals.rb +40 -0
- data/lib/rblade/compiler/statements/compiles_form.rb +48 -0
- data/lib/rblade/compiler/statements/compiles_props.rb +23 -4
- data/lib/rblade/compiler/statements/compiles_stacks.rb +39 -15
- data/lib/rblade/compiler/tokenizes_statements.rb +13 -2
- data/lib/rblade/compiler.rb +9 -0
- data/lib/rblade/component_store.rb +1 -1
- data/lib/rblade/helpers/attributes_manager.rb +37 -7
- data/lib/rblade/helpers/html_string.rb +4 -0
- data/lib/rblade/helpers/slot_manager.rb +31 -0
- data/lib/rblade/helpers/stack_manager.rb +4 -0
- data/lib/rblade/rails_template.rb +1 -0
- data/rblade.gemspec +1 -1
- metadata +9 -7
- data/TODO.md +0 -2
data/README.md
CHANGED
@@ -1,12 +1,41 @@
|
|
1
1
|
# RBlade Templates
|
2
2
|
|
3
|
-
- [
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
- [RBlade Templates](#rblade-templates)
|
4
|
+
* [Introduction](#introduction)
|
5
|
+
* [Displaying Data](#displaying-data)
|
6
|
+
+ [HTML Entity Encoding](#html-entity-encoding)
|
7
|
+
+ [Blade and JavaScript Frameworks](#blade-and-javascript-frameworks)
|
8
|
+
- [The `@verbatim` Directive](#the-at-verbatim-directive)
|
9
|
+
* [RBlade Directives](#rblade-directives)
|
10
|
+
+ [If Statements](#if-statements)
|
11
|
+
- [Environment Directives](#environment-directives)
|
12
|
+
+ [Case Statements](#case-statements)
|
13
|
+
+ [Loops](#loops)
|
14
|
+
+ [Conditional Classes & Styles](#conditional-classes-and-styles)
|
15
|
+
+ [Additional Attributes](#additional-attributes)
|
16
|
+
+ [The `@once` Directive](#the-once-directive)
|
17
|
+
+ [Raw Ruby](#raw-ruby)
|
18
|
+
+ [Comments](#comments)
|
19
|
+
* [Components](#components)
|
20
|
+
+ [Rendering Components](#rendering-components)
|
21
|
+
- [Namespaces](#namespaces)
|
22
|
+
+ [Passing Data to Components](#passing-data-to-components)
|
23
|
+
- [Component Properties](#component-properties)
|
24
|
+
- [Short Attribute Syntax](#short-attribute-syntax)
|
25
|
+
- [Escaping Attribute Rendering](#escaping-attribute-rendering)
|
26
|
+
+ [Component Attributes](#component-attributes)
|
27
|
+
- [Default & Merged Attributes](#default-and-merged-attributes)
|
28
|
+
- [Non-Class Attribute Merging](#non-class-attribute-merging)
|
29
|
+
- [Conditionally Merge Classes](#conditionally-merge-classes)
|
30
|
+
- [Retrieving and Filtering Attributes](#retrieving-and-filtering-attributes)
|
31
|
+
+ [Slots](#slots)
|
32
|
+
- [Slot Attributes](#slot-attributes)
|
33
|
+
+ [Registering Additional Component Directories](#registering-additional-component-directories)
|
34
|
+
+ [Index Components](#index-components)
|
35
|
+
* [Forms](#forms)
|
36
|
+
+ [Old Input](#old-input)
|
37
|
+
+ [Method Field](#method-field)
|
38
|
+
* [Stacks](#stacks)
|
10
39
|
|
11
40
|
<a name="introduction"></a>
|
12
41
|
## Introduction
|
@@ -31,9 +60,9 @@ Hello, {{ name }}.
|
|
31
60
|
```
|
32
61
|
|
33
62
|
> [!NOTE]
|
34
|
-
> RBlade's `{{ }}`
|
63
|
+
> RBlade's `{{ }}` print statements are automatically sent through Rails' `h` function to prevent XSS attacks.
|
35
64
|
|
36
|
-
You are not limited to displaying the contents of the variables passed to the view. You may also print the results of any Ruby function. In fact, you can put any Ruby code you wish inside of a
|
65
|
+
You are not limited to displaying the contents of the variables passed to the view. You may also print the results of any Ruby function. In fact, you can put any Ruby code you wish inside of a RBlade print directive:
|
37
66
|
|
38
67
|
```rblade
|
39
68
|
The current UNIX timestamp is {{ Time.now.to_i }}.
|
@@ -77,7 +106,7 @@ The `@` symbol may also be used to escape RBlade directives:
|
|
77
106
|
<a name="the-at-verbatim-directive"></a>
|
78
107
|
#### The `@verbatim` Directive
|
79
108
|
|
80
|
-
If you are displaying JavaScript variables in a large portion of your template, you may wrap the HTML in the `@verbatim` directive so that you do not have to prefix each Blade
|
109
|
+
If you are displaying JavaScript variables in a large portion of your template, you may wrap the HTML in the `@verbatim` directive so that you do not have to prefix each Blade print directive with an `@` symbol:
|
81
110
|
|
82
111
|
```rblade
|
83
112
|
@verbatim
|
@@ -98,57 +127,28 @@ In addition to template inheritance and displaying data, RBlade also provides co
|
|
98
127
|
<a name="if-statements"></a>
|
99
128
|
### If Statements
|
100
129
|
|
101
|
-
You may construct `if` statements using the `@if`, `@
|
130
|
+
You may construct `if` statements using the `@if`, `@elseIf`, `@else`, `@endIf`, `@unless`, and `@endUnless` directives. These directives function identically to their Ruby counterparts:
|
102
131
|
|
103
132
|
```rblade
|
104
133
|
@unless(records.nil?)
|
105
134
|
@if (records.count === 1)
|
106
135
|
I have one record!
|
107
|
-
@
|
136
|
+
@elseIf (records.count > 1)
|
108
137
|
I have multiple records!
|
109
138
|
@else
|
110
139
|
I don't have any records!
|
111
|
-
@
|
112
|
-
@
|
140
|
+
@endIf
|
141
|
+
@endUnless
|
113
142
|
```
|
114
143
|
|
115
|
-
In addition to the conditional directives
|
116
|
-
**TODO add nil? directive?**
|
117
|
-
```rblade
|
118
|
-
@isset($records)
|
119
|
-
// $records is defined and is not null...
|
120
|
-
@endisset
|
121
|
-
|
122
|
-
@empty($records)
|
123
|
-
// $records is "empty"...
|
124
|
-
@endempty
|
125
|
-
```
|
126
|
-
|
127
|
-
<a name="authentication-directives"></a>
|
128
|
-
#### Authentication Directives
|
129
|
-
**TODO add authentication directives?**
|
130
|
-
The `@auth` and `@guest` directives may be used to quickly determine if the current user is [authenticated](/docs/{{version}}/authentication) or is a guest:
|
131
|
-
|
132
|
-
```rblade
|
133
|
-
@auth
|
134
|
-
// The user is authenticated...
|
135
|
-
@endauth
|
136
|
-
|
137
|
-
@guest
|
138
|
-
// The user is not authenticated...
|
139
|
-
@endguest
|
140
|
-
```
|
141
|
-
|
142
|
-
If needed, you may specify the authentication guard that should be checked when using the `@auth` and `@guest` directives:
|
144
|
+
In addition to the conditional directives above, the `@blank?`, `defined?`, `@empty?`, `@nil?` and `@present` directives may be used as convenient shortcuts:
|
143
145
|
|
144
146
|
```rblade
|
145
|
-
@
|
146
|
-
//
|
147
|
-
@
|
148
|
-
|
149
|
-
@
|
150
|
-
// The user is not authenticated...
|
151
|
-
@endguest
|
147
|
+
@present?(records)
|
148
|
+
// records is defined and is not nil
|
149
|
+
@else
|
150
|
+
// These directives are shortcuts to if statements so @else may be used
|
151
|
+
@endempty?
|
152
152
|
```
|
153
153
|
|
154
154
|
<a name="environment-directives"></a>
|
@@ -159,7 +159,7 @@ You may check if the application is running in the production environment using
|
|
159
159
|
```rblade
|
160
160
|
@production
|
161
161
|
// Production specific content...
|
162
|
-
@
|
162
|
+
@endProduction
|
163
163
|
```
|
164
164
|
|
165
165
|
Or, you may determine if the application is running in a specific environment using the `@env` directive:
|
@@ -167,30 +167,17 @@ Or, you may determine if the application is running in a specific environment us
|
|
167
167
|
```rblade
|
168
168
|
@env('staging')
|
169
169
|
// The application is running in "staging"...
|
170
|
-
@
|
170
|
+
@endEnv
|
171
171
|
|
172
172
|
@env(['staging', 'production'])
|
173
173
|
// The application is running in "staging" or "production"...
|
174
|
-
@
|
175
|
-
```
|
176
|
-
|
177
|
-
<a name="session-directives"></a>
|
178
|
-
#### Session Directives
|
179
|
-
**TODO add sessuib directives**
|
180
|
-
The `@session` directive may be used to determine if a [session](/docs/{{version}}/session) value exists. If the session value exists, the template contents within the `@session` and `@endsession` directives will be evaluated. Within the `@session` directive's contents, you may echo the `$value` variable to display the session value:
|
181
|
-
|
182
|
-
```rblade
|
183
|
-
@session('status')
|
184
|
-
<div class="p-4 bg-green-100">
|
185
|
-
{{ $value }}
|
186
|
-
</div>
|
187
|
-
@endsession
|
174
|
+
@endEnv
|
188
175
|
```
|
189
176
|
|
190
177
|
<a name="switch-statements"></a>
|
191
178
|
### Case Statements
|
192
179
|
|
193
|
-
Case statements can be constructed using the `@case`, `@when`, `@else` and `@
|
180
|
+
Case statements can be constructed using the `@case`, `@when`, `@else` and `@endCase` directives:
|
194
181
|
|
195
182
|
```rblade
|
196
183
|
@case(i)
|
@@ -200,7 +187,7 @@ Case statements can be constructed using the `@case`, `@when`, `@else` and `@end
|
|
200
187
|
Second case...
|
201
188
|
@else
|
202
189
|
Default case...
|
203
|
-
@
|
190
|
+
@endCase
|
204
191
|
```
|
205
192
|
|
206
193
|
<a name="loops"></a>
|
@@ -211,28 +198,28 @@ In addition to conditional statements, RBlade provides simple directives for wor
|
|
211
198
|
```rblade
|
212
199
|
@for (i in 0...10)
|
213
200
|
The current value is {{ i }}
|
214
|
-
@
|
201
|
+
@endFor
|
215
202
|
|
216
203
|
{{-- Compiles to users.each do |user| ... --}}
|
217
204
|
@each (user in users)
|
218
205
|
<p>This is user {{ user.id }}</p>
|
219
|
-
@
|
206
|
+
@endEach
|
220
207
|
|
221
|
-
@
|
208
|
+
@forElse (name in [])
|
222
209
|
<li>{{ name }}</li>
|
223
210
|
@empty
|
224
211
|
<p>No names</p>
|
225
|
-
@
|
212
|
+
@endForElse
|
226
213
|
|
227
|
-
@
|
214
|
+
@eachElse (user in users)
|
228
215
|
<li>{{ user.name }}</li>
|
229
216
|
@empty
|
230
217
|
<p>No users</p>
|
231
|
-
@
|
218
|
+
@endEachElse
|
232
219
|
|
233
220
|
@while (true)
|
234
221
|
<p>I'm looping forever.</p>
|
235
|
-
@
|
222
|
+
@endWhile
|
236
223
|
```
|
237
224
|
|
238
225
|
When using loops you can also skip the current iteration or end the loop using the `@next` and `@break` directives:
|
@@ -241,79 +228,29 @@ When using loops you can also skip the current iteration or end the loop using t
|
|
241
228
|
for (user in users)
|
242
229
|
@if (user.type == 1)
|
243
230
|
@next
|
244
|
-
@
|
231
|
+
@endIf
|
245
232
|
|
246
233
|
<li>{{ user.name }}</li>
|
247
234
|
|
248
235
|
@if (user.number == 5)
|
249
236
|
@break
|
250
|
-
@
|
251
|
-
@
|
237
|
+
@endIf
|
238
|
+
@endFor
|
252
239
|
```
|
253
240
|
|
254
241
|
You may also include the continuation or break condition within the directive declaration:
|
255
242
|
|
256
243
|
```rblade
|
257
|
-
@
|
244
|
+
@for (user in users)
|
258
245
|
@next(user.type == 1)
|
259
246
|
|
260
|
-
<li>{{
|
247
|
+
<li>{{ user.name }}</li>
|
261
248
|
|
262
249
|
@break(user.number == 5)
|
263
|
-
@
|
264
|
-
```
|
265
|
-
|
266
|
-
<a name="the-loop-variable"></a>
|
267
|
-
### The Loop Variable
|
268
|
-
**TODO can/should we add this?**
|
269
|
-
While iterating through a `foreach` loop, a `$loop` variable will be available inside of your loop. This variable provides access to some useful bits of information such as the current loop index and whether this is the first or last iteration through the loop:
|
270
|
-
|
271
|
-
```rblade
|
272
|
-
@foreach ($users as $user)
|
273
|
-
@if ($loop->first)
|
274
|
-
This is the first iteration.
|
275
|
-
@endif
|
276
|
-
|
277
|
-
@if ($loop->last)
|
278
|
-
This is the last iteration.
|
279
|
-
@endif
|
280
|
-
|
281
|
-
<p>This is user {{ $user->id }}</p>
|
282
|
-
@endforeach
|
283
|
-
```
|
284
|
-
|
285
|
-
If you are in a nested loop, you may access the parent loop's `$loop` variable via the `parent` property:
|
286
|
-
|
287
|
-
```rblade
|
288
|
-
@foreach ($users as $user)
|
289
|
-
@foreach ($user->posts as $post)
|
290
|
-
@if ($loop->parent->first)
|
291
|
-
This is the first iteration of the parent loop.
|
292
|
-
@endif
|
293
|
-
@endforeach
|
294
|
-
@endforeach
|
250
|
+
@endFor
|
295
251
|
```
|
296
252
|
|
297
|
-
|
298
|
-
|
299
|
-
<div class="overflow-auto">
|
300
|
-
|
301
|
-
| Property | Description |
|
302
|
-
| ------------------ | ------------------------------------------------------ |
|
303
|
-
| `$loop->index` | The index of the current loop iteration (starts at 0). |
|
304
|
-
| `$loop->iteration` | The current loop iteration (starts at 1). |
|
305
|
-
| `$loop->remaining` | The iterations remaining in the loop. |
|
306
|
-
| `$loop->count` | The total number of items in the array being iterated. |
|
307
|
-
| `$loop->first` | Whether this is the first iteration through the loop. |
|
308
|
-
| `$loop->last` | Whether this is the last iteration through the loop. |
|
309
|
-
| `$loop->even` | Whether this is an even iteration through the loop. |
|
310
|
-
| `$loop->odd` | Whether this is an odd iteration through the loop. |
|
311
|
-
| `$loop->depth` | The nesting level of the current loop. |
|
312
|
-
| `$loop->parent` | When in a nested loop, the parent's loop variable. |
|
313
|
-
|
314
|
-
</div>
|
315
|
-
|
316
|
-
<a name="conditional-classes"></a>
|
253
|
+
<a name="conditional-classes-and-styles"></a>
|
317
254
|
### Conditional Classes & Styles
|
318
255
|
|
319
256
|
The `@class` directive conditionally adds CSS classes. The directive accepts a `Hash` of classes where the key contains the class or classes you wish to add, and the value is a boolean expression:
|
@@ -322,7 +259,7 @@ The `@class` directive conditionally adds CSS classes. The directive accepts a `
|
|
322
259
|
@ruby
|
323
260
|
isActive = false;
|
324
261
|
hasError = true;
|
325
|
-
@
|
262
|
+
@endRuby
|
326
263
|
|
327
264
|
<span @class({
|
328
265
|
"p-4": true,
|
@@ -339,7 +276,7 @@ Likewise, the `@style` directive may be used to conditionally add inline CSS sty
|
|
339
276
|
```rblade
|
340
277
|
@ruby
|
341
278
|
isActive = true;
|
342
|
-
@
|
279
|
+
@endRuby
|
343
280
|
|
344
281
|
<span @style({
|
345
282
|
"background-color: red": true,
|
@@ -369,7 +306,7 @@ Likewise, the `@selected` directive may be used to indicate if a given select op
|
|
369
306
|
<option value="{{ version }}" @selected(version == selectedVersion)>
|
370
307
|
{{ version }}
|
371
308
|
</option>
|
372
|
-
@
|
309
|
+
@endEach
|
373
310
|
</select>
|
374
311
|
```
|
375
312
|
|
@@ -446,20 +383,7 @@ In some situations, it's useful to embed Ruby code into your views. You can use
|
|
446
383
|
```rblade
|
447
384
|
@ruby
|
448
385
|
counter = 1;
|
449
|
-
@
|
450
|
-
```
|
451
|
-
|
452
|
-
**TODO add require?**
|
453
|
-
Or, if you only need to use PHP to import a class, you may use the `@use` directive:
|
454
|
-
|
455
|
-
```rblade
|
456
|
-
@use('App\Models\Flight')
|
457
|
-
```
|
458
|
-
|
459
|
-
A second argument may be provided to the `@use` directive to alias the imported class:
|
460
|
-
|
461
|
-
```php
|
462
|
-
@use('App\Models\Flight', 'FlightModel')
|
386
|
+
@endRuby
|
463
387
|
```
|
464
388
|
|
465
389
|
<a name="comments"></a>
|
@@ -530,7 +454,7 @@ You can pass data to RBlade components using HTML attributes. Hard-coded strings
|
|
530
454
|
|
531
455
|
#### Component Properties
|
532
456
|
|
533
|
-
You can define a component's data properties using a `@props`
|
457
|
+
You can define a component's data properties using a `@props` directive at the top of the component. You can then reference these properties using local variables within the template:
|
534
458
|
|
535
459
|
```rblade
|
536
460
|
{{-- alert.rblade --}}
|
@@ -538,25 +462,18 @@ You can define a component's data properties using a `@props` statement at the t
|
|
538
462
|
<div class="{{ type }}">{{ message }}</div>
|
539
463
|
```
|
540
464
|
|
541
|
-
The `@props`
|
465
|
+
The `@props` directive accepts a `Hash` where the key is the name of the attribute, and the value is the default value for the property. You can use the special `_required` value to represent a property with no default that must always be defined:
|
542
466
|
|
543
467
|
```rblade
|
544
468
|
{{-- This will give an error because the alert component requires a message propery --}}
|
545
469
|
<x-alert/>
|
546
470
|
```
|
547
471
|
|
548
|
-
|
549
|
-
|
550
|
-
```rblade
|
551
|
-
@props({"data-value": _required})
|
552
|
-
<div>{{ data_value }}</div>
|
553
|
-
```
|
554
|
-
|
555
|
-
Properties that contain other non-alphanumeric characters or are Ruby reserved keywords are not created as local variables. However, you can reference them via the `attributes` local variable:
|
472
|
+
All properties in the `@props` directive are automatically removed from `attributes`. Properties with names that aren't valid Ruby variable names or are Ruby reserved keywords are not created as local variables. However, you can reference them via the `attributes` local variable:
|
556
473
|
|
557
474
|
```rblade
|
558
|
-
@props({"for": _required})
|
559
|
-
<div>{{ attributes[:for] }}</div>
|
475
|
+
@props({"for": _required, "data-value": nil})
|
476
|
+
<div>{{ attributes[:for] }} {{ attributes[:'data-value'] }}</div>
|
560
477
|
```
|
561
478
|
|
562
479
|
<a name="short-attribute-syntax"></a>
|
@@ -575,7 +492,7 @@ When passing attributes to components, you may also use a "short attribute" synt
|
|
575
492
|
<a name="escaping-attribute-rendering"></a>
|
576
493
|
#### Escaping Attribute Rendering
|
577
494
|
|
578
|
-
Since some JavaScript frameworks such as Alpine.js also use colon-prefixed attributes, you may use a double colon (`::`) prefix to inform RBlade that the attribute is not a
|
495
|
+
Since some JavaScript frameworks such as Alpine.js also use colon-prefixed attributes, you may use a double colon (`::`) prefix to inform RBlade that the attribute is not a Ruby expression. For example, given the following component:
|
579
496
|
|
580
497
|
```rblade
|
581
498
|
<x-button ::class="{ danger: isDeleting }">
|
@@ -600,8 +517,6 @@ We've already examined how to pass data attributes to a component; however, some
|
|
600
517
|
<x-alert type="error" :message class="mt-4"/>
|
601
518
|
```
|
602
519
|
|
603
|
-
**TODO remove from attributes bag using @props? Rename to attributes bag?**
|
604
|
-
|
605
520
|
All of the attributes that are not part of the component's constructor will automatically be added to the component's "attribute manager". This attribute manager is automatically made available to the component via the `attributes` variable. All of the attributes may be rendered within the component by printing this variable:
|
606
521
|
|
607
522
|
```rblade
|
@@ -610,8 +525,8 @@ All of the attributes that are not part of the component's constructor will auto
|
|
610
525
|
</div>
|
611
526
|
```
|
612
527
|
|
613
|
-
<a name="default-merged-attributes"></a>
|
614
|
-
#### Default
|
528
|
+
<a name="default-and-merged-attributes"></a>
|
529
|
+
#### Default & Merged Attributes
|
615
530
|
|
616
531
|
Sometimes you may need to specify default values for attributes or merge additional values into some of the component's attributes. To accomplish this, you may use the attribute manager's `merge` method. This method is particularly useful for defining a set of default CSS classes that should always be applied to a component:
|
617
532
|
|
@@ -664,31 +579,22 @@ The rendered HTML of the `button` component in this example would be:
|
|
664
579
|
</button>
|
665
580
|
```
|
666
581
|
|
667
|
-
**Todo add prepends**
|
668
|
-
If you would like an attribute other than `class` or `style` to have its default value and injected values joined together, you can use the `prepends` method. In this example, the `data-controller` attribute will always begin with `profile-controller` and any additional injected `data-controller` values will be placed after this default value:
|
669
|
-
|
670
|
-
```rblade
|
671
|
-
<div {{ attributes.merge({"data-controller": attributes.prepends("profile-controller")}) }}>
|
672
|
-
{{ slot }}
|
673
|
-
</div>
|
674
|
-
```
|
675
|
-
|
676
582
|
<a name="conditionally-merge-classes"></a>
|
677
583
|
#### Conditionally Merge Classes
|
678
|
-
|
679
|
-
Sometimes you may wish to merge classes if a given condition is `true`. You can accomplish this via the `class` method, which accepts
|
584
|
+
|
585
|
+
Sometimes you may wish to merge classes if a given condition is `true`. You can accomplish this via the `class` method, which accepts a Hash of classes where the array key contains the class or classes you wish to add, while the value is a boolean expression:
|
680
586
|
|
681
587
|
```rblade
|
682
|
-
<div {{
|
683
|
-
{{
|
588
|
+
<div {{ attributes.class({'p-4': true, 'bg-red': hasError}) }}>
|
589
|
+
{{ message }}
|
684
590
|
</div>
|
685
591
|
```
|
686
592
|
|
687
593
|
If you need to merge other attributes onto your component, you can chain the `merge` method onto the `class` method:
|
688
594
|
|
689
595
|
```rblade
|
690
|
-
<button {{
|
691
|
-
{{
|
596
|
+
<button {{ attributes.class({'bg-red': hasError}).merge({type: 'button'}) }}>
|
597
|
+
{{ slot }}
|
692
598
|
</button>
|
693
599
|
```
|
694
600
|
|
@@ -698,62 +604,39 @@ If you need to merge other attributes onto your component, you can chain the `me
|
|
698
604
|
<a name="filtering-attributes"></a>
|
699
605
|
#### Retrieving and Filtering Attributes
|
700
606
|
|
701
|
-
|
607
|
+
The attributes manager is a wrapper around the Ruby Hash class. Unless explicitly overwritten, any methods called on the attributes manager will call that same method on the underlying Hash.
|
702
608
|
|
703
|
-
You may filter attributes using the `filter`
|
609
|
+
You may filter attributes using the `filter` and `slice` methods. These methods call `filter` and `slice` on the underlying Hash and return a new attributes manager with the result.
|
704
610
|
|
705
611
|
```rblade
|
706
|
-
{{
|
612
|
+
{{ attributes.filter { |k, v| k == 'foo'} }}
|
613
|
+
{{ attributes.slice :foo }}
|
707
614
|
```
|
708
615
|
|
709
|
-
|
616
|
+
If you would like to check if an attribute is present on the component, you may use the `has?` method. This method accepts the attribute name as its only argument and returns a boolean indicating whether or not the attribute is present:
|
710
617
|
|
711
618
|
```rblade
|
712
|
-
|
713
|
-
```
|
714
|
-
|
715
|
-
Conversely, the `whereDoesntStartWith` method may be used to exclude all attributes whose keys begin with a given string:
|
716
|
-
|
717
|
-
```rblade
|
718
|
-
{{ $attributes->whereDoesntStartWith('wire:model') }}
|
719
|
-
```
|
720
|
-
|
721
|
-
Using the `first` method, you may render the first attribute in a given attribute bag:
|
722
|
-
|
723
|
-
```rblade
|
724
|
-
{{ $attributes->whereStartsWith('wire:model')->first() }}
|
725
|
-
```
|
726
|
-
|
727
|
-
If you would like to check if an attribute is present on the component, you may use the `has` method. This method accepts the attribute name as its only argument and returns a boolean indicating whether or not the attribute is present:
|
728
|
-
|
729
|
-
```rblade
|
730
|
-
@if ($attributes->has('class'))
|
619
|
+
@if (attributes.has?(:class))
|
731
620
|
<div>Class attribute is present</div>
|
732
621
|
@endif
|
733
622
|
```
|
734
623
|
|
735
|
-
If
|
624
|
+
If multiple parameters are passed to the `has?` method, the method will determine if all of the given attributes are present on the component:
|
736
625
|
|
737
626
|
```rblade
|
738
|
-
@if (
|
627
|
+
@if (attributes.has?('name', 'class'))
|
739
628
|
<div>All of the attributes are present</div>
|
740
629
|
@endif
|
741
630
|
```
|
742
631
|
|
743
|
-
The `
|
632
|
+
The `has_any?` method may be used to determine if any of the given attributes are present on the component:
|
744
633
|
|
745
634
|
```rblade
|
746
|
-
@if (
|
635
|
+
@if (attributes.has_any?('href', ':href', 'v-bind:href'))
|
747
636
|
<div>One of the attributes is present</div>
|
748
637
|
@endif
|
749
638
|
```
|
750
639
|
|
751
|
-
You may retrieve a specific attribute's value using the `get` method:
|
752
|
-
|
753
|
-
```rblade
|
754
|
-
{{ $attributes->get('class') }}
|
755
|
-
```
|
756
|
-
|
757
640
|
<a name="slots"></a>
|
758
641
|
### Slots
|
759
642
|
|
@@ -774,11 +657,11 @@ We may pass content to the `slot` by injecting content into the component:
|
|
774
657
|
</x-alert>
|
775
658
|
```
|
776
659
|
|
777
|
-
**TODO this**
|
778
660
|
Sometimes a component may need to render multiple different slots in different locations within the component. Let's modify our alert component to allow for the injection of a "title" slot:
|
779
661
|
|
780
662
|
```rblade
|
781
663
|
{{-- /app/views/components/alert.rblade --}}
|
664
|
+
@props({title: _required})
|
782
665
|
<span class="alert-title">{{ title }}</span>
|
783
666
|
<div class="alert alert-danger">
|
784
667
|
{{ slot }}
|
@@ -797,13 +680,13 @@ You may define the content of the named slot using the `x-slot` tag. Any content
|
|
797
680
|
</x-alert>
|
798
681
|
```
|
799
682
|
|
800
|
-
|
683
|
+
The slot object extends the String interface, so you can invoke a slot's `empty?` method to determine if the slot contains content:
|
801
684
|
|
802
685
|
```rblade
|
803
686
|
<span class="alert-title">{{ title }}</span>
|
804
687
|
|
805
688
|
<div class="alert alert-danger">
|
806
|
-
@if (slot.
|
689
|
+
@if (slot.empty?)
|
807
690
|
This is default content if the slot is empty.
|
808
691
|
@else
|
809
692
|
{{ slot }}
|
@@ -811,29 +694,6 @@ You may invoke a slot's `isEmpty` method to determine if the slot contains conte
|
|
811
694
|
</div>
|
812
695
|
```
|
813
696
|
|
814
|
-
Additionally, the `hasActualContent` method may be used to determine if the slot contains any "actual" content that is not an HTML comment:
|
815
|
-
|
816
|
-
```rblade
|
817
|
-
@if (slot.hasActualContent)
|
818
|
-
The scope has non-comment content.
|
819
|
-
@endif
|
820
|
-
```
|
821
|
-
|
822
|
-
<a name="scoped-slots"></a>
|
823
|
-
#### Scoped Slots
|
824
|
-
**TODO can we do this easily?**
|
825
|
-
If you have used a JavaScript framework such as Vue, you may be familiar with "scoped slots", which allow you to access data or methods from the component within your slot. You may achieve similar behavior in Laravel by defining public methods or properties on your component and accessing the component within your slot via the `$component` variable. In this example, we will assume that the `x-alert` component has a public `formatAlert` method defined on its component class:
|
826
|
-
|
827
|
-
```rblade
|
828
|
-
<x-alert>
|
829
|
-
<x-slot:title>
|
830
|
-
{{ $component->formatAlert('Server Error') }}
|
831
|
-
</x-slot>
|
832
|
-
|
833
|
-
<strong>Whoops!</strong> Something went wrong!
|
834
|
-
</x-alert>
|
835
|
-
```
|
836
|
-
|
837
697
|
<a name="slot-attributes"></a>
|
838
698
|
#### Slot Attributes
|
839
699
|
|
@@ -855,15 +715,14 @@ Like RBlade components, you may assign additional [attributes](#component-attrib
|
|
855
715
|
|
856
716
|
To interact with slot attributes, you may access the `attributes` property of the slot's variable. For more information on how to interact with attributes, please consult the documentation on [component attributes](#component-attributes):
|
857
717
|
|
858
|
-
**TODO allow class to contain a string**
|
859
718
|
```rblade
|
860
719
|
@props({
|
861
720
|
"heading": _required,
|
862
721
|
"footer": _required,
|
863
722
|
})
|
864
723
|
|
865
|
-
<div {{ attributes.class(
|
866
|
-
<h1 {{ heading.attributes
|
724
|
+
<div {{ attributes.class('border') }}>
|
725
|
+
<h1 {{ heading.attributes.class('text-lg') }}>
|
867
726
|
{{ heading }}
|
868
727
|
</h1>
|
869
728
|
|
@@ -875,16 +734,6 @@ To interact with slot attributes, you may access the `attributes` property of th
|
|
875
734
|
</div>
|
876
735
|
```
|
877
736
|
|
878
|
-
<a name="dynamic-components"></a>
|
879
|
-
### Dynamic Components
|
880
|
-
**TODO add this**
|
881
|
-
Sometimes you may need to render a component without knowing which component should be rendered until runtime. In this situation, you may use RBlade's built-in `dynamic-component` component to render the component based on a runtime value or variable:
|
882
|
-
|
883
|
-
```rblade
|
884
|
-
@ruby(componentName = "secondary-button")
|
885
|
-
<x-dynamic-component :component="componentName" class="mt-4" />
|
886
|
-
```
|
887
|
-
|
888
737
|
<a name="registering-additional-component-directories"></a>
|
889
738
|
### Registering Additional Component Directories
|
890
739
|
|
@@ -902,33 +751,6 @@ RBlade::ComponentStore.add_path(Rails.root.join("app", "views", "partials"), "pa
|
|
902
751
|
|
903
752
|
If multiple directories are registered with the same namespace, RBlade will search for components in all the directories in the order they were registered.
|
904
753
|
|
905
|
-
<a name="manually-registering-components"></a>
|
906
|
-
### Manually Registering Components
|
907
|
-
**TODO would this be useful? It'd be useful for testing...**
|
908
|
-
> [!WARNING]
|
909
|
-
> The following documentation on manually registering components is primarily applicable to those who are writing Laravel packages that include view components. If you are not writing a package, this portion of the component documentation may not be relevant to you.
|
910
|
-
|
911
|
-
When writing components for your own application, components are automatically discovered within the `app/View/Components` directory and `resources/views/components` directory.
|
912
|
-
|
913
|
-
However, if you are building a package that utilizes Blade components or placing components in non-conventional directories, you will need to manually register your component class and its HTML tag alias so that Laravel knows where to find the component. You should typically register your components in the `boot` method of your package's service provider:
|
914
|
-
|
915
|
-
use Illuminate\Support\Facades\Blade;
|
916
|
-
use VendorPackage\View\Components\AlertComponent;
|
917
|
-
|
918
|
-
/**
|
919
|
-
* Bootstrap your package's services.
|
920
|
-
*/
|
921
|
-
public function boot(): void
|
922
|
-
{
|
923
|
-
Blade::component('package-alert', AlertComponent::class);
|
924
|
-
}
|
925
|
-
|
926
|
-
Once your component has been registered, it may be rendered using its tag alias:
|
927
|
-
|
928
|
-
```rblade
|
929
|
-
<x-package-alert/>
|
930
|
-
```
|
931
|
-
|
932
754
|
<a name="anonymous-index-components"></a>
|
933
755
|
### Index Components
|
934
756
|
|
@@ -956,64 +778,26 @@ However, when an `index.rblade` template exists in a directory, it will be rende
|
|
956
778
|
/app/views/components/accordion/item.rblade
|
957
779
|
```
|
958
780
|
|
959
|
-
<a name="
|
960
|
-
|
961
|
-
**TODO this? It might be complicated**
|
962
|
-
Sometimes you may want to access data from a parent component inside a child component. In these cases, you may use the `@aware` directive. For example, imagine we are building a complex menu component consisting of a parent `<x-menu>` and child `<x-menu.item>`:
|
963
|
-
|
964
|
-
```rblade
|
965
|
-
<x-menu color="purple">
|
966
|
-
<x-menu.item>...</x-menu.item>
|
967
|
-
<x-menu.item>...</x-menu.item>
|
968
|
-
</x-menu>
|
969
|
-
```
|
970
|
-
|
971
|
-
The `<x-menu>` component may have an implementation like the following:
|
972
|
-
|
973
|
-
```rblade
|
974
|
-
<!-- /resources/views/components/menu/index.rblade -->
|
781
|
+
<a name="forms"></a>
|
782
|
+
## Forms
|
975
783
|
|
976
|
-
|
784
|
+
### Old Input
|
977
785
|
|
978
|
-
|
979
|
-
{{ $slot }}
|
980
|
-
</ul>
|
981
|
-
```
|
982
|
-
|
983
|
-
Because the `color` prop was only passed into the parent (`<x-menu>`), it won't be available inside `<x-menu.item>`. However, if we use the `@aware` directive, we can make it available inside `<x-menu.item>` as well:
|
786
|
+
The Rails `params` Hash is available in views and components. However, the `@old` directive is a useful shortcut that will output the old input value for a given key:
|
984
787
|
|
985
788
|
```rblade
|
986
|
-
|
987
|
-
|
988
|
-
@aware(['color' => 'gray'])
|
989
|
-
|
990
|
-
<li {{ $attributes->merge(['class' => 'text-'.$color.'-800']) }}>
|
991
|
-
{{ $slot }}
|
992
|
-
</li>
|
789
|
+
<input type="text" name="email" value="@old('email', user.email)">
|
993
790
|
```
|
994
791
|
|
995
|
-
|
996
|
-
> The `@aware` directive can not access parent data that is not explicitly passed to the parent component via HTML attributes. Default `@props` values that are not explicitly passed to the parent component can not be accessed by the `@aware` directive.
|
997
|
-
|
998
|
-
<a name="forms"></a>
|
999
|
-
## Forms
|
1000
|
-
|
1001
|
-
<a name="csrf-field"></a>
|
1002
|
-
### CSRF Field
|
1003
|
-
**TODO I don't think we need this?**
|
1004
|
-
Anytime you define an HTML form in your application, you should include a hidden CSRF token field in the form so that [the CSRF protection](/docs/{{version}}/csrf) middleware can validate the request. You may use the `@csrf` Blade directive to generate the token field:
|
792
|
+
The first parameter is the name of the previous input, and the second input is the default if the key isn't present in `params`. The previous example is the equivalent of calling `params.fetch`:
|
1005
793
|
|
1006
794
|
```rblade
|
1007
|
-
<
|
1008
|
-
@csrf
|
1009
|
-
|
1010
|
-
...
|
1011
|
-
</form>
|
795
|
+
<input type="text" name="email" value="{{ params.fetch(:email, user.email) }}">
|
1012
796
|
```
|
1013
797
|
|
1014
798
|
<a name="method-field"></a>
|
1015
799
|
### Method Field
|
1016
|
-
|
800
|
+
|
1017
801
|
Since HTML forms can't make `PUT`, `PATCH`, or `DELETE` requests, you will need to add a hidden `_method` field to spoof these HTTP verbs. The `@method` RBlade directive can create this field for you:
|
1018
802
|
|
1019
803
|
```rblade
|
@@ -1024,52 +808,8 @@ Since HTML forms can't make `PUT`, `PATCH`, or `DELETE` requests, you will need
|
|
1024
808
|
</form>
|
1025
809
|
```
|
1026
810
|
|
1027
|
-
|
1028
|
-
### Validation Errors
|
1029
|
-
**TODO this**
|
1030
|
-
The `@error` directive may be used to quickly check if [validation error messages](/docs/{{version}}/validation#quick-displaying-the-validation-errors) exist for a given attribute. Within an `@error` directive, you may echo the `$message` variable to display the error message:
|
1031
|
-
|
1032
|
-
```rblade
|
1033
|
-
<!-- /resources/views/post/create.rblade -->
|
1034
|
-
|
1035
|
-
<label for="title">Post Title</label>
|
1036
|
-
|
1037
|
-
<input id="title"
|
1038
|
-
type="text"
|
1039
|
-
class="@error('title') is-invalid @enderror">
|
1040
|
-
|
1041
|
-
@error('title')
|
1042
|
-
<div class="alert alert-danger">{{ $message }}</div>
|
1043
|
-
@enderror
|
1044
|
-
```
|
1045
|
-
|
1046
|
-
Since the `@error` directive compiles to an "if" statement, you may use the `@else` directive to render content when there is not an error for an attribute:
|
1047
|
-
|
1048
|
-
```rblade
|
1049
|
-
<!-- /resources/views/auth.rblade -->
|
1050
|
-
|
1051
|
-
<label for="email">Email address</label>
|
1052
|
-
|
1053
|
-
<input id="email"
|
1054
|
-
type="email"
|
1055
|
-
class="@error('email') is-invalid @else is-valid @enderror">
|
1056
|
-
```
|
1057
|
-
|
1058
|
-
You may pass [the name of a specific error bag](/docs/{{version}}/validation#named-error-bags) as the second parameter to the `@error` directive to retrieve validation error messages on pages containing multiple forms:
|
1059
|
-
|
1060
|
-
```rblade
|
1061
|
-
<!-- /resources/views/auth.rblade -->
|
1062
|
-
|
1063
|
-
<label for="email">Email address</label>
|
811
|
+
Alternatively, you can use the dedicated directives for each method: `@put`, `@patch`, or `@delete`.
|
1064
812
|
|
1065
|
-
<input id="email"
|
1066
|
-
type="email"
|
1067
|
-
class="@error('email', 'login') is-invalid @enderror">
|
1068
|
-
|
1069
|
-
@error('email', 'login')
|
1070
|
-
<div class="alert alert-danger">{{ $message }}</div>
|
1071
|
-
@enderror
|
1072
|
-
```
|
1073
813
|
|
1074
814
|
<a name="stacks"></a>
|
1075
815
|
## Stacks
|
@@ -1079,11 +819,11 @@ RBlade allows you to push to named stacks which can be rendered elsewhere in ano
|
|
1079
819
|
```rblade
|
1080
820
|
@push('scripts')
|
1081
821
|
<script src="/example.js"></script>
|
1082
|
-
@
|
822
|
+
@endPush
|
1083
823
|
```
|
1084
824
|
|
1085
|
-
If you would like to `@push` content if a given boolean expression evaluates to `true`, you may use the `@
|
1086
|
-
|
825
|
+
If you would like to `@push` content if a given boolean expression evaluates to `true`, you may use the `@pushif` directive:
|
826
|
+
|
1087
827
|
```rblade
|
1088
828
|
@pushIf(shouldPush, 'scripts')
|
1089
829
|
<script src="/example.js"></script>
|
@@ -1105,153 +845,11 @@ If you would like to prepend content onto the beginning of a stack, you should u
|
|
1105
845
|
```rblade
|
1106
846
|
@push('scripts')
|
1107
847
|
This will be second...
|
1108
|
-
@
|
848
|
+
@endPush
|
1109
849
|
|
1110
850
|
// Later...
|
1111
851
|
|
1112
852
|
@prepend('scripts')
|
1113
853
|
This will be first...
|
1114
|
-
@
|
1115
|
-
```
|
1116
|
-
|
1117
|
-
<a name="rendering-blade-fragments"></a>
|
1118
|
-
## Rendering Blade Fragments
|
1119
|
-
**TODO this?**
|
1120
|
-
When using frontend frameworks such as [Turbo](https://turbo.hotwired.dev/) and [htmx](https://htmx.org/), you may occasionally need to only return a portion of a Blade template within your HTTP response. Blade "fragments" allow you to do just that. To get started, place a portion of your Blade template within `@fragment` and `@endfragment` directives:
|
1121
|
-
|
1122
|
-
```rblade
|
1123
|
-
@fragment('user-list')
|
1124
|
-
<ul>
|
1125
|
-
@foreach ($users as $user)
|
1126
|
-
<li>{{ $user->name }}</li>
|
1127
|
-
@endforeach
|
1128
|
-
</ul>
|
1129
|
-
@endfragment
|
1130
|
-
```
|
1131
|
-
|
1132
|
-
Then, when rendering the view that utilizes this template, you may invoke the `fragment` method to specify that only the specified fragment should be included in the outgoing HTTP response:
|
1133
|
-
|
1134
|
-
```php
|
1135
|
-
return view('dashboard', ['users' => $users])->fragment('user-list');
|
1136
|
-
```
|
1137
|
-
|
1138
|
-
The `fragmentIf` method allows you to conditionally return a fragment of a view based on a given condition. Otherwise, the entire view will be returned:
|
1139
|
-
|
1140
|
-
```php
|
1141
|
-
return view('dashboard', ['users' => $users])
|
1142
|
-
->fragmentIf($request->hasHeader('HX-Request'), 'user-list');
|
1143
|
-
```
|
1144
|
-
|
1145
|
-
The `fragments` and `fragmentsIf` methods allow you to return multiple view fragments in the response. The fragments will be concatenated together:
|
1146
|
-
|
1147
|
-
```php
|
1148
|
-
view('dashboard', ['users' => $users])
|
1149
|
-
->fragments(['user-list', 'comment-list']);
|
1150
|
-
|
1151
|
-
view('dashboard', ['users' => $users])
|
1152
|
-
->fragmentsIf(
|
1153
|
-
$request->hasHeader('HX-Request'),
|
1154
|
-
['user-list', 'comment-list']
|
1155
|
-
);
|
1156
|
-
```
|
1157
|
-
|
1158
|
-
<a name="extending-blade"></a>
|
1159
|
-
## Extending Blade
|
1160
|
-
**TODO this?**
|
1161
|
-
Blade allows you to define your own custom directives using the `directive` method. When the Blade compiler encounters the custom directive, it will call the provided callback with the expression that the directive contains.
|
1162
|
-
|
1163
|
-
The following example creates a `@datetime($var)` directive which formats a given `$var`, which should be an instance of `DateTime`:
|
1164
|
-
|
1165
|
-
<?php
|
1166
|
-
|
1167
|
-
namespace App\Providers;
|
1168
|
-
|
1169
|
-
use Illuminate\Support\Facades\Blade;
|
1170
|
-
use Illuminate\Support\ServiceProvider;
|
1171
|
-
|
1172
|
-
class AppServiceProvider extends ServiceProvider
|
1173
|
-
{
|
1174
|
-
/**
|
1175
|
-
* Register any application services.
|
1176
|
-
*/
|
1177
|
-
public function register(): void
|
1178
|
-
{
|
1179
|
-
// ...
|
1180
|
-
}
|
1181
|
-
|
1182
|
-
/**
|
1183
|
-
* Bootstrap any application services.
|
1184
|
-
*/
|
1185
|
-
public function boot(): void
|
1186
|
-
{
|
1187
|
-
Blade::directive('datetime', function (string $expression) {
|
1188
|
-
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
|
1189
|
-
});
|
1190
|
-
}
|
1191
|
-
}
|
1192
|
-
|
1193
|
-
As you can see, we will chain the `format` method onto whatever expression is passed into the directive. So, in this example, the final PHP generated by this directive will be:
|
1194
|
-
|
1195
|
-
<?php echo ($var)->format('m/d/Y H:i'); ?>
|
1196
|
-
|
1197
|
-
> [!WARNING]
|
1198
|
-
> After updating the logic of a Blade directive, you will need to delete all of the cached Blade views. The cached Blade views may be removed using the `view:clear` Artisan command.
|
1199
|
-
|
1200
|
-
<a name="custom-echo-handlers"></a>
|
1201
|
-
### Custom Echo Handlers
|
1202
|
-
**TODO this? Need to do something similar for attribute manager anyway**
|
1203
|
-
If you attempt to "echo" an object using Blade, the object's `__toString` method will be invoked. The [`__toString`](https://www.php.net/manual/en/language.oop5.magic.php#object.tostring) method is one of PHP's built-in "magic methods". However, sometimes you may not have control over the `__toString` method of a given class, such as when the class that you are interacting with belongs to a third-party library.
|
1204
|
-
|
1205
|
-
In these cases, Blade allows you to register a custom echo handler for that particular type of object. To accomplish this, you should invoke Blade's `stringable` method. The `stringable` method accepts a closure. This closure should type-hint the type of object that it is responsible for rendering. Typically, the `stringable` method should be invoked within the `boot` method of your application's `AppServiceProvider` class:
|
1206
|
-
|
1207
|
-
use Illuminate\Support\Facades\Blade;
|
1208
|
-
use Money\Money;
|
1209
|
-
|
1210
|
-
/**
|
1211
|
-
* Bootstrap any application services.
|
1212
|
-
*/
|
1213
|
-
public function boot(): void
|
1214
|
-
{
|
1215
|
-
Blade::stringable(function (Money $money) {
|
1216
|
-
return $money->formatTo('en_GB');
|
1217
|
-
});
|
1218
|
-
}
|
1219
|
-
|
1220
|
-
Once your custom echo handler has been defined, you may simply echo the object in your Blade template:
|
1221
|
-
|
1222
|
-
```rblade
|
1223
|
-
Cost: {{ $money }}
|
1224
|
-
```
|
1225
|
-
|
1226
|
-
<a name="custom-if-statements"></a>
|
1227
|
-
### Custom If Statements
|
1228
|
-
**TODO this**
|
1229
|
-
Programming a custom directive is sometimes more complex than necessary when defining simple, custom conditional statements. For that reason, Blade provides a `Blade::if` method which allows you to quickly define custom conditional directives using closures. For example, let's define a custom conditional that checks the configured default "disk" for the application. We may do this in the `boot` method of our `AppServiceProvider`:
|
1230
|
-
|
1231
|
-
use Illuminate\Support\Facades\Blade;
|
1232
|
-
|
1233
|
-
/**
|
1234
|
-
* Bootstrap any application services.
|
1235
|
-
*/
|
1236
|
-
public function boot(): void
|
1237
|
-
{
|
1238
|
-
Blade::if('disk', function (string $value) {
|
1239
|
-
return config('filesystems.default') === $value;
|
1240
|
-
});
|
1241
|
-
}
|
1242
|
-
|
1243
|
-
Once the custom conditional has been defined, you can use it within your templates:
|
1244
|
-
|
1245
|
-
```rblade
|
1246
|
-
@disk('local')
|
1247
|
-
<!-- The application is using the local disk... -->
|
1248
|
-
@elsedisk('s3')
|
1249
|
-
<!-- The application is using the s3 disk... -->
|
1250
|
-
@else
|
1251
|
-
<!-- The application is using some other disk... -->
|
1252
|
-
@enddisk
|
1253
|
-
|
1254
|
-
@unlessdisk('local')
|
1255
|
-
<!-- The application is not using the local disk... -->
|
1256
|
-
@enddisk
|
854
|
+
@endPrepend
|
1257
855
|
```
|