Skip to content

Commit

Permalink
Release 0.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
jkphl committed Mar 16, 2018
1 parent 3267eb8 commit fdf01a4
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 57 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.4.0] - Unreleased
## [0.4.0] - 2018-03-16

### Added

- Introduced form framework component
- Introduce form framework component
- Add support for custom component folder names via local `label` setting

### Changed

Expand Down
94 changes: 54 additions & 40 deletions Classes/Component/AbstractComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,15 @@ abstract class AbstractComponent implements ComponentInterface

/**
* Component constructor
*
* @param ControllerContext|null $controllerContext Controller context
*/
public function __construct(ControllerContext $controllerContext = null)
{
$this->controllerContext = $controllerContext;
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->request = $this->objectManager->get(Request::class);
$this->preview = new FluidTemplate($this->getDependencyTemplateResources());
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->request = $this->objectManager->get(Request::class);
$this->preview = new FluidTemplate($this->getDependencyTemplateResources());

$this->determineExtensionName();
$this->determineNameAndVariant();
Expand Down Expand Up @@ -236,32 +237,33 @@ protected function getDependencyTemplateResources()
* Find the extension name the current component belongs to
*
* @throws \RuntimeException If the component path is invalid
* @throws \ReflectionException
*/
protected function determineExtensionName()
{
$reflectionClass = new \ReflectionClass($this);
$reflectionClass = new \ReflectionClass($this);
$componentFilePath = dirname($reflectionClass->getFileName());

// If the file path is invalid
$extensionDirPosition = strpos($componentFilePath, 'ext'.DIRECTORY_SEPARATOR);
$extensionDirPosition = strpos($componentFilePath, 'ext' . DIRECTORY_SEPARATOR);
if ($extensionDirPosition === false) {
throw new \RuntimeException('Invalid component path', 1481360976);
}

// Extract the extension key
$componentPath = explode(
DIRECTORY_SEPARATOR,
substr($componentFilePath, $extensionDirPosition + strlen('ext'.DIRECTORY_SEPARATOR))
substr($componentFilePath, $extensionDirPosition + strlen('ext' . DIRECTORY_SEPARATOR))
);
$extensionKey = array_shift($componentPath);
$extensionKey = array_shift($componentPath);

// If the extension is unknown
if (!in_array($extensionKey, ExtensionManagementUtility::getLoadedExtensionListArray())) {
throw new \RuntimeException(sprintf('Unknown extension key "%s"', $extensionKey), 1481361198);
}

// Register the extension key & name
$this->extensionKey = $extensionKey;
$this->extensionKey = $extensionKey;
$this->extensionName = GeneralUtility::underscoredToUpperCamelCase($extensionKey);

// Process the component path
Expand All @@ -277,16 +279,17 @@ protected function determineExtensionName()
protected function determineNameAndVariant()
{
$reflectionClass = new \ReflectionClass($this);
$componentName = preg_replace('/Component$/', '', $reflectionClass->getShortName());
$componentName = preg_replace('/Component$/', '', $reflectionClass->getShortName());
list($name, $variant) = preg_split('/_+/', $componentName, 2);
$this->name = self::expandComponentName($name);
$this->name = self::expandComponentName($name);
$this->variant = self::expandComponentName($variant);
}

/**
* Prepare a component path
*
* @param string $componentPath Component path
*
* @return string Component name
*/
public static function expandComponentName($componentPath)
Expand Down Expand Up @@ -326,31 +329,32 @@ protected function addDocumentation()
$validIndexDocuments = [
'index.md',
'readme.md',
strtolower($this->name.'.md')
strtolower($this->name . '.md')
];
$indexDocument = null;
$documents = [];
$indexDocument = null;
$documents = [];

// Run through all documentation files
foreach (scandir($docDirectory) as $document) {
if (!is_file($docDirectory.DIRECTORY_SEPARATOR.$document)) {
if (!is_file($docDirectory . DIRECTORY_SEPARATOR . $document)) {
continue;
}

// If there's a valid documentation index file
if (in_array(strtolower($document), $validIndexDocuments)) {
if ($indexDocument === null) {
$indexDocument = $docDirectory.DIRECTORY_SEPARATOR.$document;
$indexDocument = $docDirectory . DIRECTORY_SEPARATOR . $document;
}
continue;
}

$documents[] = $docDirectory.DIRECTORY_SEPARATOR.$document;
$documents[] = $docDirectory . DIRECTORY_SEPARATOR . $document;
}

// If there's an index document
if ($indexDocument !== null) {
$this->addNotice(file_get_contents($indexDocument));

return;
}

Expand All @@ -361,8 +365,8 @@ protected function addDocumentation()
// Run through all documents
foreach ($documents as $document) {
$extension = strtolower(pathinfo($document, PATHINFO_EXTENSION));
$listing[] = '* '.(in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'svg']) ? '!' : '').
'['.pathinfo($document, PATHINFO_FILENAME).']('.basename($document).')';
$listing[] = '* ' . (in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'svg']) ? '!' : '') .
'[' . pathinfo($document, PATHINFO_FILENAME) . '](' . basename($document) . ')';
}

$this->addNotice(implode(PHP_EOL, $listing));
Expand All @@ -374,13 +378,15 @@ protected function addDocumentation()
* Return the documentation directory for this component
*
* @param bool $rootRelative Return a root relative path
*
* @return string Documentation directory
*/
protected function getDocumentationDirectory($rootRelative = false)
{
$reflectionObject = new \ReflectionObject($this);
$componentFile = $reflectionObject->getFileName();
$docDirectory = dirname($componentFile).DIRECTORY_SEPARATOR.$this->name;
$componentFile = $reflectionObject->getFileName();
$docDirectory = dirname($componentFile) . DIRECTORY_SEPARATOR . $this->name;

return $rootRelative ? substr($docDirectory, strlen(PATH_site) - 1) : $docDirectory;
}

Expand All @@ -392,7 +398,7 @@ protected function getDocumentationDirectory($rootRelative = false)
protected function addNotice($notice)
{
if (!$this->variant) {
$notice = trim($notice);
$notice = trim($notice);
$this->notice = strlen($notice) ? $this->exportNotice($notice) : null;
}
}
Expand All @@ -401,15 +407,17 @@ protected function addNotice($notice)
* Export a notice
*
* @param $notice
*
* @return mixed
*/
protected function exportNotice($notice)
{
$docDirectoryPath = strtr($this->getDocumentationDirectory(true), [DIRECTORY_SEPARATOR => '/']).'/';
return preg_replace_callback('/\[([^\]]*?)\]\(([^\)]*?)\)/', function ($match) use ($docDirectoryPath) {
return '['.$match[1].']('
.(preg_match('%^https?\:\/\/%i', $match[2]) ? '' : $docDirectoryPath)
.$match[2].')';
$docDirectoryPath = strtr($this->getDocumentationDirectory(true), [DIRECTORY_SEPARATOR => '/']) . '/';

return preg_replace_callback('/\[([^\]]*?)\]\(([^\)]*?)\)/', function($match) use ($docDirectoryPath) {
return '[' . $match[1] . ']('
. (preg_match('%^https?\:\/\/%i', $match[2]) ? '' : $docDirectoryPath)
. $match[2] . ')';
}, $notice);
}

Expand All @@ -433,22 +441,22 @@ protected function configure()
final public function export()
{
$properties = [
'status' => $this->status,
'name' => $this->name,
'status' => $this->status,
'name' => $this->name,
'variant' => $this->variant,
'label' => $this->label,
'class' => get_class($this),
'type' => $this->type,
'valid' => false,
'path' => $this->componentPath,
'docs' => $this->getDocumentationDirectory(),
'label' => $this->label,
'class' => get_class($this),
'type' => $this->type,
'valid' => false,
'path' => $this->componentPath,
'docs' => $this->getDocumentationDirectory(),
];

// Export the component properties
try {
$properties = array_merge($properties, $this->exportInternal());
$properties = array_merge($properties, $this->exportInternal());
$properties['request'] = $this->exportRequest();
$properties['valid'] = true;
$properties['valid'] = true;

// In case of an error
} catch (\Exception $e) {
Expand All @@ -474,8 +482,8 @@ protected function exportInternal()
throw new \RuntimeException('Invalid configuration', 1481363496);
}

$properties['config'] = $this->config;
$properties['template'] = $this->template;
$properties['config'] = $this->config;
$properties['template'] = $this->template;
$properties['extension'] = $this->extension;

// Export the associated resources
Expand Down Expand Up @@ -520,7 +528,7 @@ protected function exportRequest()
}

return [
'method' => $this->request->getMethod(),
'method' => $this->request->getMethod(),
'arguments' => $this->request->getArguments(),
];
}
Expand Down Expand Up @@ -590,21 +598,27 @@ protected function setPreview($preview)
*/
protected function initializeTSFE()
{
$GLOBALS['TSFE'] = TypoScriptUtility::getTSFE($this->page, $this->typeNum);
$GLOBALS['TSFE'] = TypoScriptUtility::getTSFE($this->page, $this->typeNum);
$GLOBALS['TSFE']->cObj = new ContentObjectRenderer($GLOBALS['TSFE']);
$GLOBALS['TSFE']->cObj->start($GLOBALS['TSFE']->page, 'pages');

return $GLOBALS['TSFE']->cObj;
}

/**
* Beautify HTML source
*
* @param string $html HTML source code
*
* @return string Beautified HTML source code
*/
protected function beautify($html)
{
// TODO: Remove this hotfix. Necessary because the formater inserts whitespaces that break inline-block css!!!
return $html;

$formatter = new Formatter();

return $formatter->format($html);
}

Expand Down
52 changes: 47 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ About
This TYPO3 extension

1. encourages and supports the development of self-contained, re-usable function and design modules ("**components**") along with your TYPO3 project and
2. exposes these components via a JSON API so that **component library, testing and styleguide tools** like [Fractal](http://fractal.build) can [extract and render](https://github.com/tollwerk/fractal-typo3) your components individually and independently of your TYPO3 frontend.
2. exposes these components via a JSON API so that **component or pattern library, testing and styleguide tools** like [Fractal](http://fractal.build) can [extract and render](https://github.com/tollwerk/fractal-typo3) your components individually and independently of your TYPO3 frontend.

### Component types

The extension distiguishes 4 main types of components:
The extension distiguishes 5 main types of components:

* **TypoScript components**: Require a [TypoScript](https://docs.typo3.org/typo3cms/TyposcriptReference/) path with an object definition to render (e.g. `lib.menu`, defined as `HMENU`).
* **Fluid template components**: Require a [Fluid template file](https://github.com/TYPO3/Fluid) (e.g. an Extbase / Fluid partial or standalone Fluid template) and an optional set of rendering parameters / variables.
* **Extbase plugin components**: Require an [Extbase controller](https://docs.typo3.org/typo3cms/ExtbaseGuide/Extbase/Step3Documentation/ActionController.html), a controller action to call and possibly a list of parameters to pass to the controller action.
* **Content components**: Convenient way to render existing TYPO3 content elements as components. Works with both default and custom content elements.
* **Form components**: Hook into the [TYPO3 Form Framework](https://docs.typo3.org/typo3cms/extensions/form/) and treat standard and custom form elements as individual components.

The extension **doesn't impose any requirements towards your TypoScript, Fluid templates or directory layout** except that every component must be individually addressable. That is, you cannot expose e.g. just a part of a rendered Fluid template as a component. In that case, you'd have to outsource the desired part as a partial file of its own.

Expand Down Expand Up @@ -193,6 +194,46 @@ class ExampleContentComponent extends ContentComponent
}
```

#### Form component

Inside your component definition, you must use the `createElement()` method to instantiate a renderable form element (`TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable`). This is basically the same object that you get via the Form Framework's [`Page::createElement()` method](https://docs.typo3.org/typo3cms/extensions/form/ApiReference/Index.html#typo3-cms-form-domain-model-formelements-page-createelement) during API form composition. You can further configure the form element using its native methods (as you would do in a form factory class).

To simulate a form validation error for your element, simply call `addElementError($message)` (the element must have been instantiated prior to adding errors). You can registrer multiple errors.

It is advised but not mandatory to register the form field's Fluid template using `setTemplate()` for a nicer display in your component library.

```php
<?php

namespace Vendor\ExtKey\Component;

use Tollwerk\TwComponentlibrary\Component\FormComponent;

/**
* Example form component
*/
class ExampleTextComponent extends FormComponent
{
/**
* Configure the component
*/
protected function configure()
{
$this->setTemplate('EXT:example/Resources/Private/Partials/Form/Text.html');

$element = $this->createElement('Text', 'name');
$element->setProperty(
'fluidAdditionalAttributes',
[
'placeholder' => 'John Doe',
]
);

$this->addError('Please enter a name');
}
}
```

#### Common properties

There's a bunch of component properties and methods that are common to all component types. Some of them are controlled via [TypoScript constants](#typoscript-constants), others by overriding [component class properties](#component-properties) or calling [shared configuration methods](#configuration-methods).
Expand Down Expand Up @@ -428,11 +469,12 @@ As of version 0.3.2, you can add directory specific configuration values to your
```json
{
"dirsort": 4
"dirsort": 4,
"label": 'Icons & images'
}
```
It's up to the consuming application to use these values for particular purposes. The [TYPO3-Fractal-bridge](https://github.com/tollwerk/fractal-typo3) for example uses the `dirsort` value for ordering the directories other than alphabetically.
It's up to the consuming application to use these values for particular purposes. The [TYPO3-Fractal-bridge](https://github.com/tollwerk/fractal-typo3) for example uses the `dirsort` value for ordering the directories other than alphabetically and `label` for component folder names that couldn't be achieved via real file system directory names.
### Command line component kickstarter
Expand All @@ -446,7 +488,7 @@ php typo3/cli_dispatch.phpsh extbase component:create Test/Button fluid tw_tollw
The command takes 4 arguments (in the following order; you can also enter it with explicit argument names):
* `--name`: Directory path and name of the component within the `Components` directory of your provider extension.
* `--type`: Component type, must be one of `fluid`, `typoscript` or `extbase`
* `--type`: Component type, must be one of `fluid`, `typoscript`, `extbase`, `content` or `form`
* `--extension`: Provider extension key
* `--vendor`: Provider extension vendor name
Expand Down
Loading

0 comments on commit fdf01a4

Please sign in to comment.