Skip to content

Commit

Permalink
Blade Components (#69)
Browse files Browse the repository at this point in the history
* WIP

* Green tests

* Skip blade components on < 8.0

* Don't try to boot components in Laravel 7

* version_compare order

* Minor refactor

* Only test components on Laravel 8

* More tests

* Trailing comma

* Test all input types

* Backwards-compat

* Tests for radio/checkbox groups

* Add support for Livewire and Alpine-style attributes to Aire Components

* Changelog and readme
  • Loading branch information
inxilpro authored Feb 5, 2021
1 parent b3e7389 commit e94489d
Show file tree
Hide file tree
Showing 67 changed files with 5,304 additions and 47 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Changelog

Starting with version 2.4.0, all notable changes will be documented in this file following
the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format. This project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.4.0] - 2021-01-22

### Added

- Added support for [Laravel Blade Components](https://laravel.com/docs/8.x/blade#components)

## 2.3.4 and before

For all releases from 2.3.4 and below, see the [Github Releases](https://github.com/glhd/aire/releases).

[Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/2.4.0...HEAD
[2.4.0]: https://github.com/olivierlacan/keep-a-changelog/compare/2.3.4...2.4.0
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ are fluent, allowing for easy configuration of your form components:
{{ Aire::close() }}
```

### Blade Components

As of Aire 2.4.0, you can also use all Aire elements as [Blade Components](https://laravel.com/docs/8.x/blade#components).
The above form is identical to:

```html
<x-aire::form route="users.update" :bind="$user">

<x-aire::input
name="given_name"
label="First/Given Name"
id="given_name"
/>
<x-aire::input
name="family_name"
label="Last/Family Name"
id="family_name"
auto-complete="off"
/>
<x-aire::email
name="email"
label="Email Address"
help-text="Please use your company email address."
/>

<x-aire::submit label="Update User" />

</x-aire::form>
```

## Installation

Install via composer with:
Expand Down
129 changes: 129 additions & 0 deletions bin/build-blade-components.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/usr/bin/env php
<?php

require_once __DIR__.'/../vendor/autoload.php';

$finder = \Symfony\Component\Finder\Finder::create()
->files()
->in(__DIR__.'/../src/Elements')
->depth(0)
->name('*.php');

$ignored_concerns = ['CreatesElements', 'CreatesInputTypes'];

$ignored_methods = collect($ignored_concerns)
->flatMap(function($name) {
return (new ReflectionClass("Galahad\\Aire\\Elements\\Concerns\\{$name}"))->getMethods();
})
->map(function(ReflectionMethod $method) {
return $method->getName();
})
->values()
->all();

collect($finder)
->map(function(\Symfony\Component\Finder\SplFileInfo $file) {
return 'Galahad\\Aire\\Elements\\'.$file->getBasename('.php');
})
->values()
->filter(function($class_name) {
return class_exists($class_name);
})
->map(function($class_name) {
return new ReflectionClass($class_name);
})
->reject(function(ReflectionClass $class) {
return $class->isAbstract();
})
->each(function(ReflectionClass $class) use ($ignored_methods) {
$methods = collect($class->getMethods(ReflectionMethod::IS_PUBLIC))
->keyBy(function(ReflectionMethod $method) {
return $method->getName();
})
->reject(function(ReflectionMethod $method) {
return $method->isStatic()
|| $method->isAbstract()
|| $method->getDeclaringClass()->getName() === 'Galahad\\Aire\\Elements\\Concerns\\CreatesElements'
|| preg_match('/^(__|(get|set|has|is)[A-Z])/', $method->getName());
})
->except([
'render',
'toHtml',
'hasViewData',
'callMacro',
'registerElement',
])
->except($ignored_methods);

$properties = $methods->map(function(ReflectionMethod $method) {
if (0 === $method->getNumberOfParameters()) {
$type = '?bool ';
} elseif ($method->getNumberOfParameters() > 1) {
$type = '?array ';
} else {
$parameter = $method->getParameters()[0];
if ($parameter->hasType()) {
$type = '?'.$parameter->getType()->getName().' ';
} else {
$type = '';
}
}

return (object) [
'name' => $method->getName(),
'type' => $type,
];
});

// $props = $properties
// ->map(function($property) {
// return "public {$property->type}\${$property->name} = null;";
// })
// ->implode("\n\t\n\t");

$params = $properties
->map(function($property) {
return "{$property->type}\${$property->name} = null";
})
->implode(",\n\t\t");

$compact = $properties
->map(function($property) {
return "'{$property->name}'";
})
->implode(",\n\t\t\t");

$name = class_basename($class->getName());

$code = <<<PHP
<?php
namespace Galahad\Aire\Components;
use Galahad\\Aire\\Elements\\{$name} as {$name}Element;
class {$name} extends ElementComponent
{
public function __construct(
{$params}
) {
\$this->createElement({$name}Element::class, compact(
{$compact}
));
}
}
PHP;

$filename = __DIR__.'/../src/Components/'.$name.'.php';

if (!file_exists($filename)) {
file_put_contents($filename, $code);
echo "Wrote $filename\n";
} else {
echo "FILE ALREADY EXISTS: $filename\n\n";
echo $code;
echo "\n\n";
}

});
89 changes: 64 additions & 25 deletions bin/codegen.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env php
<?php

use Illuminate\Support\Str;

require_once __DIR__.'/../vendor/autoload.php';

$tag_whitelist = [
Expand All @@ -11,8 +13,8 @@
'select',
'option',
'textarea',
'fieldset',
'legend',
// 'fieldset',
// 'legend',
];

$form_element_tags = [
Expand Down Expand Up @@ -61,13 +63,13 @@ function attribute_spellings($attribute)
{
global $attribute_methods;

$camel = $attribute_methods[$attribute] ?? camel_case($attribute);
$camel = $attribute_methods[$attribute] ?? Str::camel($attribute);

return (object) [
'camel' => $camel,
'snake' => snake_case($camel),
'hyphen' => snake_case($camel, '-'),
'studly' => studly_case($camel),
'snake' => Str::snake($camel),
'hyphen' => Str::snake($camel, '-'),
'studly' => Str::studly($camel),
];
}

Expand Down Expand Up @@ -175,12 +177,13 @@ function print_setter($attribute, $attribute_config, $parent = 'Element') {
}
}

function print_setter_test($attribute, $attribute_config, $tag = 'form') {
$class_name = studly_case($tag);
function print_setter_test($attribute, $attribute_config, $tag = 'form', $component = false) {
$class_name = Str::studly($tag);
$is_flag = isset($attribute_config['type']) && 'flag' === $attribute_config['type'];
$is_bool = isset($attribute_config['type']) && 'boolean' === $attribute_config['type'];
$test_name = $attribute_config['spellings']->snake;
$method = $attribute_config['spellings']->camel;
$xml_attribute = $attribute_config['spellings']->hyphen;

if ($is_flag) {

Expand All @@ -199,37 +202,59 @@ function print_setter_test($attribute, $attribute_config, $tag = 'form') {

}

$target = '$form';
$target = '$'.strtolower($class_name);

if ('Form' !== $class_name) {
$target = '$'.strtolower($class_name);

echo "\t\t$target = new $class_name(\$this->aire(), \$this->aire()->form());\n";
} else {
echo "\t\t\$form = \$this->aire()->form();\n";
if (!$component) {
if ('Form' !== $class_name) {
echo "\t\t$target = new $class_name(\$this->aire(), \$this->aire()->form());\n";
} else {
echo "\t\t$target = \$this->aire()->form();\n";
}
echo "\t\t\n";
}

echo "\t\t\n";

if ($is_flag) {

echo "\t\t$target->$method();\n";
if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag $xml_attribute />');\n";
} else {
echo "\t\t$target->$method();\n";
}
echo "\t\t\$this->assertSelectorAttribute($target, '$tag', '$attribute');\n";
echo "\t\t\n";
echo "\t\t$target->$method(false);\n";

if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag :$xml_attribute=\"false\" />');\n";
} else {
echo "\t\t$target->$method(false);\n";
}
echo "\t\t\$this->assertSelectorAttributeMissing($target, '$tag', '$attribute');\n";
echo "\t}\n";
echo "\t\n";

} else if ($is_bool) {

echo "\t\t$target->$method();\n";
if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag $xml_attribute />');\n";
} else {
echo "\t\t$target->$method();\n";
}
echo "\t\t\$this->assertSelectorAttribute($target, '$tag', '$attribute', 'true');\n";
echo "\t\t\n";
echo "\t\t$target->$method(false);\n";

if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag :$xml_attribute=\"false\" />');\n";
} else {
echo "\t\t$target->$method(false);\n";
}
echo "\t\t\$this->assertSelectorAttribute($target, '$tag', '$attribute', 'false');\n";
echo "\t\t\n";
echo "\t\t$target->$method(null);\n";

if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag :$xml_attribute=\"null\" />');\n";
} else {
echo "\t\t$target->$method(null);\n";
}
echo "\t\t\$this->assertSelectorAttributeMissing($target, '$tag', '$attribute');\n";
echo "\t}\n";
echo "\t\n";
Expand All @@ -239,19 +264,33 @@ function print_setter_test($attribute, $attribute_config, $tag = 'form') {
if (isset($attribute_config['attribOption'])) {
foreach ($attribute_config['attribOption'] as $value) {
$value = addslashes($value);
echo "\t\t$target->$method('$value');\n";

if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag $xml_attribute=\"$value\" />');\n";
} else {
echo "\t\t$target->$method('$value');\n";
}
echo "\t\t\$this->assertSelectorAttribute($target, '$tag', '$attribute', '$value');\n";
echo "\t\t\n";
}
} else {
echo "\t\t\$value = Str::random();\n";
echo "\t\t\n";
echo "\t\t$target->$method(\$value);\n";

if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag :$xml_attribute=\"\$value\" />', compact('value'));\n";
} else {
echo "\t\t$target->$method(\$value);\n";
}
echo "\t\t\$this->assertSelectorAttribute($target, '$tag', '$attribute', \$value);\n";
echo "\t\t\n";
}

echo "\t\t$target->$method(null);\n";
if ($component) {
echo "\t\t$target = \$this->renderBlade('<x-aire::$tag :$xml_attribute=\"null\" />');\n";
} else {
echo "\t\t$target->$method(null);\n";
}
echo "\t\t\$this->assertSelectorAttributeMissing($target, '$tag', '$attribute');\n";
echo "\t}\n";
echo "\t\n";
Expand Down
37 changes: 37 additions & 0 deletions bin/codegen/component_global_attribute_tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

if ($write) {
ob_start();
}

echo "<?php\n\n";

echo "/**\n";
echo " * Portions of this code have been generated using Atom autocompletion data.\n";
echo " *\n";
echo " * @see https://github.com/atom/autocomplete-html\n";
echo " *\n";
echo " * $license_docblock\n";
echo " *\n";
echo " */\n\n";

echo "namespace Galahad\Aire\Tests\Components;\n\n";

echo "use Illuminate\Support\Str;\n\n";

echo "class GlobalAttributesTest extends ComponentTestCase\n";
echo "{\n";

foreach ($global_attributes as $attribute => $attribute_config) {
print_setter_test($attribute, $attribute_config, 'form', true);
}

echo "}\n";

if ($write) {
$php = ob_get_clean();

$file_path = __DIR__.'/../../tests/Components/GlobalAttributesTest.php';
file_put_contents($file_path, $php);
echo "Wrote $file_path\n";
}
Loading

0 comments on commit e94489d

Please sign in to comment.