Beefy Boxes and Bandwidth Generously Provided by pair Networks
Clear questions and runnable code
get the best and fastest answer
 
PerlMonks  

Splitting long text for Template

by Bod (Curate)
on Feb 07, 2021 at 22:35 UTC ( #11128036=perlquestion: print w/replies, xml ) Need Help??

Bod has asked for the wisdom of the Perl Monks concerning the following question:

In my quest of Refactoring webcode to use templates, I have hit an issue which I can solve in a couple of ways. But...I am trying to keep the logic separate from the display and whichever way I look at, I am struggling to maintain that separation.

On an HTML page I have a block of text pulled from a database. The length of the text is not known until runtime. If the text is too long, it is split into a visible and a hidden section with the display attribute toggled by a bit of Javascript.

<script> function toggleDisplay(el, txt) { if (document.getElementById(txt).style.display == 'none') { document.getElementById(txt).style.display = 'inline'; el.innerHTML = ' [read less...]'; } else { document.getElementById(txt).style.display = 'none'; el.innerHTML = ' [read more...]'; } } </script>
The code that does the shortening and formatting of the text will become a function in a module but it currently is a subroutine in the script that outputs all the HTML.
sub abstract { my ($visible, $hidden); if (length $_[0] < 150) { $visible = $_[0]; } else { my @words = split / +/, $_[0]; my $flag = 0; while (length $visible < 150) { $visible .= ' ' if $flag; $visible .= shift @words; $flag = 1; } $hidden = join ' ', @words; $visible .= qq[ <span id="t2" style="display:none">$hidden</sp +an> <span onClick="toggleDisplay(this, 't2');"> [read more ...]</span +>]; } return $visible; }

If I use the same code with a Template, I am going to be passing partly formatted text to the process method. This does not entirely separate the logic from the display as part of the display is created in the Perl code.

The other way I came up with was to use a callback from the template to the Perl function that splits the text. Again, this seems to tie the logic and display together.

Is there an elegant solution that I am overlooking to what must be quite a common problem?

Replies are listed 'Best First'.
Re: Splitting long text for Template
by 1nickt (Canon) on Feb 07, 2021 at 22:57 UTC

    Yes, it's a common problem. The 'elegant' solution would be to have a function in the client code (i.e. Javascript) that does the formatting/truncating once handed the complete string by Perl. Or as you say you could enable PERL directives in your template; or you could write a custom filter in Perl which Template would use in the template. Or you could probably use simple conditional IF blocks in the template. None of these are what you might call elegant, but functionality trumps elegance, and I would personally use the last mentioned approach because I dislike programming in JavaScript so much. In my experience accepting a somewhat fuzzy boundary between C and V works best for me.

    Hope this helps!


    The way forward always starts with a minimal test.
      > you could probably use simple conditional IF blocks in the template

      I fully agree. Especially if you want to hide the rest of the text for a purpose, e.g. show the full text only to the paying customers. Hiding it via JavaScript would still make them have the full text at their disposal. The distinction of a paying/not paying customer is definitely not part of the View's business.

      map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
        > The distinction of a paying/not paying customer is definitely not part of the View's business.

        But that's another use case, which would affect the Model too, cause you'd need a table customer for a paywall scenario.

        The OP said "if the text is too long"

        This can also depend on the output media is it ...

        • a large desktop browser?
        • a mobile?
        • for print?
        • ...

        Clearly presentation!

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

        Especially if you want to hide the rest of the text for a purpose, e.g. show the full text only to the paying customers.

        I hadn't even considered that as a possibility! In this case, it is only to keep the amount of text short enough to get lots of entries on a page. The user can expand any they for which they want further information.

        So, something like [% IF text.length > 150 %] - I can't see how to ensure the text is split on a word boundary with a simple IF condition.

Re: Splitting long text for Template
by Radiola (Monk) on Feb 08, 2021 at 09:48 UTC

    I have an app where the shortened “preview” can be supplied by the article author or editor – so it might not be text from the beginning of the article. For me, that makes the preview a potential attribute of the article itself. (If there’s no supplied preview, I generate one by extracting text up to the first paragraph break after a length threshold.)

    So the article has html and preview_as_html methods, and the template does something like:

    [% IF article.has_preview %] [% article.preview_as_html %] <p class="readmore"><a href="[% article.uri %]">Read more…</a></p> [% ELSE %] [% article.html %] [% END %]

    You could just as easily have the template output them both, with the full text hidden, and have your JS toggle between them.

    This also shows how I handle the model/view distinction in this app. The article object that the template sees is considered “view support” code and is a wrapper around the object from the DB. I’m doing a conversion from MultiMarkdown → HTML inside the wrapper object. (Actually, it’s not quite as simple as that or I could do that in the template with the Template::Plugin::MultiMarkdown plugin.)

    – Aaron
    Preliminary operational tests were inconclusive. (The damn thing blew up.)

      "You could just as easily have the template output them both, with the full text hidden, and have your JS toggle between them."

      You can do this without JavaScript, which if you are concerned about not impacting those who browser without this enabled by default, which IIRC search engines also take into accounts.

      That's a nice idea.

      > The article object that the template sees is considered “view support” code

      Questions are:

      • Where is this code located?
      • How do you make the distinction to "controller support" Perl code?

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Questions are:

        • Where is this code located?

        This is actually a pretty tiny app, so it’s actually in MyApp::Article, which loads the original from disk (not a DB). In a larger app I usually have a MyApp::View namespace for that stuff to go in.

        • How do you make the distinction to “controller support” Perl code?

        I’m not quite sure what you’re asking, so I’ll answer it two different ways. :)

        If you mean “how do I decide what goes in the template and what goes in a module?”: I prefer my templates be structural and (except for debugging) keep PERL blocks out of it entirely. If the code isn’t real simple, I find it’s more of a pain to write and maintain as part of the template than in a module. For example, when I have to generate a preview I generate it from the Markdown source. If you truncate Markdown source, you might end up separating the first and second parts of a reference link. That leaves Markdown bits in your rendered HTML, so you have to either clean that up or make sure you also grab the second part of any references. (Maybe instead I should render to HTML first and try to truncate that…) Either way that’s much more Perl than I want in a Template file.

        If you mean “how do I decide what’s model code and what’s view support code?”: I struggle with this distinction myself, and I’ve done it a few different ways. :D (I've actually thought about writing a question about this, but haven't gotten around to doing it.) One way I think about it is: if I had a command-line utility that needed the raw data for its own reasons, what would it want to call vs. what the web view wants to call?

        Mine are fairly small, single-person projects. I’m not part of a team. The stakes are small. I’m sure it's much different on larger team projects.

        – Aaron
        Preliminary operational tests were inconclusive. (The damn thing blew up.)
      I have an app where the shortened “preview” can be supplied by the article author or editor

      The code in the question was significantly cut down to get to the core of the problem. In the actual implementation, there are two blocks of text. One is a preview and the other is the main text. It is the preview that we need to be able to toggle between just an extract and the full copy. Although even here I missed a bit out. The creator of the text can add a <split> tag to control where the block of text is split. In reality, this is not often used but it will remain in the refactored, templated code.

      Thanks for sharing - that gives me more food for thought.

        Cool, that's very much the kind of thing I'm doing too. My logic goes:

        preview = pre-written preview || split-with-tag preview || generated preview

        The preview method (well, _build_preview — it's Moo) figures that all out so the Template only has to call one method and display what it's given.

        – Aaron
        Preliminary operational tests were inconclusive. (The damn thing blew up.)
Re: Splitting long text for Template (updated)
by LanX (Sage) on Feb 07, 2021 at 23:19 UTC
    In my experience is the MVC approach more an ideal° than a bulletproof method.

    BUT ... in this case I'd say it belongs into the View.

    One rational for MVCs is the possibility to delegate responsibility to domain experts in your team (or to be at least capable to easily scale your team up if necessary)

    So ask yourself: Whose responsibility is it?

    IMHO it'll be the decision of the designer of the View how long the visible text is and how it's presented.

    It's not the domain of the programmer (Controller) nor the DB-Admin (Model)

    Unfortunately I lack expertise with Template::Toolkit on how best to achieve this ... it has it's own mini language.

    But I suppose there is more then one way and the Monastery is full of experts. :)

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

    °) Shaky Metaphor: A good Christian might attempt to follow the example of Jesus, but not many are ready to die at the cross.

    UPDATE

    > The other way I came up with was to use a callback from the template to the Perl function that splits the text. Again, this seems to tie the logic and display together.

    I'd keep such Perl code inside a separate file/module named like view_helper.pl with an extra namespace like package View::Helper;

    See it as an extension of your template framework in the responsibility of your designer. He might delegate change requests to the programmer, if he can't handle Perl, but it's his call.

      So ask yourself: Whose responsibility is it?

      Thank you - that's the sort of great question that, when asked, can bring so much clarity. That has helped a lot.

      Implementation may prove a challenge but clarity is always a good start.

        > Implementation may prove a challenge but clarity is always a good start.

        I know, for the time being your team is small and you are playing all roles by yourself.

        But imagine having a split personality disorder, where on certain weekdays you are a "Mr. Designer" without hard programming skills.

        This cartoon reflects my experience quite well

        Your "Dr. Programmer" should try to offer your Mr. Designer a custom Perl-function to be embedded

        ($visible,$hidden) = divide_by_words($text,100)

        which is maintained in a dedicated view-helper module.

        One day you might realize that web-design is not your forte and you want to hire someone creative who is good with colors, fonts, user experience, pink unicorns and so on.

        No need to require Perl or Regex skills then, your Perl programmers will maintain the central view-helper-module for the designer.

        And one day the programmer will realize that split / +/ is not a good idea and fix it at one central place instead of dozens of templates.

        And the designer can decide to change the "100" to another number depending on output media and page.

        One anecdote: (I told this before ...)

        My first payed Perl job was in a (former) start up which created little interactive ads embedded in thousands of partnering web-sites.

        The team was divided into programmers, designers, dev-ops, some DBAs and (pseudo) management.

        To my horror I realized that JS was considered the responsibility of the designers who couldn't really code ... (well with one exception).

        That worked reasonably well as long as they only used standard features from frameworks like MooTools, but imagine the C&P disaster when they googled for more complex logic.

        I tried to argue that it'll be wiser if someone from the Perl department provided the JS coding. But that's how I learned how management "works".

        Disclaimer:

        I'm a bad designer and have the highest respect for good ones.

        If you leave web-design to programmers you'll end up in sites like this one.

        Cheers Rolf
        (addicted to the Perl Programming Language :)
        Wikisyntax for the Monastery

Re: Splitting long text for Template
by 1nickt (Canon) on Feb 08, 2021 at 01:22 UTC

    Hi Bod,

    Making use of Template's EVAL_PERL constructor option :

    use strict; use warnings; use Template; my $tt = Template->new({ EVAL_PERL => 1 }); my $str = 'This is too verbose to be printed in its entirety'; # 16 characters here ^ $tt->process(\*DATA, { string => $str }) || die $tt->error; __DATA__ Original string : [% string %] Formatted string : [%- PERL -%] if ( length '[%- string -%]' > 16 ) { (my $truncated = '[%- string -%]') =~ s/^(.{0,16}\b).*$/$1.../ +s; print $truncated; } else { print '[% string %]'; } [%- END -%]
    Output:
    Original string : This is too verbose to be printed in its entirety Formatted string : This is too ...

    Hope this helps!


    The way forward always starts with a minimal test.
      Hope this helps!

      It does a lot thanks 1nickt.
      Seeing an example can be very illuminating

Re: Splitting long text for Template
by jcb (Parson) on Feb 08, 2021 at 03:20 UTC

    Actually, using client-side JavaScript to replace the long text blocks with expand buttons and shorter text (as other monks have suggested) is the best way to handle this, because that will also gracefully degrade if the user blocks JavaScript, presenting all information, but in a less-convenient format.

    Client-side JS code can also query the available display area and set CSS properties to hide only text as needed to accommodate some minimal number of records initially displayed at once. (You can collapse records at points chosen to ensure that N records will initially fit in the user's viewport, regardless of platform, but you will need to also handle the case where the viewport is too small to accommodate N records as further graceful degradation. You can test this by simply resizing a browser window to a small size and viewing the site.)

    NoScript and similar extensions are commonly used as security measures because all or nearly all recent browser exploits have depended on JavaScript to function. I for one have a general policy of simply leaving sites that do not render with JavaScript disabled.

      > because that will also gracefully degrade if the user blocks JavaScript

      He could split the text into two span-tags on the server and deliver them. Activated JS could than hide the second portion. This would limit the necessary JS logic to a minimum.

      I'm not sure anymore if there is a possibility for server side detection of JS in the HTTP headers, worst case would be a JS-triggered redirect to another path.

      > I for one have a general policy of simply leaving sites that do not render with JavaScript disabled.

      I have a contrary view, JS is disabled by default in my browsers and I tend to ignore blank sites. That's why I prefer minimal "static" functionality with optional JS add-ons. YMMV.

      But yeah, how to handle (no) JS was not part of the MVC concepts, and still causing the biggest headache.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11128036]
Approved by 1nickt
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (2)
As of 2022-05-26 04:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you prefer to work remotely?



    Results (93 votes). Check out past polls.

    Notices?