Jump to content

Search the Community

Showing results for tags 'templating'.

  • Search By Tags

    Type tags separated by commas.
  • Search By Author

Content Type


Forums

  • Welcome to ProcessWire
    • News & Announcements
    • Showcase
    • Wishlist & Roadmap
  • Community Support
    • Getting Started
    • Tutorials
    • FAQs
    • General Support
    • API & Templates
    • Modules/Plugins
    • Themes and Profiles
    • Multi-Language Support
    • Security
    • Jobs
  • Off Topic
    • Pub
    • Dev Talk

Product Groups

  • Form Builder
  • ProFields
  • ProCache
  • ProMailer
  • Login Register Pro
  • ProDrafts
  • ListerPro
  • ProDevTools
  • Likes
  • Custom Development

Find results in...

Find results that contain...


Date Created

  • Start

    End


Last Updated

  • Start

    End


Filter by number of...

Joined

  • Start

    End


Group


AIM


MSN


Website URL


ICQ


Yahoo


Jabber


Skype


Location


Interests

Found 11 results

  1. Plates for ProcessWire is a module to make using Plates with your ProcessWire templates plug-and-play. Plates is an extremely lightweight pure PHP templating system that provides features that developers have come to expect when building applications and sites. From the Plates website: Highlights from the documentation: Native PHP templates, no new syntax to learn Plates is a template system, not a template language Plates encourages the use of existing PHP functions Increased code reuse with template layouts and inheritance Template folders for grouping templates into namespaces Data sharing across templates Pe-assign data to specific templates Built-in escaping helpers Easy to extend using functions and extensions Plates is an extremely stable application that has been in development and use in production since 2014. This module is also a simple adapter so I am confident in it's stability as I've already used it myself. However, the custom extensions included should be considered early releases and bug reports are welcome, pull requests are very welcome as well! If you're familiar with Plates or just want to get started now, you can download the module from the Github repository. Batteries are included, documentation provided. So, FireWire, why another templating engine? There are many stellar templating engines available. I've used several of them and they have truly great features. I also appreciate the simplicity of working with PHP. While templating engines do sometimes offer more terse syntax, it's not a killer feature for everyone like code reuse, nesting, and layouts may be. Code editors will always offer first-class support for PHP and syntax highlighting makes some of the arguments about readability less of a feature benefit for alternatives. Plates takes care of the limitations that come with writing pure PHP templates. Plates feels very at home with the ProcessWire API. Like ProcessWire, it also scales infinitely and its focus on core features over the large library approach makes for a great developer experience. If you've worked with templating engines in the past, the features are familiar. If you haven't, you'll be up to speed remarkably fast. I wrote this module with the intention of making it a "drop-in and code" experience, and I've worked on using the extensibility of Plates to add some extra superpowers to boot. Plates is another great option that may be useful to you whether because it's more your style, it fits your use case, or maybe your first time adding a little extra oomph to your ProcessWire templates. The first 10 minutes you spend with the Plates documentation might be the last 10 minutes. A Simple Example Start with files and folders. Things to know off the bat: Plates for ProcessWire comes pre-configured to look for Plates templates in your /site/templates folder By default it will look for files with the extension '.plates.php' to help differentiate from ProcessWire files, however this may be customized to any extension you prefer on the module config page The folder structure here is entirely up to you, this example can be used but is not required. /site /templates /components image_gallery.plates.php /layouts main.plates.php /views home.plates.php home.php ready.php Your ProcessWire templates will contain one line that hands off rendering to Plates <!-- /site/templates/home.php --> <?=$plates->templates->render('views/home')?> Start by creating your layout. We'll keep it simple. <?php namespace ProcessWire; // /site/templates/layouts/main.plates.php /** * @property string|null $title Page title * @property string|null $description Meta description */ $navBase = $pages->get('/'); ?> <!DOCTYPE html> <html> <head> <title><?= $title ?? $page->title; ?></title> <?php if ($description ?? null): ?> <meta name="description" content="<?=$description?>"> <?php endif ?> <link rel="stylesheet" href="<?=$config->paths->templates?>styles/app.css"> </head> <body> <header class> <img src="/path/to/logo.jpg"> <nav> <ul> <?php foreach ($navBase->children->prepend($navBase) as $navPage): ?> <li> <a href="<?=$navPage->url?>"><?=$navPage->title?></a> </li> <?php endforeach ?> </ul> </nav> </header> <section> <?= $this->section('hero'); ?> </section> <?= $this->section('content') ?> <footer> <?= $this->section('footer'); ?> </footer> <script src="/path/to/your/file.js"></script> </body> </html> I like to add docblocks at the top of my Plates templates because we can pass data to any template or layout wherever needed. This is optional and just a style preference. Some notes: The full ProcessWire API is available Your Plates templates are rendered inside a Plates Template object. To use any Plates function, custom function, or custom extension you use $this Jumping over to home.plates.php <?php namespace ProcessWire; // /site/templates/views/home.plates.php $this->layout('layouts/main', ['description' => $page->description]); ?> <?php $this->start('hero') ?> <h1><?=$page->headline?></h1> <img src="<?=$page->hero_image->url?>" alt="$page->hero_image->description"> <?php $this->end() ?> <section> some stuff here </section> <section> <?php if ($page->gallery->count()): ?> <?php $this->insert('components/image_gallery', [ 'images' => $page->gallery, 'title' => __('Image Gallery'), ]) ?> <?php endif ?> </section> <section> Some stuff there </section> <?php $this->start('footer') ?> <p>Thanks for visiting</p> <?php $this->end() ?> Things to note: The full ProcessWire API is available including language functions Even though this file is located in the 'views' subdirectory, Plates is configured out of the box to use '/site/templates/' as the base directory, so you can write paths without '../' directory traversal Plates has a feature called Folders that allow you to create namespaced directory locations at any depth with nice and clean syntax. The example packaged with the module shows a demo. We chose the main layout and passed the 'description' variable which is available in main.plates.php as $description $this->start('hero') and $this->stop() capture what is output to those sections in main.plates.php, there is no limit on sections and they can have any name aside from 'content' which is reserved. Any content that exists outside of a defined start/stop section is automatically output to the 'content' section in your layout And the image gallery: <?php namespace ProcessWire; // /site/templates/components/image_gallery.plates.php /** * @property string|null $title Optional gallery title * @property Pageimages $images Images field */ ?> <div> <?php if ($title ?? null): ?> <?=$this->batch($title, 'strtolower|ucfirst')?> <?php endif ?> <ul> <?php foreach ($images as $image): ?> <li> <img src="<?=$image->url?>" alt="<?=$image->description?>"> </li> <?php endforeach ?> </ul> </div> Some more notes: You can use $this->insert() in any Plates file, including layouts. You can also nest using $this->insert() recursively and nest in other component-style files Any template can have a layout and you can nest layouts. So home.plates.php could use main.plates.php as its layout, and main.plates.php could use something like base.plates.php so layouts themselves can share code through inheritance. You can use batch() to execute multiple functions on a value. Any PHP function that accepts one argument (or one argument and the rest optional) can be chained in a batch. This also works with custom functions and Extension functions where you can do some really neat stuff. This is similar to functions and filters in other templating engines. The Syntax The syntax, like ProcessWire, is just PHP and has complementary style and a simple API. Plates only makes style recommendations. One of the key elements to making templates in any engine work is knowing where to put logic and where control structures should do the heavy lifting. With PageClasses and organized code, templates can be clean and concise with just PHP. At it's core, Plates primarily keeps focus on templates which makes it different than other engines that tend to include new syntax and tools because they already have to build a parser or interpreter. The batch() function covers a most use cases and is a welcome tool to use as is or as a complement to more custom functions and extensions. That's all you need to get started using Plates for ProcessWire. I highly recommend reviewing the short documentation to get the most out of templates in your projects. Layouts - A core templating feature for sharing page designs and base code between templates Nesting - Enhanced code reusability by inserting code blocks Inheritance - Use code sharing between templates to build more complex designs with simplicity Functions - Batching functions and writing your own Plates for ProcessWire comes with several custom build extensions for this module that may be useful. All extensions are optional and disabled by default. You can start building with the core Plates system. Extras: Plates for ProcessWire Extensions This module comes with several optional extensions that add useful tools for building templates. Many also provide some parity with other templating solutions. All custom extensions are optional and can be enabled/disabled on the module config page. Plates for ProcessWire extensions provide over 100 custom functions to use in your templates. Many of them are batchable, many of them are written to use with ProcessWire objects such as Page and WireArray/PageArray. Others are intended to make template code shorter and cleaner, others are just nice to have. The Conditionals Extension brings some nice to have utilities. <!-- From our example above --> <?php if ($page->gallery->count()): ?> <?php $this->insert('components/image_gallery', [ 'images' => $page->gallery, 'title' => __('Image Gallery'), ]) ?> <?php endif ?> <!-- Consider this instead --> <?php $this->insertIf('components/image_gallery', $page->gallery->count(), [ 'images' => $page->gallery, 'title' => __('Image Gallery'), ]) ?> Tidy up single line outputs <!-- Instead of this --> <?php if ($page->text_field): ?> <h2><?=$page->text_field?></h2> <?php endif ?> <!-- Consider this --> <?=$this->if($page->text_field, "<h2>{$page->text_field}</h2>")?> Use match instead of long if/elseif/else chains, or matchTrue for more complex conditions <h2> <?=$this->match($weather, [ 'sunny' => __('Grab your sunglasses'), 'cold' => __('Wear a coat'), 'rainy' => __('Bring an umbrella'), ])?> </h2> <h2> <?=$this->matchTrue([ __('Tickets are available') => $ticketCount > 10, __('Hurry, tickets are almost sold out') => $ticketCount > 1, __('Sold Out') => true, ])?> </h2> The Functions Extension provides a wide array of flexible and batchable functions <!-- Get the sum of all items in a WireArray/PageArray, associative array, or object by field/key/property, Also works on indexed arrays --> <p>Total: <?=$this->sum($page->cart_items, 'price')?></p> <!-- Group items in an associative array, array of objects, or WireArray/PageArray by property or field --> <?php foreach ($this->group($pages->get('template=players'), 'team_name')) as $teamName => $players): ?> <h2><?=$teamName?></h2> <ul> <?php foreach ($players as $player): ?> <li> <?=$player->title?><br> <?=$player->position?> </li> <?php endforeach ?> </ul> <?php endforeach ?> <!-- Get PageArrays inclusive of their parent using withChildren() Assign attributes/values if a page matches the current page using attrIfPage() withChildren() accepts a Page, selector, page ID. A second selector can be passed to filter child pages --> <nav> <ul> <?php foreach ($this->withChildren('/') as $navItem): ?> <li<?=$this->attrIfPage($navItem, 'class', 'active')?>> <a href="<?=$navItem->url?>"> <?=$navItem->title?> </a> </li> <?php endforeach ?> </ul> </nav> <!-- Generate an unordered list of breadcrumbs --> <?=$this->breadcrumbs(['startPage' => '/', 'separator' => ' | ', 'ulClass' => 'breadcrumb-nav'])?> <!-- Create an indexed array with iterator from index 1 on any iterable object --> <?php foreach ($this->batch($page->images, 'toList|from1') as $i => $image): ?> <img src="<?=$image->url?>" alt="<?=$image->description?>" data-slide-index="<?=$i?>"> <?php endforeach ?> The configurable Asset Loader extension lets you link, inline, and preload assets with automatic cache busting version parameters. Directories and namespaces are yours to choose. <?=$this->preloadAssets([ 'fonts::ProximaNova.woff2', 'fonts::ProximaNovaLight.woff2', ])?> <?=$this->preloadAsset('js::app.js')?> <?=$this->linkAsset('styles::app.css')?> <?=$this->inlineAsset('styles::critical.css')?> <?=$this->linkAsset('js::app.js')?> There are more extensions and a lot more functions, with documentation. Many functions that work with arrays also work with WireArray and WireArray derived objects making them batchable. If you're a RockPageBuilder rockstar, check out the README file for details on how to use an included utility function to make Plates and RPB work together 👍 Try It Out! If you want to give it a try, download the module from the Github repository and take it for a spin. When it gets a little more testing I may submit it to the modules directory. I'm a consistent user of plain PHP, Latte, and Blade for templating and I think Plates is a great addition to the developer toolbox. Interested to hear your thoughts.
  2. I have recently started to integrate Twig in my ProcessWire projects to have a better separation between logic and views as well as have cleaner, smaller template files as opposed to large multi-purpose PHP-templates. Though there is a Twig module, I have opted to initialize twig manually to have more control over the structure and settings of the twig environment. This is certainly not required to get started, but I find that having no "secret sauce" gives the me as a developer more agency over my code structure, and better insights into how the internals of the libraries I'm using work. Framework like Drupal or Craft have their own Twig integration, which comes with some opinionated standards on how to organize your templates. Since Twig is not native to ProcessWire, integrating Twig requires one to build a solid template structure to be able to keep adding pages and partials without repeating oneself or having templates grow to unwieldy proportions. This will be an (opinionated) guide on how to organize your own flexible, extensible template system with ProcessWire and Twig, based on the system I developed for some projects at work. Somehow this post got way too long, so I'm splitting it in two parts. I will cover the following topics: Part 1: Extendible template structures How to initialize a custom twig environment and integrate it into ProcessWire How to build an extendible base template for pages, and overwrite it for different ProcessWire templates with custom layouts and logic How to build custom section templates based on layout regions and Repeater Matrix content sections Part 2: Custom functionality and integrations How to customize and add functionality to the twig environment How to bundle your custom functionality into a reusable library Thoughts on handling translations A drop-in template & functions for responsive images as a bonus However, I will not include a general introduction to the Twig language. If you are unfamiliar with Twig, read the Twig guide for Template Designers and Twig for Developers and then come back to this tutorial. That's a lot of stuff, so let's get started ? Initializing the Twig environment First, we need to install Twig. If you set up your site as described in my tutorial on setting up Composer, you can simply install it as a dependency: composer require "twig/twig:^2.0" I'll initialize the twig environment inside a prependTemplateFile and call the main render function inside the appendTemplateFile. You can use this in your config.php: $config->prependTemplateFile = '_init.php'; $config->appendTemplateFile = '_main.php'; Twig needs two things: A FilesystemLoader to load the templates and an Environment to render them. The FilesystemLoader needs the path to the twig template folder. I'll put my templates inside site/twig: $twig_main_dir = $config->paths->site . 'twig'; $twig_loader = new \Twig\Loader\FilesystemLoader($twig_main_dir); As for the environment, there are a few options to consider: $twig_env = new \Twig\Environment( $twig_loader, [ 'cache' => $config->paths->cache . 'twig', 'debug' => $config->debug, 'auto_reload' => $config->debug, 'strict_variables' => false, 'autoescape' => true, ] ); if ($config->debug) { $twig_env->addExtension(new \Twig\Extension\DebugExtension()); } Make sure to include a cache directory, or twig can't cache the compiled templates. The development settings (debug, auto_reload) will be dependent on the ProcessWire debug mode. I turned strict_variables off, since it's easier to check for non-existing and non-empty fields with some parts of the ProcessWire API. You need to decide on an escaping strategy. You can either use the autoescape function of twig, or use textformatters to filter out HTML tags (you can't use both, as it will double escape entities, which will result in a broken frontend). I'm using twig's inbuilt autoescaping, as it's more secure and I don't have to add the HTML entities filter for every single field. This means pretty much no field should use any entity encoding textformatter. I also added the Debug Extension when $config->debug is active. This way, you can use the dump function, which makes debugging templates much easier. Now, all that's left is to add a few handy global variables that all templates will have access to, and initialize an empty array to hold additional variables defined by the individual ProcessWire templates. // add the most important fuel variables to the environment foreach (['page', 'pages', 'config', 'user', 'languages', 'sanitizer'] as $variable) { $twig_env->addGlobal($variable, wire($variable)); }; $twig_env->addGlobal('homepage', $pages->get('/')); $twig_env->addGlobal('settings', $pages->get('/site-settings/')); // each template can add custom variables to this, it // will be passed down to the page template $variables = []; This includes most common fuel variables from ProcessWire, but not all of them. You could also just iterate over all fuel variables and add them all, but I prefer to include only those I will actually need in my templates. The "settings" variable I'm including is simply one page that holds a couple of common settings specific to the site, such as the site logo and site name. Page templates By default, ProcessWire loads a PHP file in the site/templates folder with the same name of the template of the current page; so for a project template/page, that would be site/templates/project.php. In this setup, those files will only include logic and preprocessing that is required for the current page, while the Twig templates will be responsible for actually rendering the markup. We'll get back to the PHP template file later, but for the moment, it can be empty (it just needs to exist, otherwise ProcessWire won't load the page at all). For our main template, we want to have a base html skeleton that all sites inherit, as well as multiple default regions (header, navigation, content, footer, ...) that each template can overwrite as needed. I'll make heavy use of block inheritance for this, so make sure you understand how that works in twig. For example, here's a simplified version of the html skeleton I used for a recent project: {# site/twig/pages/page.twig #} <!doctype html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}{{ '%s | %s'|format(page.get('title'), homepage.title) }}{% endblock %}</title> <link rel="stylesheet" type="text/css" href="{{ config.urls.site }}css/main.css"> </head> <body class="{{ page.template }}"> {% block navigation %}{{ include("components/navigation.twig") }}{% endblock %} {% block header %}{{ include("components/header.twig") }}{% endblock %} {% block before_content %}{% endblock %} {% block content %} {# Default content #} {% endblock %} {% block after_content %}{% endblock %} {% block footer %}{{ include("components/footer.twig") }}{% endblock %} </body> </html> All layout regions are defined as twig blocks, so each page template can override them individually, without having to touch those it doesn't need. I'll fill the content block with default content soon, but for now this will do. Now, templates for our content types can just extend this base. This is the template for the homepage: {# site/twig/templates/pages/page--home.twig #} {% extends "pages/page.twig" %} {# No pipe-seperated site name on the homepage #} {% block title page.get('title') %} {# The default header isn't used on the homepage #} {% block header %}{% endblock %} {# The homepage has a custom slider instead of the normal header #} {% block before_content %} {{ include('sections/section--homepage-slider.twig', { classes: ['section--homepage-slider'] }) }} {% endblock %} Note that I don't do most of the actual html markup in the page templates, but in individual section templates (e.g. section--homepage-slider.twig) that can be reused across content types. More on this in the next section. We still need to actually render the template. The page template (which will be the entry point for twig) will be loaded in our _main.php, which we defined as the appendTemplateFile earlier, so it will always be included after the template specific PHP file. $template_file = 'pages/page--' . $page->template->name . '.twig'; $twig_template = file_exists($twig_main_dir . '/' . $template_file) ? $template_file : 'pages/page.twig'; echo $twig_env->render($twig_template, $variables); This function checks if a specific template for the current content type exists (e.g. pages/page--home.twig) and falls back to the default page template if it doesn't (e.g. pages/page.twig). This way, if you want a blank slate for a specific content type, you can just write a twig template that doesn't extend page.twig, and you will get a blank page ready to be filled with whatever you want. Note that it passes the $variables we initialized in the _init.php. This way, if you need to do any preprocessing or data crunching for this request, you can do it inside the PHP template and include the results in the $variables array. At this point, you can start building your default components (header, footer, navigation, et c.) and they will be included on every site that extends the base page template. Custom sections Now we've done a great deal of setup but haven't actually written much markup yet. But now that we have a solid foundation, we can add layout components very easily. For most of my projects, I use the brilliant Repeater Matrix module to set up dynamic content sections / blocks (this is not the focus of this tutorial, here's a detailed explanation). The module does have it's own built-in template file structure to render it's blocks, but since it won't work with my Twig setup, I'll create some custom twig templates for this. The approach will be the same as with the page template itself: create a "base" section template that includes some boilerplate HTML for recurring markup (such as a container element to wrap the section content in) and defines sections that can be overwritted by section-specific templates. First, let's create a template that will iterate over our the Repeater Matrix field (here it's called sections) and include the relevant template for each repeater matrix type: {# components/sections.twig #} {% for section in page.sections %} {% set template = 'sections/section--' ~ section.type ~ '.twig' %} {{ include( [template, 'sections/section.twig'], { section: section }, with_context = false ) }} {% endfor %} Note that the array syntax in the include function tells Twig to render the first template that exists. So for a section called downloads, it will look look for the template sections/section--downloads.twig and fallback to the generic sections/section.twig. The generic section template will only include the fields that are common to all sections. In my case, each section will have a headline (section_headline) and a select field to choose a background colour (section_background) : {# sections/section.twig #} {% set section_classes = [ 'section', section.type ? 'section--' ~ section.type, section.section_background.first.value ? 'section--' ~ section.section_background.first.value ] %} <div class="{{ section_classes|join(' ')|trim }}"> <section class="container"> {% block section_headline %} {% if section.section_headline %} <h2 class="section__headline">{{ section.section_headline }}</h2> {% endif %} {% endblock %} {% block section_content %} {{ section.type }} {% endblock %} </section> </div> This section template generates classes based on the section type and background colour (for example: section section--downloads section--green) so that I can add corresponding styling with CSS / SASS. The specific templates for each section will extend this base template and fill the block section_content with their custom markup. For example, for our downloads section (assuming it contains a multivalue files field download_files): {# sections/section--download.twig #} {% extends "sections/section.twig" %} {% block section_content %} <ul class="downloads"> {% for download in section.download_files %} <li class="downloads__row"> <a href="{{ download.file.url }}" download class="downloads__link"> {{ download.description ?: download.basename }} </a> </li> {% endfor %} </ul> {% endblock %} It took some setup, but now every section needs only care about their own unique fields and markup without having to include repetitive boilerplate markup. And it's still completely extensible: If you need, for example, a full-width block, you can just not extend the base section template and get a clean slate. Also, you can always go back to the base template and add more blocks as needed, without having to touch all the other sections. By the way, you can also extend the base section template from everywhere you want, not only from repeater matrix types. Now we only need to include the sections component inside the page template {# pages/page.twig #} {% block content %} {% if page.hasField('sections') and page.sections.count %} {{ include('components/sections.twig' }} {% endif %} {% endblock %} Conclusion This first part was mostly about a clean environment setup and template structure. By now you've probably got a good grasp on how I organize my twig templates into folders, depending on their role and importance: blocks: This contains reusable blocks that will be included many times, such as a responsive image block or a link block. components: This contains special regions such as the header and footer that will probably be only used once per page. sections: This will contain reusable, self-contained sections mostly based on the Repeater Matrix types. They may be used multiple times on one page. pages: High-level templates corresponding to ProcessWire templates. Those contain very little markup, but mostly include the appropriate components and sections. Depending on the size of the project, you could increase or decrease the granularity of this as needed, for example by grouping the sections into different subfolders. But it's a solid start for most medium-sized projects I tackle. Anyway, thanks for reading! I'll post the next part in a couple of days, where I will go over how to add more functionality into your environment in a scalable and reusable way. In the meantime, let me know how you would improve this setup!
  3. This is part two of my tutorial on integrating Twig in ProcessWire sites. As a reminder, here's the table of contents: Part 1: Extendible template structures How to initialize a custom twig environment and integrate it into ProcessWire How to build an extendible base template for pages, and overwrite it for different ProcessWire templates with custom layouts and logic How to build custom section templates based on layout regions and Repeater Matrix content sections Part 2: Custom functionality and integrations How to customize and add functionality to the twig environment How to bundle your custom functionality into a reusable library Thoughts on handling translations A drop-in template & functions for responsive images as a bonus Make sure to check out part one if you haven't already! This part will be less talk, more examples, so I hope you like reading some code ? Adding functionality This is more generic Twig stuff, so I'll keep it short, just to show why Twig is awesome and you should use it! Twig makes it super easy too add functions, filters, tags et c. and customize what the language can do in this way. I'll show a couple of quick examples I built for my projects. As a side note, I had some trouble with functions defined inside a namespace that I couldn't figure out yet. For the moment, it sufficed to define the functions I wanted to use in twig inside a separate file in the root namespace (or, as shown further below, put all of it in a Twig Extension). If you want a more extensible, systematic approach, check out the next section (going further). Link template with external target detection This is a simple template that builds an anchor-tag (<a>) and adds the necessary parameters. What's special about this is that it will automatically check the target URL and include a target="_blank" attribute if it's external. The external URL check is contained in a function: // _functions.php /** * Finds out whether a url leads to an external domain. * * @param string $url * @return bool */ function urlIsExternal(string $url): bool { $parser = new \League\Uri\Parser(); [ 'host' => $host ] = $parser->parse($url); return $host !== null && $host !== $_SERVER['HTTP_HOST']; } // _init.php require_once($config->paths->templates . '_functions.php'); // don't forget to add the function to the twig environment $twig_env->addFunction(new \Twig\TwigFunction('url_is_external', 'urlIsExternal')); This function uses the excellent League URI parser, by the way. Now that the function is available to the Twig environment, the link template is straightforward: {# # Renders a single anchor (link) tag. Link will automatically # have target="_blank" if the link leads to an external domain. # # @var string url The target (href). # @var string text The link text. Will default to display the URL. # @var array classes Optional classes for the anchor. #} {%- set link_text = text is not empty ? text : url -%} <a href="{{ url }}" {%- if classes is not empty %} class="{{ classes|join(' ') }}"{% endif %} {%- if url_is_external(url) %} target="_blank"{% endif %}> {{- link_text -}} </a> String manipulation A couple of functions I wrote to generate clean meta tags for SEO, as well as valid, readable IDs based on the headline field for my sections. /** * Truncate a string if it is longer than the specified limit. Will append the * $ellipsis string if the input is longer than the limit. Pass true as $strip_tags * to strip all markup before measuring the length. * * @param string $text The text to truncate. * @param integer $limit The maximum length. * @param string|null $ellipsis A string to append if the text is truncated. Pass an empty string to disable. * @param boolean $strip_tags Strip markup from the text? * @return string */ function str_truncate( string $text, int $limit, ?string $ellipsis = ' …', bool $strip_tags = false ): string { if ($strip_tags) { $text = strip_tags($text); } if (strlen($text) > $limit) { $ell_length = $ellipsis ? strlen($ellipsis) : 0; $append = $ellipsis ?? ''; $text = substr($text, 0, $limit - ($ell_length + 1)) . $append; } return $text; } /** * Convert all consecutive newlines into a single space character. * * @param string $text The text to convert. */ function str_nl2singlespace( string $text ): string { return preg_replace( '/[\r\n]+/', ' ', $text ); } /** * Build a valid html ID based on the passed text. * * @param string $title * @return string */ function textToId(string $title): string { return strtolower(preg_replace( [ '/[Ää]/u', '/[Öö]/u', '/[Üü]/u', '/ß/u', '/[\s._-]+/', '/[^\w\d-]/', ], [ 'ae', 'oe', 'ue', 'ss', '-', '-', ], $title )); } // again, add those functions to the twig environment $twig_env->addFilter(new \Twig\TwigFilter('truncate', 'str_truncate')); $twig_env->addFilter(new \Twig\TwigFilter('nl2ss', 'str_nl2singlespace')); $twig_env->addFilter(new \Twig\TwigFilter('text_to_id', 'textToId')); Example usage for SEO meta tags: {% if seo.description %} {% set description = seo.description|truncate(150, ' …', true)|nl2ss %} <meta name="description" content="{{ description }}"> <meta property="og:description" content="{{ description }}"> {% endif %} instanceof for Twig By default, Twig doesn't have an equivalent of PHP's instanceof keyword. The function is super simple, but vital to me: // instanceof test for twig // class must be passed as a FQCN with escaped backslashed $twig_env->addTest(new \Twig\TwigTest('instanceof', function ($var, $class) { return $var instanceof $class; })); In this case, I'm adding a TwigTest instead of a function. Read up on the different type of extensions you can add in the documentation for Extending Twig. Note that you have to use double backslashes to use this in a Twig template: {% if og_img is instanceof('\\Processwire\\Pageimages') %} Going further: custom functionality as a portable Twig extension Most of the examples above are very general, so you'll want to have them available in every project you start. It makes sense then to put them into a single library that you can simply pull into your projects with git or Composer. It's really easy to wrap functions like those demonstrated above in a custom Twig extension. In the following example, I have wired the namespace "moritzlost\" to the "src" folder (see my Composer + ProcessWire tutorial if you need help with that): // src/MoritzFuncsTwigExtension.php <?php namespace moritzlost; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; use Twig\TwigFilter; use Twig\TwigTest; class MoritzFuncsTwigExtension extends AbstractExtension { // import responsive image functions use LinkHelpers; public function getFunctions() { return [ new TwigFunction('url_is_external', [$this, 'urlIsExternal']), ]; } public function getFilters() { return [ new TwigFilter('text_to_id', [$this, 'textToId']), ]; } public function getTests() { return [ new TwigTest('instanceof', function ($variable, string $namespace) { return $variable instanceof $namespace; }), ]; } } // src/LinkHelpers.php <?php namespace moritzlost; trait LinkHelpers { // this trait contains the textToId and urlIsExternal methods // see the section above for the full code } Here I'm building my own class that extends the AbstractExtension class from Twig. This way, I can keep boilerplate code to a minimum. All I need are public methods that return an array of all functions, filters, tests et c. that I want to add with this extension. As is my custom, I've further split the larger functions into their own wrapper file. In this case, I'm using a trait to group the link-related functions (it's easier this way, since classes can only extend one other class, but use as many traits as they want to). Now all that's left is to add an instance of the extension to our Twig environment: // custom extension to add functionality $twig_env->addExtension(new MoritzFuncsTwigExtension()); Just like that we have a separate folder that can be easily put under version control and released as a micro-package that can then be installed and extended in other projects. Translations If you are building a multi-language site, you will need to handle internationalization of your code. ProcessWire can't natively handle translations in Twig files, so I wanted to briefly touch on how to handle this. For a recent project I considered three approaches: Build a module to add twig support to ProcessWire's multi-language system. Use an existing module to do that. Build a custom solution that bypasses ProcessWire's translation system. For this project, I went with the latter approach; I only needed a handful of phrases to be translated, as I tend to make labels and headlines into editable page fields or use the field labels themselves, so there are only few translatable phrases inside my ProcessWire templates. But the beauty of ProcessWire is that you can build your site whatever way you want. As an example, here's the system I came up with. I used a single Table field (part of the ProFields module) with two columns: msgid (a regular text field which functions as a key for the translations) and trans (a multi-language text field that holds the translations in each language). I added this field to my central settings page and wrote a simple function to access individual translations by their msgid: /** * Main function for the translation API. Gets a translation for the msgid in * the current language. If the msgid doesn't exist, it will create the * corresponding entry in the settings field (site settings -> translations). * In this case, the optional second parameter will be used as the default * translation for this msgid in the default language. * * @param string $msgid * @param ?string $default * @return string */ function trans_api( string $msgid, ?string $default = null ): string { // this is a reference to my settings page with the translations field $settings = \Processwire\wire('config')->settings; $translations = $settings->translations; $row = $settings->translations->findOne("msgid={$msgid}"); if ($row) { if ($row->trans) { return $row->trans; } else { return $msgid; } } else { $of = $settings->of(); $settings->of(false); $new = $translations->makeBlankItem(); $new->msgid = $msgid; if ($default) { $default_lang = \Processwire\wire('languages')->get('default'); $new->trans->setLanguageValue($default_lang, $default); } $settings->translations->add($new); $settings->save('translations'); $settings->of($of); return $default ?? $msgid; } } // _init.php // add the function with the key "trans" to the twig environment $twig_env->addFunction(new \Twig\TwigFunction('trans', 'trans_api')); // some_template.twig // example usage with a msgid and a default translation {{ trans('detail_link_label', 'Read More') }} This function checks if a translation with the passed msgid exists in the table and if so, returns the translation in the current language. If not, it automatically creates the corresponding row. This way, if you want to add a translatable phrase inside a template, you simply add the function call with a new msgid, reload the page once, and the new entry will be available in the backend. For this purpose, you can also add a second parameter, which will be automatically set as the translation in the default language. Sweet. While this works, it will certainly break (in terms of performance and user-friendliness) if you have a site that required more than a couple dozen translations. So consider all three approaches and decide what will work best for you! Bonus: responsive image template & functions I converted my responsive image function to a Twig template, I'm including the full code here as a bonus and thanks for making it all the way through! I created a gist with the extension & and template that you can drop into your projects to create responsive images quickly (minor warning: I had to adjust the code a bit to make it universal, so this exact version isn't properly tested, let me know if you get any errors and I'll try to fix it!). Here's the gist. There's a usage example as well. If you don't understand what's going on there, make sure to read my tutorial on responsive images with ProcessWire. Conclusion Including the first part, this has been the longest tutorial I have written so far. Again, most of this is opinionated and influenced by my own limited experience (especially the part about translations), so I'd like to hear how you all are using Twig in your projects, how you would improve the examples above and what other tips and tricks you have!
  4. Hi, First, this is production realestate site, big one. So please, if any chanse to fix this without touching database, it would be great I have field "pstatus" as a PageReference, In the tree under Status as children: sold unavailable paused In the template "property" I have dropdown were I choose: empty (nothing); sold; unavailable or paused. If you do not choose anything, property will have active selling status. I need help sorting property template in this order: show all empty and than show all sold. 1031 is Status, parent of the sold unavailable and paused. $status = $pages->get('1031')->children('sort=pstatus'); $selector[] = "sort=price, pstatus!=$status"; But I got there all except from status. I need to list "sold" too.
  5. I have $select = 'template=post, when<=now, sort=when, '; $results = $pages->find($select); then output the results' data. It works fine without the sort=when. With it, I get this: Error: Exception: SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: '_sort_when' (in /Applications/AMPPS/www/website/wire/core/PageFinder.php line 384) what is this I have this elsewhere $start = date('Y', $pages->get($select.' sort=when')->getUnformatted('when')); and it works fine, returns the earliest year.
  6. Hi, How can I use the __('Some Words to Translate') feature in Processwire Smarty Templates like {__('Some Words to Translate in Smarty')}?
  7. think i've got myself into a muddle here but i've got each template as a section in my homepage, when I go to each page they work fine (as they wrap the _main page content after), but on the homepage template I want to get all children and rendren the retun from JUST the template itself i.e. getting all the $content returns. Any way of doing this, not somthing I would normally do, just don't want to basically replicate each template into my homepag template as they may go out of sync. Thanks
  8. Hi, We're looking for a PW freelancer (preferably based around Gloucestershire/South West - but will consider further afield) to build the front-end for a PW site we're currently building. The site has been designed (files available) and prototyped. Currently we are building the backend (members) area which pulls in a JSON feed and stores it in PW. This information is then shown to the Members. A restricted view of the data is shown to the public. Timescales for this project are to go live on W/C 18th April. Please post here with contact details and website URLs and I will contact you. Many thanks Pete Jones Head of Digital www.jfd.co.uk
  9. Is there a somewhat comprehensive guide or tutorial somewhere on theming the PW admin? There's so much untapped potential there I just don't know really where to start to learn my way around it. If not, would anyone care to create one? I know I would greatly appreciate it, as would many more users. There are no admin themes in the modules directory currently that claim to be compatible with 2.5. That would probably change with a solid beginner's guide.
  10. For a recent (low-budget) ... would instead become: echo ul( li( 'Home' ) . li_foreach_page( $page->children ) ); I have collected the code I'm regularly re-using so far in a github repo: https://github.com/samuell/pwutils'>github.com/samuell/pwutils I'm sure many of you are doing things like this already, but I was just thinking that this kind of thing could be a good idea help each other improve upon. I was thinking that having a common such micro-library with all the common cases covered to speed things up, should be a productivity booster that we all can benefit from. What do you guys think? Maybe there is something like this already, that I just missed?
  11. Hello, I found ProcessWire today when exploring ModX little bit. It's absolutely amazing. I have few questions and I must state that I don't have php skills, but PW seems simple enough to implement without these skills. 1. What is the best way to "split" code for better templating? I mean writing head section only once, footer only once etc. I realize that it may be done in many "good" ways, but how would you prefer it being done? Can they be seperate templates so that I could create reusable building blocks? 2. Can I have "global variables" that I would use in templates but don't want to create separate template or page for? 3. How do you differentiate pages when you have 2 menus on website (how to create page added to second menu only)? How 2 menus play with the tree? I will ask more noob questions soon
×
×
  • Create New...