Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#1 2020-04-18 23:30:29

david@druna.cz
Member
Registered: 2017-07-25
Posts: 41

Custom short-tags more advanced uses + PHP interplay

Just by luck I read a post here that upgrade to 4.7.0 would have broken my site because PHP in smd macros would stop working (still running 4.6.2) and that upgrading to 4.8.0 should be fine again. Also I learned that smd_macro is now considered obsolete. Standing just before writing quite a bit of new macro code I’m looking into how I can easily reimplement my existing macros with the new Custom short-tags feature. There are is a couple of points I couldn’t find answer for in the documentation.

  1. How can I access the inner content of the tag, when using it as a container tag? (In docs only attribute values are accessed.)
  2. What would be the best practice for interating with PHP inside the custom tag, ie. how to get the arguments into PHP variables?

Another question would be – is the new core solution better in any way that smd_macros? Two remarks:

  1. It seems an easy way to declare set of parameters (outside simple comments) is missing, is that so?
  2. What I was hoping for in future versions of macros was to be somehow able to provide multiple sections of inner contents, meaning multiple instances of {smd_container}. (Using that the custom tag could be for instance a layout element three_column_layout with three separate inner content sections).

Last edited by david@druna.cz (2020-04-18 23:32:21)

Offline

#2 2020-04-19 07:11:23

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 3,810
Website

Re: Custom short-tags more advanced uses + PHP interplay

You can access the inner content of a container tag with txp:yield. For example for a summary and detail combo, you could make a short-tag form called summary:

<details class="reveal">
    <txp:yield name="title" wraptag="summary" class="reveal__title" />
    <txp:yield />
</details>

And then call that with:

<txp::summary title="Banana">An elongated, edible fruit – botanically a berry – produced by several kinds of large herbaceous flowering plants in the genus Musa</txp::summary>

which gives you:

<details class="reveal">
    <summary class="reveal__title">Banana</summary>
    An elongated, edible fruit – botanically a berry – produced by several kinds of large herbaceous flowering plants in the genus Musa
</details>

If you want, you can use if_yield to defend against forgetting to supply the title attribute.

Alternatively, you can supply a default, e.g. <txp:yield name="title" default="Further information" />.

You can access Textpattern variables in php using $variable['variable_name'], for example:

<txp:variable name="favourite_fruit">banana</txp:variable>
<txp:php>
    global $variable;
    $variable['favourite_fruit'] = "apple";
</txp:php>
<txp:variable name="favourite_fruit" />

which returns apple.

EDIT: if you meant getting at the specified yield attributes in PHP, then you can do:

$php_var = parse('<txp:yield name="attribute_name" default="whatever" />');

See this thread comment by Stef for an example.

an easy way to declare set of parameters

I’m not sure I understand this. Can you elaborate? You can, for example, do a txp:hide block in your short-tag form providing documentation for yourself of the expected variables. You can also use the default attribute to do things like Stef’s smd_none trick but you may no longer need that because txp:if_yield (has the attribute been supplied at all?) and txp:if_yield name="attribute_name" value="" (is the attribute empty?) are different (which was were smd_none came in if I remember correctly). And you can use <txp:if_yield name="attribute"> … <txp:else /> … {error message} … </txp:if_yield> to guard against missing input.

What I was hoping for in future versions of macros was to be somehow able to provide multiple sections of inner contents

Can you elaborate on that too?

A lot of column-based markup can now be done with various CSS methods, e.g. a class on the wrapper tag, flex fit layouts or min-max with CSS grids, but I’m sure you know that.

Maybe you can do something like use a separator, e.g. ##### that you search and replace into a </div><div class="col"> in your short code, but I’m just guessing at what your situation is.


TXP Builders – finely-crafted code, design and txp

Offline

#3 2020-04-19 11:34:19

david@druna.cz
Member
Registered: 2017-07-25
Posts: 41

Re: Custom short-tags more advanced uses + PHP interplay

an easy way to declare set of parameters

As in a function. To see at first glance the form takes given parameters and if they’re not supplied there’s trouble. Also ideally the tag would throw an error if some of the parameters were missing. Finally the default values should be apparent at this signature as opposed to a distant place where are being used.

As far as I can see, currently we’re looking at the following code. It’s a case where parameters 1 and 3 are mandatory and don’t have a default value and parameter 2 is optional with a default value.

<txp:if_yield name="parameter-1">
<txp:if_yield name="parameter-3">
  <txp:variable name="argument-1">
    <txp:yield name="parameter-1" />
  </txp:variable>
  <txp:variable name="argument-2">
    <txp:yield name="parameter-2" defaut="default-value-2" />
  </txp:variable>
  <txp:variable name="argument-3">
    <txp:yield name="parameter-3" />
  </txp:variable>

  <txp:php>
      global $variable;
      $arg1 = variable['argument-1'];
      $arg2 = variable['argument-2'];
      $arg3 = variable['argument-3'];

      ... do stuff
  </txp:php>

</txp:if_yield>
</txp:if_yield>

That feels a bit cluttered. (Also I’m not sure what would the best way to make the form fail?) The way parameters were pointed out in smd_macros worked quite well for me.

Alternatively hopefully we could do something like the folowing when choosing to do the check in PHP.

<txp:variable name="argument-1">
  <txp:yield name="parameter-1" />
</txp:variable>
<txp:variable name="argument-2">
  <txp:yield name="parameter-2" defaut="default-value-2" />
</txp:variable>
<txp:variable name="argument-3">
  <txp:yield name="parameter-3" />
</txp:variable>

<txp:php>
run_form();

function run_form() {
  if(!isset(variable['argument-1'])) return;
  $argument2 = isset(variable['argument-2']) ? 'default-value-2' : '';
  if(!isset(variable['argument-3'])) return;

  doStuff(variable['argument-1'], $argument2, variable['argument-3']);
}
</txp:php>

I would be hoping in something like the following. The given parameters would then automatically be exported to variables of the same name. Additionally the form exeution would fail in case one of the parameter missing the ‘optional’ or ‘default’ attribute would be missing.

<txp:parameter name="parameter 1" />
<txp:parameter name="parameter 2" default="default-value-2" />
<txp:parameter name="parameter 1" />

<txp:php>
$argument1 = variable(array('name' => 'parameter 1'));
$argument2 = variable(array('name' => 'parameter 2'));
$argument3 = variable(array('name' => 'parameter 3'));

doStuff($argument1, $argument2, $argument3);
</txp:php>

Or even better, the <txp:parameter> would place the variables in a dedicated input arguments array accessible in PHP.

<txp:parameter name="parameter 1" />
<txp:parameter name="parameter 2" default="default-value-2" />
<txp:parameter name="parameter 1" />

<txp:php>
$argument1 = $FORM_ARGS['parameter 1'];
$argument2 = $FORM_ARGS['parameter 2'];
$argument3 = $FORM_ARGS['parameter 3'];

doStuff($argument1, $argument2, $argument3);
</txp:php>

What I was hoping for in future versions of macros was to be somehow able to provide multiple sections of inner contents

The search for a delimiter would be my first choice, but best would be if it could handled for us as with the <txp:else /> tag. Then it could be something like the following.

<txp:three_cols>
<p>I live in the first column.</p>
<txp:separator>
<p>I live in the second column.</p>
<txp:separator>
<p>I live in the third column.</p>
</txp:three_cols>

And then use it in the form.

<txp:parameter block at="1" />
<txp:parameter block at="2" />
<txp:parameter block at="3" />

Sometimes it could be very cool to be able to name those sections and therefore have named block arguments for the form.

<txp:three_cols>
<txp:section name="first-column">
<p>I live in the first column.</p>

<txp:section name="second-column">
<p>I live in the second column.</p>

<txp:section name="three-column">
<p>I live in the third column.</p>
</txp:three_cols>

And then use it in the form.

<txp:parameter block name="first-column" />
<txp:parameter block name="second-column" />
<txp:parameter block name="third-column" />

Last edited by david@druna.cz (2020-04-19 11:52:32)

Offline

#4 2020-04-19 12:33:40

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 9,328
Website

Re: Custom short-tags more advanced uses + PHP interplay

david@druna.cz wrote #322634:

As in a function. To see at first glance the form takes given parameters and if they’re not supplied there’s trouble.

Use <txp:hide> at the top of your Form code, as jakob says. Keeps everything together. If you’re comfortable with PHPDoc syntax then by all means do this:

<txp:hide>
/**
 * My function description
 * @param  parameter1  Param description.
 * @param  parameter2  Param description. Default: value2
 */
</txp:hide>

<txp: ... rest of your shortcode here

ideally the tag would throw an error if some of the parameters were missing.

That’s been mentioned by jakob above. Use <txp:if_yield> and throw a warning message to the screen in the <txp:else /> portion.

That feels a bit cluttered.

It is! Why do you need to go through the assign-to-variable dance, unless you’re using them later? Use the yield/if_yield directly. You can even use those in PHP if you like:

<txp:php>
$argument1 = parse('<txp:yield name="parameter1" />');
$argument2 = parse('<txp:yield name="parameter2" default="value2" />');
$argument3 = parse('<txp:yield name="parameter3" />');

doStuff($argument1, $argument2, $argument3);
</txp:php>

The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#5 2020-04-19 17:45:52

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 3,810
Website

Re: Custom short-tags more advanced uses + PHP interplay

You can always bunch your error handling at the top, for example something like:

<txp:variable name="missing_parameters" escape="trim">
    <txp:if_yield name="parameter-1" not>[parameter-1]</txp:if_yield>
    <txp:if_yield name="parameter-3" not>[parameter-3]</txp:if_yield>
</txp:if_variable>
<txp:if_variable name="missing_parameters" value="" not>
    <p class="plugin_error">Missing <txp:variable name="missing_parameters" /> in <code>txp::your_short_tag</code>.</p>
<txp:else />
    <!-- your txp short tag form -->
</txp:if_variable>

If you’re worried about the extra code up front, you can wrap it in <txp:hide process> … </txp:hide> which suppresses any output but still processes the contents of the hidden block.

As regards your other idea of separation, you almost answered your own question with txp:separator but I’ll come back to that. You were probably just writing quickly, but in the interest of avoiding unnecessary misunderstandings just to clarify:

  • Short tags are denoted by a double colon. So you need txp::your_short_tag to call it. Otherwise, Textpattern will throw an unknown tag error.
  • txp tags must be either a container or if standalone then end with xml-style /> so your examples with txp:section and txp:parameter as they stand in your example will throw an unclosed tag error.
  • txp::parameter block name="xyz" means the short tag txp::parameter with attributes block="1" and name="xyz". Use an underscore if you meant to group them, e.g. txp::parameter_block or block_name.
  • txp:section is already used by Textpattern to output the section name/title…

Apologies if that was all obvious.

To come back to your example: you could probably make it more adaptable by using a more general term that doesn’t tie it to specifically three columns. For the sake of argument, I’ll call it txp::module and make it produce the HTML code for a single item. With the help of a wrapper attribute and a second txp::separator short-tag, you can use it to create a single, two-col, three-col, five-col etc. container. You can also use it to make individual modules that you place where you want.

<txp:if_yield name="wrapper" value="1">
<div class="wrapper<txp:yield name="wrapclass" wraptag=" <+>" />">
</txp:if_yield>
    <div class="module">
        <txp:yield />
    </div>
<txp:if_yield name="wrapper" value="1">
</div>
</txp:if_yield>

(the wraptag=" <+>" means if it exists, output it preceded by a space, where <+> is a placeholder for the content … saves you having to wrap it in a an if_ statement just for the space).

Then make a second short tag called txp::separator:

    </div>
    <div class="module">

Now you can do:

<txp::module wrapper wrapclass="three-col">
    <h3>First column header</h3>
    <p>I live in the first column.</p>
<txp::separator />
    <h3>Second column header</h3>
    <p>I live in the second column.</p>
<txp::separator />
    <h3>Third column header</h3>
    <p>I live in the third column.</p>
</txp::module>

That’s a pretty simple which works for however many columns you want. The class is entirely arbitrary but may help for your css. You then get:

<div class="wrapper three-col">
    <div class="module">
        <h3>First column header</h3>
        <p>I live in the first column.</p>
    </div>
    <div class="module">
        <h3>Second column header</h3>
        <p>I live in the second column.</p>
    </div>
    <div class="module">
        <h3>Third column header</h3>
        <p>I live in the third column.</p>
    </div>
</div>

You can equally output each module separately into a txp:variable and then place them where you want in your code, for example:

<txp:variable name="module_col1" escape="trim">
    <txp::module>
        <h3>First column header</h3>
        <p>I live in the first column.</p>
    </txp::module>
</txp:variable>

You can then place it anywhere you want elsewhere on the page with <txp:variable name="module_col1" />.

In the above I did a container with a separator as a second short-tag but you could reverse the principle and make a txp::module short-tag for each item, and then enclose them in a second txp::module_container short-tag that does the wrapper. That could be better if each module is more complex with lots of attributes for each one, e.g. you might be making a ‘card’ component with a title, img_id, content, link_url and perhaps also a label. Then you’d do something like:

<txp::module_wrapper class="three-col" header="Travel films season">

<txp::module title="Wild" img_id="345" link_url="/films/wild" label="PREVIEW">
Cheryl Strayed decided to walk the Pacific Crest Trail to face her demons
</txp::module>

<txp::module title="Tracks" img_id="347" link_url="/films/my-big-fat-divorce" label="Coming Friday">
A nine-month trek across the Australian desert on camels
</txp::module>

<txp::module title="Into The Wild" img_id="348" link_url="/films/into-the-wild" label="Next week">
Alexander Supertramp gave away all his possessions and money to charity and hitchhiked across Northern America to Alaska
</txp::module>

</txp::module_wrapper>

I’ll leave you to work out the actual short-tags for those yourself, but as you can see there are lots of ways of going about it.


TXP Builders – finely-crafted code, design and txp

Offline

#6 2020-04-19 18:41:51

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 9,328
Website

Re: Custom short-tags more advanced uses + PHP interplay

That’s clever, jakob. I wonder if the separator could be simplified even further (or even designed out) with a break attribute. Not sure. Might have got my wires crossed here.


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#7 2020-04-20 16:31:57

david@druna.cz
Member
Registered: 2017-07-25
Posts: 41

Re: Custom short-tags more advanced uses + PHP interplay

Bloke wrote #322637:

Use <txp:hide> at the top of your Form code, as jakob says. Keeps everything together. If you’re comfortable with PHPDoc syntax then by all means do this:

Sure that’s an option, however then you have to keep two places in sync – comments and code – which is unfortunate.

It is! Why do you need to go through the assign-to-variable dance, unless you’re using them later? Use the yield/if_yield directly. You can even use those in PHP if you like:

True, that looks better. Although using the parse method always feels kind of hacky to me. I would probably call a yield method instead. Does it return undefined so that it’s possible to test whether the argument has been provided?

Offline

#8 2020-04-20 17:17:32

david@druna.cz
Member
Registered: 2017-07-25
Posts: 41

Re: Custom short-tags more advanced uses + PHP interplay

jakob wrote #322641:

You can always bunch your error handling at the top, for example something like:

<txp:variable name="missing_parameters" escape="trim">...

Thank you for the tip. I must say that’s a lot of code for a task that will probably be very frequent with the new custom short-codes.

If you’re worried about the extra code up front, you can wrap it in <txp:hide process> … </txp:hide> which suppresses any output but still processes the contents of the hidden block.

Interesting construct. I don’t understand how it would help in this case though. Can you elaborate more?

As regards your other idea of separation, you almost answered your own question with txp:separator but I’ll come back to that. You were probably just writing quickly, but in the interest of avoiding unnecessary misunderstandings just to clarify:

  • Short tags are denoted by a double colon. So you need txp::your_short_tag to call it. Otherwise, Textpattern will throw an unknown tag error.
  • txp tags must be either a container or if standalone then end with xml-style /> so your examples with txp:section and txp:parameter as they stand in your example will throw an unclosed tag error.
  • txp::parameter block name="xyz" means the short tag txp::parameter with attributes block="1" and name="xyz". Use an underscore if you meant to group them, e.g. txp::parameter_block or block_name.
  • txp:section is already used by Textpattern to output the section name/title…

Apologies if that was all obvious.

All true. I don’t have that all in my head well enough yet. Except for the txp:separator which was supposed to be a hypothetical core language construct, and the boolean attribute block, that was on purpose and would be a way to distinguish attribute parameters from block parameters.

To come back to your example: you could probably make it more adaptable by using a more general term that doesn’t tie it to specifically three columns. For the sake of argument, I’ll call it txp::module and make it produce the HTML code for a single item. With the help of a wrapper attribute and a second txp::separator short-tag, you can use it to create a single, two-col, three-col, five-col etc. container. You can also use it to make individual modules that you place where you want.

It’s probably important to state one thing here – I’m aiming at creating modules used by non-programmers. The way you mentioned might save some work for a programmer but aren’t trivial enough to be used by content editors.

The whole responsibility of creating an n-column layout should be on the n-col shortcode. By creating a separator short-code in a way you wrote takes away that responsibility and divides it between the wrapper and the separator and on top of that puts another responsibility on the editor to know that it is exactly the separator element that goes into the wrapper element.

What if there is another element apart from n-col layout that takes multiple blocks of code? Will there be a separator-2 element and the editor will have to learn which separator goes into which wrapper element? Some knowledge of rules/syntax is always needed from everyone but this needs to be a unified separator element without logic of its own, acting really as just a separator and it should be responsibility of the containing element to provide the logic.

Finally these short-codes are an attempt to abstract from things like css classes so their users shouldn’t worried about providing them. So as I see it we’re looking at a need for the container to be able to inspect its contained content.

Anyway thank you for this dicussion, I really appreciate it.

Last edited by david@druna.cz (2020-04-20 17:24:59)

Offline

#9 2020-04-20 17:50:02

Bloke
Developer
From: Leeds, UK
Registered: 2006-01-29
Posts: 9,328
Website

Re: Custom short-tags more advanced uses + PHP interplay

david@druna.cz wrote #322652:

then you have to keep two places in sync – comments and code – which is unfortunate.

Not sure I get that reservation, sorry. In PHP you have to keep the PHPDoc in sync with the function signature by hand (unless you use a fancy IDE that can automate that?) so it’s no different here. The comments sit above the code in the same form so if you change your parameters, you change your docblock too. It’s not self-documenting, sadly!

using the parse method always feels kind of hacky to me. I would probably call a yield method instead.

I used to think the same but I had my mind changed by Jukka. Using the yield() method works fine now, but if we change the function signature in core or move all tag handling to classes (which we’ve started and will complete in time), your code may break.

Using the tag construct inside parse() is more future-proof, as we rarely break public tag syntax, for backwards-compatibility reasons.


The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.

Txp Builders – finely-crafted code, design and Txp

Offline

#10 2020-04-20 21:04:44

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 3,810
Website

Re: Custom short-tags more advanced uses + PHP interplay

Short-tags as they stand at the moment are effectively the existing mechanism of txp forms into which arbitrary parameters can be passed, and which can now be called by their own name. I agree they are no replacement for a full-blown php function, and you have to do your defensive mechanisms and any clearing up yourself.

david@druna.cz wrote #322653:

Thank you for the tip. I must say that’s a lot of code for a task that will probably be very frequent with the new custom short-codes.

I guess so. Maybe I went overboard by collecting the error messages and outputting an informative message rather than just using if_yield name="xyz" not and spitting out an error and exiting at the first opportunity. I imagine the same idea of collecting error messages in PHP would not be so different.

Interesting construct. I don’t understand how it would help in this case though. Can you elaborate more?

No, there’s no need for it here. I simply sometimes wrap all my variable definitions in <txp:hide process> – everything here is processed but nothing is output ... </txp:hide> so that I can write and comment freely without inadvertently outputting something. I do the same for key page variables, and gather them in a separate form outside of the html_head form to avoid the messy tiered if_search / if_author / if_category / if_section / if_article_list recurring over and over in the html_head form.

It’s probably important to state one thing here – I’m aiming at creating modules used by non-programmers. The way you mentioned might save some work for a programmer but aren’t trivial enough to be used by content editors.

You’re right, end users don’t need to know the inner workings. I can imagine inserting #### or <txp:separator /> is not too difficult for a user to comprehend as it equates visually to a column break, Similarly, wrapping each module individually is not a huge cognitive leap. Adding the wrapper is perhaps stretching it, unless it corresponds to a visual element /section of the page … which is what I had in my minds eye in the above example, e.g. a block with a tinted background and title with x-items inside it.

The whole responsibility of creating an n-column layout should be on the n-col shortcode // it should be responsibility of the containing element to provide the logic …

Yes, that sounds reasonable. You can, of course, make a specific three_col short-tag (… and a two_col tag and a four_col tag) if you prefer. If the two_col, three_col and four_col tags end up being essentially similar but with some different presets at the beginning, you could perhaps set the preset in each short-tag and pass that on to a nested output_form / short-tag that does the repeating code. Essentially you’d then have three separate named tags instead of one tag with an attribute.

… there is a need for the container to be able to inspect its contained content.

Yes, that’s not so straightforward. An off-the-cuff idea: maybe you can run a counter that advances with each separator / contained item to add code at intervals where you need it. txp:variable now has an add and reset attribute. You might be able to combine that with txp:evaluate and a divisible query to produce code every 2/3/4 items.

Or if you use a specific identifiable separator string, maybe you can use smd_each or rah_repeat to create iteration patterns. Just some ideas.

There is a breakby and a breakform attribute but that is for txp:article/_custom or category/section lists where distinct items are being processed. Even then, you’re still splitting the break element processing off into a separate form, so not fundamentally different to the txp::separator principle.

EDIT: See this thread for a related discussion of some of this.

What if there is another element apart from n-col layout that takes multiple blocks of code?

How about storing the code for the separator needed for the respective container as a variable at the top of the container tag and having your txp::separator short-tag output that variable. That way the same txp:separator tag would work correctly in different container tags.

Sorry, I know that’s not the ideal case you were thinking of / hoping to hear, but I’m trying to think of possible solutions with what we have.

Anyway thank you for this dicussion, I really appreciate it.

You’re welcome. It is interesting to see other people’s needs and the limits people come up against.

One final question: did you manage to do all that before with smd_macro? Maybe you could share that? In the posts above, I’ve been guessing at what you might mean or abstracting from my own site designs, but we might get to more concrete results if we knew better what you are trying to achieve.

@bloke: does smd_macro not work anymore in txp 4.7/4.8 or is it just ‘deprecated’ because there’s now an official mechanism? I know rah_beacon worked with 4.7 and handled the tag-registering of user-created macros.


TXP Builders – finely-crafted code, design and txp

Offline

Board footer

Powered by FluxBB