Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add n:tagname macro for changing html tag name #248

Closed
daun opened this issue Dec 20, 2020 · 7 comments
Closed

Add n:tagname macro for changing html tag name #248

daun opened this issue Dec 20, 2020 · 7 comments

Comments

@daun
Copy link

daun commented Dec 20, 2020

I'd love to see an official n:tagname macro that allows changing the html tag name of the current element.

This is useful in two scenarios:

  • Semantical & accessible applications that require differing heading levels (h1, h2, h3) throughout the app
  • Markup requirements where just removing an element via n:tag-if is too much and you'd rather turn that a into a span depending on some variable.

If you agree this is a useful feature, see the code below for a working implementation.

Example

{* title.latte *}

{default $heading = null}

<h2 n:tagname="$heading">
    {$page->title}
</h2>

This will create an h2:

{include 'title.latte'}

This will create an h3:

{include 'title.latte', heading => h3}

Prior art

Vue components have this behavior; the macro there is called is which is nice and concise:

<!-- Render a <table> tag -->
<component is="table"></component>

While the is attr is well-known and unambigious, I feel that tagName would be more obvious for quick scanning of the source code.

Implementation

I have implemented this behavior for a project before. See below. Might serve as inspiration or starting point.

    /**
     * n:tagname
     */
    public function macroTagname(MacroNode $node, PhpWriter $writer)
    {
        if (!$node->prefix || $node->prefix !== MacroNode::PREFIX_NONE) {
            throw new CompileException("Unknown {$node->getNotation()}, use n:{$node->name} attribute.");
        }
        if (!$node->args) {
            throw new CompileException("The n:{$node->name} value cannot be empty.");
        }
    }

    /**
     * n:tagname
     *
     */
    public function macroTagnameEnd(MacroNode $node, PhpWriter $writer)
    {
        $exception = CompileException::class;

        $node->openingCode = '<?php
            $__tagMacro = "' . $node->name . '";
            $__tagNameOld = "' . $node->htmlNode->name . '";
            $__tagNameNew = ' . $writer->formatArgs() . ';

            // Enforce valid tag name if defined
            if ($__tagNameNew !== null) {
                if (!is_string($__tagNameNew)) {
                    throw new ' . $exception . '("The tag name passed to n:{$__tagMacro} must be a string.");
                }
                $__tagNameNew = trim($__tagNameNew);
                if (!$__tagNameNew) {
                    throw new ' . $exception . '("The tag name passed to n:{$__tagMacro} must not be empty.");
                }
                try {
                    new \DOMElement($__tagNameNew);
                } catch (\DOMException $e) {
                    throw new ' . $exception . '("Invalid tag name supplied to n:{$__tagMacro}: {$__tagNameNew}.");
                }
            }

            ob_start(function () {});
        ?>';

        $node->closingCode = '<?php
            $__tagContent = ob_get_clean();
            if ($__tagNameNew && $__tagNameOld !== $__tagNameNew) {
                // Replace starting tag
                $__tagContent = preg_replace(
                    "/^(\s*)<" . $__tagNameOld . "\b/",
                    "$1<" . $__tagNameNew,
                    $__tagContent
                );
                // Replace end tag
                $__tagContent = preg_replace(
                    "/<\/" . $__tagNameOld . ">(\s*)$/",
                    "</" . $__tagNameNew . ">$1",
                    $__tagContent
                );
            }
            echo $__tagContent;
        ?>';
    }
@dg
Copy link
Member

dg commented Dec 20, 2020

I think it could be called simply n:tag

@dg dg closed this as completed in d739ebe Dec 20, 2020
dg added a commit that referenced this issue Dec 20, 2020
@daun
Copy link
Author

daun commented Dec 21, 2020

Wow, that was quick, thanks!

@f3l1x
Copy link
Member

f3l1x commented Dec 21, 2020

❤️

@milo
Copy link
Member

milo commented Dec 21, 2020

What if tag name is changed to script? It changes inner escaping.

@dg
Copy link
Member

dg commented Dec 21, 2020

Also, it is not possible to change void to nonvoid elements. I'll fix it.

dg added a commit that referenced this issue Dec 21, 2020
@daun
Copy link
Author

daun commented Dec 21, 2020

Is it possible to allow null as representing the current tag name, i.e. leaving the tag alone? Currently it throws an exception. Allowing null would avoid some duplication in markup. See examples below:

Currently, this is required, duplicating the h2 declaration:

<h2 n:tag="$heading ?? h2">{$title}</h2>

Allowing null, this is changed to:

{default $heading = null}

<h2 n:tag="$heading">{$title}</h2>

@daun
Copy link
Author

daun commented Dec 21, 2020

I just saw a newer commit which apparently allows null values. Please consider this comment void if that's the case.

dg added a commit that referenced this issue Dec 22, 2020
dg added a commit that referenced this issue Jan 15, 2021
dg added a commit that referenced this issue Jan 25, 2021
dg added a commit that referenced this issue Jan 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants