nextras / forms-rendering

Rendering helpers for Nette Framework Forms.
https://nextras.org
MIT License
14 stars 4 forks source link

Allow installing with Latte 3 #8

Closed jtojnar closed 2 years ago

jtojnar commented 2 years ago

This relaxes the version bounds so that people can continue to use the renderers after upgrading to Latte 3.

I did not really bother fixing the latte macros themselves as they do not really seem to be maintained, only supporting Bootstrap 3. The existing latte.macros config key will just be a no-op with Latte 3, so Latte will just switch to the default macro definitions from Nette Forms.

jtojnar commented 2 years ago

For the record, porting the macros would require introducing a new LatteExtension as done in https://github.com/nette/forms/commit/fe2109ce8b77846a5f664bc412c7cf3008f63074. I tried to do that but got stuck on weird errors in Latte parser so I decided to leave the work for someone who actually uses the macros. Here is what I made so far:

diff --git a/examples/lattemacros/LatteMacrosPresenter.latte b/examples/lattemacros/LatteMacrosPresenter.latte
index f87708f..3f9bb57 100644
--- a/examples/lattemacros/LatteMacrosPresenter.latte
+++ b/examples/lattemacros/LatteMacrosPresenter.latte
@@ -12,51 +12,50 @@
    <div class="container">
        <h1>Form LatteMacros rendering</h1>
        <hr>
-       <p class="alert alert-danger" n:if="version_compare(\Latte\Engine::VERSION, '3', '>=')">The macros are currently only supported on Latte 2.</p>
        {form form class => form-horizontal}
        <div class="form-group">
-           {label text class => "label-control col-sm-3" /}
+           {label2 text class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input text}{inputError text}</div>
        </div>
        <div class="form-group">
            <div class="col-sm-9 col-sm-offset-3">
                <div class="checkbox">
-               {label checkbox}{input checkbox}{/label}
+               {label2 checkbox}{input checkbox}{/label2}
                </div>
                {inputError checkbox}
            </div>
        </div>

        <div class="form-group">
-           {label checkbox_list class => "label-control col-sm-3" /}
+           {label2 checkbox_list class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input checkbox_list}{inputError checkbox_list}</div>
        </div>
        <div class="form-group">
-           {label integer class => "label-control col-sm-3" /}
+           {label2 integer class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input integer}{inputError integer}</div>
        </div>
        <div class="form-group">
-           {label multi_select class => "label-control col-sm-3" /}
+           {label2 multi_select class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input multi_select}{inputError multi_select}</div>
        </div>
        <div class="form-group">
-           {label password class => "label-control col-sm-3" /}
+           {label2 password class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input password}{inputError password}</div>
        </div>
        <div class="form-group">
-           {label radio_list class => "label-control col-sm-3" /}
+           {label2 radio_list class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input radio_list}{inputError radio_list}</div>
        </div>
        <div class="form-group">
-           {label select class => "label-control col-sm-3" /}
+           {label2 select class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input select}{inputError select}</div>
        </div>
        <div class="form-group">
-           {label textarea class => "label-control col-sm-3" /}
+           {label2 textarea class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input textarea}{inputError textarea}</div>
        </div>
        <div class="form-group">
-           {label multi_upload class => "label-control col-sm-3" /}
+           {label2 multi_upload class => "label-control col-sm-3" /}
            <div class="col-sm-9">{input multi_upload}{inputError multi_upload}</div>
        </div>
        <div class="form-group">
diff --git a/examples/lattemacros/config.neon b/examples/lattemacros/config.neon
index b05710d..68d970d 100644
--- a/examples/lattemacros/config.neon
+++ b/examples/lattemacros/config.neon
@@ -4,8 +4,13 @@ application:
        *: NextrasDemos\FormsRendering\LatteMacros\*Module\*Presenter

 latte:
+   # For Latte 2
    macros:
        - Nextras\FormsRendering\LatteMacros\Bs3InputMacros::install

+   # For Latte 3
+   extensions:
+       - Nextras\FormsRendering\LatteMacros\Bs3InputMacrosExtension
+
 services:
    routing.router: Nette\Application\Routers\SimpleRouter('LatteMacros:default')
diff --git a/readme.md b/readme.md
index dea8014..a2316c2 100644
--- a/readme.md
+++ b/readme.md
@@ -12,7 +12,7 @@ Form renderers:
 - *Bs4Renderer* - renderer for Bootstrap 4 with support for horizontal, vertial and inline mode;
 - *Bs5Renderer* - renderer for Bootstrap 5 with support for horizontal, vertial and inline mode;

-Latte Macros renderers (only for Latte 2):
+Latte Macros renderers:
 - *Bs3InputMacros* - modifies Form Macros to add Bootstrap 3 classes automatically;

 ### Installation
@@ -23,7 +23,7 @@ The best way to install is using [Composer](http://getcomposer.org/):
 $ composer require nextras/forms-rendering

-Register Bs3InputMacros using Nette DI config (only for Latte 2): +Register Bs3InputMacros using Nette DI config:

 latte:
diff --git a/src/LatteMacros/Bs3InputMacrosExtension.php b/src/LatteMacros/Bs3InputMacrosExtension.php
new file mode 100644
index 0000000..d7bb6a9
--- /dev/null
+++ b/src/LatteMacros/Bs3InputMacrosExtension.php
@@ -0,0 +1,27 @@
+<?php declare(strict_types = 1);
+
+/**
+ * This file is part of the Nextras community extensions of Nette Framework
+ *
+ * @license    MIT
+ * @link       https://github.com/nextras/forms-rendering
+ */
+
+namespace Nextras\FormsRendering\LatteMacros;
+
+use Latte;
+
+
+/**
+ * Latte v3 extension for Nette Forms.
+ */
+final class Bs3InputMacrosExtension extends Latte\Extension
+{
+   public function getTags(): array
+   {
+       return [
+           'label2' => [Nodes\Bs3\LabelNode::class, 'create'],
+           // 'input' => [Nodes\Bs3InputNode::class, 'create'],
+       ];
+   }
+}
diff --git a/src/LatteMacros/Nodes/Bs3/LabelNode.php b/src/LatteMacros/Nodes/Bs3/LabelNode.php
new file mode 100644
index 0000000..1ea2f0b
--- /dev/null
+++ b/src/LatteMacros/Nodes/Bs3/LabelNode.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types = 1);
+
+/**
+ * This file is part of the Nextras community extensions of Nette Framework
+ *
+ * @license    MIT
+ * @link       https://github.com/nextras/forms-rendering
+ */
+
+namespace Nextras\FormsRendering\LatteMacros\Nodes\Bs3;
+
+use Latte\Compiler\Nodes\StringNode;
+use Latte\Compiler\PrintContext;
+use Nette\Forms\Controls\BaseControl;
+use Nette\Utils\Html;
+use Nextras\FormsRendering\LatteMacros\Nodes\LabelNode as BaseLabelNode;
+
+
+class LabelNode extends BaseLabelNode
+{
+   public static function label(Html $label, BaseControl $control, bool $isSubItem): Html
+   {
+       if ($label->getName() === 'label' && !$isSubItem) {
+           $label->addClass('control-label');
+       }
+
+       return $label;
+   }
+}
diff --git a/src/LatteMacros/Nodes/LabelNode.php b/src/LatteMacros/Nodes/LabelNode.php
new file mode 100644
index 0000000..847c000
--- /dev/null
+++ b/src/LatteMacros/Nodes/LabelNode.php
@@ -0,0 +1,46 @@
+<?php declare(strict_types = 1);
+
+/**
+ * This file is part of the Nextras community extensions of Nette Framework
+ *
+ * @license    MIT
+ * @link       https://github.com/nextras/forms-rendering
+ */
+
+namespace Nextras\FormsRendering\LatteMacros\Nodes;
+
+use Latte\Compiler\Nodes\StringNode;
+use Latte\Compiler\PrintContext;
+use Nette\Bridges\FormsLatte\Nodes\LabelNode as NetteLabelNode;
+use Nette\Forms\Controls\BaseControl;
+use Nette\Utils\Html;
+
+
+class LabelNode extends NetteLabelNode
+{
+   public function print(PrintContext $context): string
+   {
+       return $context->format(
+           ($this->name instanceof StringNode
+               ? 'if ($ʟ_label = end($this->global->formsStack)[%node]->'
+               : '$ʟ_input = is_object($ʟ_tmp = %node) ? $ʟ_tmp : end($this->global->formsStack)[$ʟ_tmp]; if ($ʟ_label = $ʟ_input->')
+           . ($this->part ? ('getLabelPart(%node)') : 'getLabel()')
+           . ') echo ' . static::class . '::label($ʟ_label'
+           . ($this->attributes->items ? '->addAttributes(%2.node)' : '')
+           . ($this->void ? ' %3.line' : '->startTag() %3.line')
+           . ', $ʟ_input, %2.var)'
+           . ('; %4.node if ($ʟ_label) echo $ʟ_label->endTag() %5.line;'),
+           $this->name,
+           $this->part,
+           $this->attributes,
+           $this->position,
+           $this->content,
+           $this->endLine,
+       );
+   }
+
+   public static function label(Html $label, BaseControl $control, bool $isSubItem): Html
+   {
+       return $label;
+   }
+}