Jump to content

Retrieve The Type of A Field From Inside A Module


Recommended Posts

Hi,

I want to remove the surrounding paragraph tags, <p> and </p> from the output of the Textile text formatter, but only for associated fields of type text.

The reason for this is that the additional paragraph tags often interfere with the page markup, e.g. when output inside header tags like this:

//Text: "Hello World!"

<h1><?php echo $page->fieldUsingTextile; ?></h1>

// Output:
// <h1><p>Hello World!</p><h1>

The module code is short and easy to follow:

    public function format(&$str) {
        require_once($this->config->paths->TextformatterTextile . 'classTextile.php');
        $textile = new Textile();
        $str = $textile->TextileThis($str);
    }

 In order to strip out the surrounding paragraph tag, I added the following line at the bottom of the function:

// Remove surrounding paragraph tags
$str = preg_replace('/<p>(.*)<\/p>/', '$1', $str);

The problem is that his code strips the outer paragraph tags for fields of type text and textfield. However, removing the outer paragraph tags on a possible multi-paragraph textfield does not make much sense and invalidates the HTML.

So I need a way to say something like this:

if (getFieldtype == 'Text' || getFieldtype == 'TextLanguage') {
  // remove paragraph tags as shown above
}

Any ideas are highly appreciated!

Cheers,

Stefan

Link to comment
Share on other sites

All you need is:

if($fields->get("fieldUsingTextile")->type == "FieldtypeText"){

I haven't played with textile, but I am curious why a plain text field would have paragraph tags in the first place. Are they coming from textile, or are they being pasted in and not stripped, as they would be in a normal plain text field?

Link to comment
Share on other sites

@adiran

Thanks for your reply.

To be honest, I don't quite understand the code you suggested. The selector "fieldUsingTextile" suggests I need to know the name of the field in advance, but what I want to accomplish is that whatever field calls the format function of this module (here is the complete code on Github) will get checked for its type. If the field type is FieldtypeText or TextLanguage, the surrounding paragraph tags are stripped, otherwise no further transformation is applied.

All the Textile module does is calling the (official, I guess) Textile class, so this is probably where the additional paragraph tags are coming from.

Cheers,

Stefan

Link to comment
Share on other sites

You can create a new text formater that does only that instead of modifying textile. Than you only have to apply it after textile on the fields where you need it.

Link to comment
Share on other sites

@diogo

Thanks for your reply.

Acually that is what I have been doing. I am using the Textile module on textfield fields and my own module on text fields.

I just wanted to be able to make the selection dynamically, instead of having to manually choose one of the formatters whenever I create a new text/textfield field type.

Also, I'd like to better understand inner workings of Processwire. So I am also asking this question out of curiosity.

Cheers,

Stefan

Link to comment
Share on other sites

Ok, there is $field->textformatters that retrieves an array of all textformatters, you can use it for instance like this:

if (in_array('TextformatterTextile', $field->textformatters)) //do something

  • Like 2
Link to comment
Share on other sites

Textile Textformatter still uses deprecated syntax

public function format(&$str) {

New is

public function formatValue(Page $page, Field $field, &$value) {

So you can't hook into and check for the field

Just add a second textformatter already in core "TextformatterPstripper" (wich uses the old syntax too btw) after the textile formatter on your text field. Easy.

  • Like 3
Link to comment
Share on other sites

If a textformatter uses fomatValue you could hook into a get the field

$this->hookAfter("TextformatterTextile::formatValue", $this, "hookTextile");
...
public function hookTextile($event){
    $field =  $event->arguments("field");
    $value = $event->arguments("value");
    if($field->type == "FieldtypeText") $value = str_replace(array("<p>","</p>"), "", $value);
}
  • Like 3
Link to comment
Share on other sites

@soma

Thanks for your great advice!

I guess I could also just copy the Textile code into my own module, replace the format function with formatValue and then retrieve the type of the field with $field->type.

It's a bit late already, but I'll try tomorrow. 

Cheers, 

Stefan

Link to comment
Share on other sites

@soma

I read your post  :) I also read the code of TextformatterPstripper, and does indeed what I need. However, I wanted to be able to always just select one formatter for both text fields and textfield fields, and then remove the surrounding paragraph tags dynamically, based on the type of the field inside the module. 

I know, this might be a bit overkill for such a small problem, but at least I was forced to find a way to retrieve the type of a field. When I could not find a solution, I asked here in the forum. 

Thanks again for your suggestions. They really helped me a lot!

Cheers, 

Stefan

Link to comment
Share on other sites

@bytesource

I'm one of the maintainers of Textile and I'm curious about your use of it for things like header fields (for which textile already has a syntax; 'h1.') Are you mainly trying to access Textile's typographic features such as automatic curly quotes etc in your fields? Do your fields ever hold more than a paragraph's worth of text?

If you only need typographic features for single lines, then we really have a new use-case for Textile which has (to date) been focused on transformation of documents rather than just fields and hence why it treats your single line fields as paragraphs by default.

I'm overdue updating the Textile textFormatter and hope you can get back to me soon as it may be possible to get this sorted out and included in the update should it prove relatively simple.

  • Like 1
Link to comment
Share on other sites

@netcarver

I use the Textile module on both fields of type text and textfield:

textfield:

Multi-line/multi-paragraph. The output is (and should be) at least one paragraph, so no problem here with the paragraph tags.

text:

Single line, mainly used for headers (h1-h5). Enclosing the output in a paragraph interferes with the styling of the site.

I know that I could just, e.g. for a h2 heading, write the text as 

h2. This is my main header

But if I later wanted to change h2 to h3, I had to go through all occurrences of this field in the admin and carry out the change. 

That's why I would prefer Textile to not wrap the output of a (single line) text field into paragraph tags. This would allow me to write code like this:

// some template file

// field type: text
// field name: header
// content: Hello World!

<h2><?php echo $page->header; ?></h2>
// => <h2>Hello World!</h2>

In case I later wanted to change h2 to h3, I only needed to make one small change in the template file.

Cheers, 

Stefan

Link to comment
Share on other sites

Hi Steve,

As for me, typographic changes on single line text fields (Text and TextLanguage) mostly comprise of making a word bold or cursive. Sometimes I also include a <span> tag (%some word%) that holds a CSS class, to achieve a more custom styling. 

 I have found that I never need a single line text to be wrapped in paragraph tags. However, I am not a web designer, so my use case might be a little bit unusual. Therefore I'd love to hear the opinion on this topic from someone more experienced than me. 

Cheers, 

Stefan

Link to comment
Share on other sites

Not following whole discussion, but just mentioning that you can combine multiple textformatters for single field. Like textile + paragraph stripper.

That's what I was trying to communicate, but no luck. :) It's what I do in a similar situation and it's the perfect solution to this problem. The paragraph stripper is exactly built for this. :D

But why simple if you can make it complicated.

Link to comment
Share on other sites

@Soma

As in my case a single text field will never be wrapped in a paragraph, I like the idea of handling this case dynamically, instead of adding a second formatter to every field of type text. 

Thanks to your help this actually was not that complicated to accomplish. 

First I 'updated' the Textile module to use formatValue() instead of format():

public function formatValue($page, $field, &$value) {
    require_once($this->config->paths->TextformatterTextile . 'classTextile.php');
    $textile = new Textile();
    $value = $textile->TextileThis($value);
}
 
As formatValue() is hookable, I added the following hook to _init.php:
 
templates/_init.php
wire()->addHookAfter("TextformatterTextile::formatValue", function(HookEvent $event) {

    $field = $event->arguments("field");
    $value = $event->arguments("value");

    if($field->type == "Text" || $field->type == "TextLanguage") {
        // Remove paragraph tags on single line text fields.
        $value = str_replace(array("<p>","</p>"), "", $value);
    }
});
 
There is one problem left, though. The after hook apparently does not get called. For example, placing an echo statement somewhere inside the function body does not yield any output. 
 
That's why I would be glad if you could point me in the right direction, even though you might prefer another solution to this formatting problem.
 
Cheers, 
 
Stefan
Link to comment
Share on other sites

As in my case a single text field will never be wrapped in a paragraph, I like the idea of handling this case dynamically, instead of adding a second formatter to every field of type text. 

 
 
I don't really get the reason why not? There must be a reason why you restrain from adding the second Textformatter that does exactly what you want? :) 
foreach($fields->find("type=FieldtypeText") as $f) {
    $f->textformatters = array("TextformatterTextile", "TextformatterPstripper"); // add textformatters
    $f->save();
}

Done.

Not sure why your hook isn't working. You mentioned that you modified the textile formatter, how and where? Make sure it is really called TextformatterTextile.

  • Like 1
Link to comment
Share on other sites

@Soma

Please don't get me wrong. Your solution is quite elegantt, and I probably will pick it over the hook approach. I extended the selector to make the function work on all text fields:

 foreach($fields->find("type=FieldtypeText|FieldtypeTextLanguage") as $f) { 
    $f->textformatters = array("TextformatterTextile", "TextformatterPstripper"); // add textformatters
    $f->save();
}

The only reason I choose to use a hook is because I wanted to become more conformable with this technique. 

As for the Textile module, I replaced the current format() function (code on Github) with formatValue():

public function formatValue($page, $field, &$value) {
    require_once($this->config->paths->TextformatterTextile . 'classTextile.php');
    $textile = new Textile();
    $value = $textile->TextileThis($value);
}

I am not sure why the after hook I added does not get called. The formatValue() function does indeed exist and the name of the class is TextformatterTextile.

Cheers, 

Stefan

Link to comment
Share on other sites

I'm not against at all with you doing it with a hook, just am confused why you choose the "extra" manual hook way over what is obviously already there and can be done via adding multiple textformatters. It's exactly designed for what you need here, so have hard time seeing the need to do extra workaround with a hook. :) But if it's for learning reason, why not... :)

I'm not sure what you mean my solution is elegant? The code to add the Textformatters? Or simply using a second textformatter on the text field, which is obviously not my solution but designed best practice in PW.

I think the method you have isn't hookable because it's not hookable. :) Does it have like "___formatValue" three underscores?

Link to comment
Share on other sites

Ahm, so just missed that also with my earlier posts, it's that formatValue() or format() in Textformatters arent really hookable as it stands now.

What you would instead do is hook into the text fields and hook into formatValue(), which is where textformatters are run when output formatting is on. Those are hookable in the system as they contain ___.

So it would look like this instead of hooking a textformatter:

$this->addHookAfter("FieldtypeText::formatValue", $this, "hookTextFormating");

public function hookTextFormatting($event){
    $field = $event->arguments("field");
    $value = $event->arguments("value");
    ....
} 

Or in a template

wire()->addHookAfter("FieldtypeText::formatValue", null, "hookTextFormating");

function hookTextFormating($event){
    $field = $event->arguments("field");
    $value = $event->arguments("value");
    ....
} 

So after all coming this route, it doesn't matter if a Textformatter uses format() or formatValue().

  • Like 1
Link to comment
Share on other sites

@Soma

I find your solution elegant - the one adding a second texformatter - because it accomplishes everything in a few lines of code. Also, I am thankful for every line of code that allows me to better understand the design of Processwire. 

As for my method hooking into TextformatterTextile::formatValue, I assumed that this was the one that was hookable. Thanks to your remark and Captain Hook's confirmation, I now know I should have hooked into FieldtypeText::formatValue

So this is the code I came up with:

wire()->addHookAfter("FieldtypeTextLanguage::formatValue", function(HookEvent $event) {

    $value = $event->arguments("value");

    // Remove paragraph tags on single line text fields.
    $value = str_replace(array("<p>","</p>"), "", $value);

    $event->return = $value;
});

Unfortunately, I still don't get the intended output. What is returned is the completely unformatted text (including all Textile specific identifiers).

EDIT: Returning the unformatted string indicates the hook is called before application of the Textile textformatter. Is there any way to make sure my hook gets called on the output of the Textile textformatter?

Cheers, 

Stefan

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...