Skip to content
This repository was archived by the owner on Jul 28, 2024. It is now read-only.

Commit 1850d4b

Browse files
authored
Merge pull request #5 from wavevision/feature/docs
Feature/docs
2 parents 6ead129 + 392eede commit 1850d4b

File tree

11 files changed

+390
-2
lines changed

11 files changed

+390
-2
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,4 @@ composer require wavevision/props-control
1818

1919
## Usage
2020

21-
See [tests](./tests/PropsControlTests/Components/TestComponent) for example implementation of the abstract component
22-
and its props.
21+
See documentation at [wavevision.github.io/props-control](https://wavevision.github.io/props-control).

docs/.nojekyll

Whitespace-only changes.

docs/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<p align="center"><a href="https://github.com/wavevision"><img alt="Wavevision s.r.o." src="https://wavevision.com/images/wavevision-logo.png" width="120" /></a></p>
2+
<h1 align="center" id="props-control">PropsControl</h1>
3+
4+
[![Build Status](https://travis-ci.org/wavevision/props-control.svg?branch=master)](https://travis-ci.org/wavevision/props-control)
5+
[![Coverage Status](https://coveralls.io/repos/github/wavevision/props-control/badge.svg?branch=master)](https://coveralls.io/github/wavevision/props-control?branch=master)
6+
[![PHPStan](https://img.shields.io/badge/style-level%20max-brightgreen.svg?label=phpstan)](https://github.com/phpstan/phpstan)
7+
8+
## What it is
9+
10+
`PropsControl` is an abstract `Nette\Application\UI\Control` that can help you to create simple, yet powerful UI components with great maintainability.
11+
12+
## Features
13+
- simple to use
14+
- automatic template file assignment
15+
- immutable props defined and validated using [`nette/schema`](https://github.com/nette/schema)
16+
- CSS classname helper to seamlessly create whole classes based on current props
17+
- style attribute helper to easily assign inline style to your component
18+
- `beforeRender` method you know from presenters with ability to add callbacks from higher order components
19+
- rendering to `string` and `Nette\Utils\Html`

docs/_coverpage.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<a href="https://github.com/wavevision"><img alt="Wavevision s.r.o." src="https://wavevision.com/images/wavevision-logo.png" width="120" /></a>
2+
<h1>PropsControl <small>6.0.0</small></h1>
3+
4+
> Create smart UI components for [@nette](https://github.com) framework.
5+
6+
[GitHub](https://github.com/wavevision/props-control/)
7+
[Getting Started](#props-control)

docs/_sidebar.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
- Getting started
2+
- [Installation](getting-started/installation.md)
3+
- [First component](getting-started/first-component.md)
4+
- Guide
5+
- [Understanding Props](guide/understanding-props.md)
6+
- [Working with Props](guide/working-with-props.md)
7+
- [CSS class helper](guide/css-class-helper.md)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# First component
2+
3+
Each component will consist of three related files that will work together:
4+
5+
- [Props](#props)
6+
- [Control](#control)
7+
- [Template](#template)
8+
9+
## Props
10+
11+
To create our first component, let us start with defining its props, so we know which data our control will use.
12+
For ease of this example we name it `FirstComponentProps`.
13+
14+
```php
15+
use Nette\Schema\Expect;
16+
use Wavevision\PropsControl\Props;
17+
18+
class FirstComponentProps extends Props
19+
{
20+
21+
public const NUMBER = 'number';
22+
23+
public const STRING = 'string';
24+
25+
26+
/**
27+
* @inheritDoc
28+
*/
29+
protected function define(): array
30+
{
31+
return [
32+
self::NUMBER => Expect::int()->nullable(),
33+
self::STRING => Expect::string()->required(),
34+
];
35+
}
36+
37+
}
38+
```
39+
40+
In our props definition we:
41+
42+
- extended `Wavevision\PropsControl\Props` abstract class
43+
- defined our prop names with constants
44+
- implemented mandatory `define` method which returns the shape of our props for `Nette\Schema`
45+
46+
## Control
47+
48+
Now we can create our component control class which will take care of everything we need to use our component.
49+
50+
```php
51+
use Wavevision\PropsControl\PropsControl;
52+
53+
class FirstComponent extends PropsControl
54+
{
55+
56+
/**
57+
* @return class-string<FirstComponentProps>
58+
*/
59+
protected function getPropsClass(): string
60+
{
61+
return FirstComponentProps::class;
62+
}
63+
64+
}
65+
```
66+
67+
In our control class we:
68+
69+
- extended `Wavevision\PropsControl\PropsControl` which does all the _goodies_ for us
70+
- implemented mandatory `getPropsClass` method which _tells_ our component to work
71+
with the props schema defined in that specific class
72+
73+
## Template
74+
75+
Each component will automatically assign a template to render in `templates/default.latte` in your component's namespace.
76+
Based on our props and control definition it can render the component like this:
77+
78+
```latte
79+
{templateType Wavevision\PropsControl\PropsControlTemplate}
80+
<div n:class="$className->block()">
81+
<p>Hello, {$props->string}!</p>
82+
<p n:if="$props->number">Your number is {$props->number}</p>
83+
</div>
84+
```
85+
86+
## Let's roll!
87+
88+
Yes, now you can use your component as you're used to. Just create an instance (directly or with factory interface)
89+
in your presenter or other component and start using it right away!
90+
91+
When you're rendering it, simply pass the data to it in your template:
92+
93+
```latte
94+
{control firstComponent, [string => 'world', number => 22]}
95+
```
96+
97+
That will output:
98+
99+
```html
100+
<div class="first-component">
101+
<p>Hello, world!</p>
102+
<p>Your number is 22</p>
103+
</div>
104+
```
105+
106+
In case we would pass to the component something like this:
107+
108+
```latte
109+
{control firstComponent, [number => '22']}
110+
```
111+
112+
Our component will tell us we're doing something wrong as we defined the `string` prop as required and the `number` prop as `integer`,
113+
thus `Nette\Schema\ValidationException` will be thrown.
114+
115+
This is just a very basic example, keep reading the docs to find out more information about all features and concepts of `PropsControl`.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Installation
2+
3+
Via [Composer](https://getcomposer.org)
4+
5+
```bash
6+
composer require wavevision/props-control
7+
```

docs/guide/css-class-helper.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# CSS class helper
2+
3+
`PropsControl` provides you with a simple helper to assemble your component's CSS class names.
4+
The helper formats classes with [simplified BEM](https://github.com/inuitcss) syntax.
5+
6+
## Base class
7+
8+
Our component's base class name is defined in `getClassName` method. By default, it will create `dash-cased` class name
9+
from your component's control class, e.g `MyComponent` will result in `my-component`.
10+
11+
If you wish to introduce a different way of creating your base class,
12+
or you simply want to define it manually, you can do it as follows:
13+
14+
```php
15+
public function getClassName(): string
16+
{
17+
return 'base-class';
18+
}
19+
```
20+
21+
## Modifiers
22+
23+
It's very common a component we create needs to modify its class name based on the data input. We can easily achieve
24+
this with `PropsControl` by defining `getClassNameModifiers` method.
25+
26+
The method will return an array of our modifiers definitions.
27+
28+
### Prop name
29+
30+
The simplest way to use a prop as modifier is to include its name in the array:
31+
32+
```php
33+
public function getClassNameModifiers(): array
34+
{
35+
return [MyComponentProps::SOME_PROP];
36+
}
37+
```
38+
39+
If `MyComponentProps::SOME_PROP` has a truthy value the **prop name** is used as a modifier.
40+
41+
```html
42+
<div class="base-class base-class--some-prop"></div>
43+
```
44+
45+
### Prop value
46+
47+
If you need to use the prop's value as a modifier instead, mark it with a constant predefined in `PropsControl`:
48+
49+
```php
50+
public function getClassNameModifiers(): array
51+
{
52+
return [MyComponentProps::SOME_PROP => self::USE_VALUE];
53+
}
54+
```
55+
56+
If `MyComponentProps::SOME_PROP` has a truthy value the **value** is used as a modifier.
57+
58+
```html
59+
<div class="base-class base-class--some-value"></div>
60+
```
61+
62+
### Custom modifiers
63+
64+
You can also define custom modifiers using a callback function that will receive `Wavevision\PropsControl\ValidProps`
65+
object as an argument.
66+
67+
```php
68+
public function getClassNameModifiers(): array
69+
{
70+
return [
71+
'custom' => function (ValidProps $props): bool {
72+
if ($entity = $props->get(MyComponentProps::ENTITY)) {
73+
return $entity->enabled;
74+
}
75+
return false;
76+
},
77+
fn (ValidProps $props): string => $props->get(MyComponentProps::IS_VALID) ? 'enabled' : 'disabled',
78+
];
79+
}
80+
```
81+
82+
It this example, `custom` will be used as a modifier if the callback returns `true`.
83+
84+
The other callback shows we can also return a `string`, in that case the returned string is a modifier.
85+
86+
```html
87+
<div class="base-class base-class--custom base-class--disabled"></div>
88+
```
89+
90+
## Formatting the CSS class
91+
92+
Once you have defined everything and your component is being rendered, its template will be provided with
93+
`Wavevision\PropsControl\Helpers\ClassName` object in `$className` variable
94+
that serves as a formatter with predefined base class and its modifiers.
95+
96+
The formatter has following methods publicly available:
97+
98+
### `block(?string ...$modifiers): string`
99+
100+
Formats a block class name with optional inline modifiers passed as parameters.
101+
102+
### `element(string $className, ?string ...$modifiers): string`
103+
104+
Formats an element class name as `base-class__$className` with optional inline modifiers passed as parameters.
105+
106+
### `create(string $baseClass, bool $block = true, bool $excludeModifiers = false): ClassName`
107+
108+
Creates a new formatter instance with a new base class. Useful for nested blocks inside your components.
109+
110+
By default, the format is `base-class-$baseClass`. If `$block` is `false`, only `$baseClass` will be used.
111+
112+
If `$excludeModifiers` is `true`, **no modifiers** defined in the component will be passed to the newly instantiated formatter.

docs/guide/understanding-props.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Understanding Props
2+
3+
For those who are familiar with [React.js](https://github.com/facebook/react) the core concept of Props is quite clear.
4+
5+
> Props stand for _properties_ which are arbitrary inputs to a component.
6+
7+
`PropsControl` brings this concept into Nette application with all its benefits.
8+
9+
## Readability and maintainability
10+
11+
Having a props definition next to your component means you can simply overview which data the component consumes and easily
12+
make changes to such definition without polluting the component itself with otherwise useless class members and properties
13+
which are only used to render some output.
14+
15+
## Immutability
16+
17+
Once you pass data into your component it cannot be changed. This means you cannot possibly break things at the moment when
18+
nothing but rendering the output should happen.
19+
20+
## Validation
21+
22+
All your data inputs are safely validated with [nette/schema](https://github.com/nette/schema). Thanks to this powerful part of Nette
23+
you can define complex data schemes through your components and make them work together (nested components).
24+
25+
## Encapsulation
26+
27+
We all know it – we have a component which renders some data and every time there's a little update needed,
28+
the component's render method and / or properties just grow and grow.
29+
30+
This will **not** happen with `PropsControl` as all the data passed will be encapsulated in an object
31+
or array the component's render method will accept as a **single** parameter.
32+
Once your data goes into your component and is validated, it gets encapsulated again,
33+
this time in `Wavevision\PropsControl\ValidProps` object that is available in the template to read and render the data there.

docs/guide/working-with-props.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Working with Props
2+
3+
As demonstrated in [First Component](getting-started/first-component.md) example, each `PropsControl` component needs its
4+
Props for data consumption and output rendering.
5+
6+
## Definition
7+
8+
The definition will give us the shape of our props, so we can structure and validate them.
9+
Also, the constants used for the definition can be later used to create and get the data while avoiding typos in prop names.
10+
Constants are also extremely useful when re-defining your props. If you refactor any constant in your IDE, the change will
11+
get propagated through your whole application.
12+
13+
After we created our definition and linked it to our component we can use the definition object to wrap our data.
14+
15+
```php
16+
$props = new FirstComponentProps([FirstComponentProps::STRING => 'world', FirstComponentProps::NUMBER => 22]);
17+
```
18+
19+
This object can be then passed to our component's render method.
20+
21+
It can also be used in some other component's props definition.
22+
23+
```php
24+
/**
25+
* @inheritDoc
26+
*/
27+
protected function define(): array
28+
{
29+
return [
30+
'firstComponent' => Expect::type(FirstComponentProps::class)->required(),
31+
];
32+
}
33+
```
34+
35+
This way you can make nested components and keep the validation working without duplicating the schemes.
36+
37+
## Reading props
38+
39+
You can access `Wavevision\PropsControl\ValidProps` object containing all validated props in your component's template
40+
in `$props` variable.
41+
42+
Your template will be also provided with a `$definition` variable, which holds the definition class of your props.
43+
44+
Reading your props is pretty straightforward.
45+
46+
### Property access
47+
48+
```latte
49+
{$props->propName}
50+
{$props->{$definition::PROP_NAME}}
51+
```
52+
53+
### Getter
54+
55+
```latte
56+
{$props->get('propName')}
57+
{$props->get($definition::PROP_NAME)}
58+
```
59+
60+
### Errors
61+
62+
Props are read-only, `Wavevision\PropsControl\Exceptions\NotAllowed` exception will be thrown if:
63+
64+
- you're trying to access an undefined prop
65+
- you're trying to write to a prop
66+
- you're calling any method not defined in `Wavevision\PropsControl\ValidProps` class

0 commit comments

Comments
 (0)