Jump to content
Sephiroth

Templating Style in Processwire: Twig Templating

Recommended Posts

Hi, So today I will writing a small tutorial on developing templates in Processwire using Twig Template, Processwire is a highly flexible CMS which gives developers/designers/users options and allows easy extension of the platform. So here goes the tutorial 

What is Twig Template ?

Simply put in my own words, Twig is a modern templating engine that compiles down to PHP code, unlike PHP, Twig is clean on the eyes , flexible and also quite *easy* to have dynamic layout site with ease ,without pulling your hair out. Twig is trusted by various platforms. It was created by the guys behind Symfony.

Take this code as an example

{% for user in users %}
    <h1>* {{ user }}</h1>
{% endfor %}

This will simply be the equivalent in PHP World

<?php

$userArray = ["Nigeria","Russia"];

foreach($userArray as $user):
?>
<h1><?= $user ?></h1>
<?php
endforeach;

The PHP code though looks simple enough however, you start to notice that you have to be concerned about the PHP tags by ensuring they are closed  properly , most times projects gets bigger and comes complex and harder to read/grasp, and also in PHP you can explicitly create variables in the template making it very hard to read as it grows and prone to getting messy WordPress is a major culprit when it comes to that regard.

Have you ever wanted to created separate layouts for different pages and  break your sites into different parts e.g Sidebar, Comment Section, Header Section ? the regular approach would be to create individual pages for each section and simply add them as templates for the pages and with time, you can end up having tons of templates, however Twig allows you to easily inherit templates and also override the templates where you can inject content into the block easily. Don't worry if you don't understand the concept, the following parts will explain with an example of how to easily inherit layouts and templates.

Layout

<!DOCTYPE html>
<html lang="en">

<head>
   {{include("layout/elements/header.twig")}}
</head>

<body>
 
    <div class="container-fluid" id="minimal">
        <header id="pageIntro">
            <div class="bio_panel">
                <div class="bio_section col-md-6">
                    <h1>Okeowo Aderemi</h1>
                    <h2>{{ page.body }}</h2>
                </div>
            </div>
            <div class="clearfix"></div>


        </header>
        <section id="page-body">
            <div class="container">
                <div id="intro" class="col-md-7 col-lg-7">
                    <h1>About me</h1>
                    <h2>
                        {{ page.summary }}
                    </h2>
                </div>
              {block name="content"}{/block}
             
                    <a style="font-size:1.799783em; font-style:italic;color:#d29c23" href="{{pages.get('/notes').url }}">Read more articles</a>
                </div>
                <div class="clearfix"></div>
            </div>

        </section>

    </div>
    <footer>
        <div class="header-container headroom headroom--not-top headroom--pinned" id="header-container">

   {{include("layout/elements/footer.twig")}}           

        </div>
    </footer>
</body>

</html>

This is basically a layout where we specify blocks and include other templates for the page, don't panic if you don't understand what is going on, I will simply break down the weird part as follows:

Include

This basically is similar to native PHP 'include', as it's name suggests it simply includes the templates and injects the content into the layout , nothing out of the ordinary here if you are already familiar with php's include function.

{{ output }}

This simply evaluates the expression and prints the value, this  evaluate expressions, functions that return contents , in my own short words it's basically the same as <?= output ?> except for the fact that it's cleaner to read.

{% expression %}

unlike the previous this executes statements such as for loops and other Twig statements.

{% for characters in attack_on_titans %}
<h1> {{characters}} </h1>
                {% endfor %}

This  executes a for loop and within the for loop, it creates a context to which variables in that context can be referenced and evaluated, unlike dealing with the opening and closing PHP tags, Twig simply blends in with markup and makes it really quick to read. 

I will simply post the contents of both the header and footer so you can see the content of what is included in the layout

header.php

<meta charset="utf-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<title>
    {{ page.title }}
</title>
<link href=" {{config.urls.templates }}assets/css/bootstrap.min.css" rel="stylesheet"/>
<link href="{{config.urls.templates }}assets/css/main.min.css" rel="stylesheet"/>
<link rel='stylesheet' type='text/css' href='{{config.urls.FieldtypeComments}}comments.css' />
<link rel="stylesheet" href="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/styles/vs.css">
<script type="text/javascript" src="{{config.urls.siteModules}}InputfieldCKEditor/plugins/codesnippet/lib/highlight/highlight.pack.js"></script>

<script src="{{config.urls.templates }}assets/js/vendors/jquery-1.11.3.min.js">
</script>
<script src="{{config.urls.templates }}assets/js/vendors/bootstrap.min.js">
</script>
<script src="{{config.urls.FieldtypeComments}}comments.js"></script>
<link rel="stylesheet" type='text/css' href="{{config.urls.templates}}js/jquery.fancybox.min.css">
 <script src="{{config.urls.templates}}js/jquery.fancybox.min.js"></script>
 {block name="javascriptcodes"}{/block}

footer.php

 <nav class="site-nav pull-right">
                <div class="trigger">
                    <a class="page-link" href="{{pages.get('/about').url}}">
                        <span>{</span> About
                        <span>}</span>
                    </a>
                    <a class="page-link" href="{{pages.get('/notes').url}}">
                        <span>{</span> Journals
                        <span>}</span>
                    </a>
               
                    <a class="page-link" target="_blank" href="https://ng.linkedin.com/in/okeowo-aderemi-82b75730">
                        <span>{</span> Linkedin
                        <span>}</span>
                    </a>
                    <a class="twitter page-link" target="_blank" href="https://twitter.com/qtguru">
                        <span>{</span> Twitter
                        <span>}</span>

                    </a>
                </div>
            </nav>

There's nothing special here, other than twig simply injecting these fragments into the main layout , the next part is the most interesting and important concept and benefit that Twig has to offer

{% block content %}{% endblock %}

This tag simply creates a placeholder in which the content would be provided by the template inheriting this layout, in lay terms it simply means child templates will provide content for that block, the 'content' simply uses the name 'content' to refer to that specific block, so assuming we were to inherit this template it would simply look like this.

Inheriting Template Layout

{% extends 'layout/blog.twig' %}
{% block content %}
<div class="container blog-container">
    <section class="blog">
        <header class="blog-header">
            <h1>
                {{page.title}}
            </h1>
            <h5 class="blog_date">
                {{page.published|date("F d, Y")}} 
            </h5>
            <br>
            </br>
        </header>
        <div class="blog_content">
            <hr class="small" />
             {{page.body}}
             <hr class="small" />

        </div>
    </section>
</div>
{% endblock %}

{% block nav %}
 <div class="col-md-4 col-xs-4 col-sm-4 prev-nav">
                        <a href="{{page.prev.url}}">
                            ← Prev
                        </a>
                    </div>
                    <div class="col-md-4 col-xs-4 col-sm-4 home-nav">
                        <a href="{{homepage.url}}">
                            Home
                        </a>
                    </div>
                    <div class="col-md-4 col-xs-4 col-sm-4 next-nav">
                        <a href="{{page.next.url}}">
                            Next →
                        </a>
                    </div>
{% endblock %}

In this snippet you can easily notice how each blocks previously created in the header and layout are simply referenced by their names, by now you will notice that twig doesn't care how you arrange the order of each block, all Twig does is to get the contents for each blocks in the child templates and inject them in the layout theme, this allows flexible templating and also extending other layouts with ease.

Twig in Processwire

Thanks to @Wanze we have a Twig Module for Processwire and it's currently what i use to build PW solutions to clients

https://modules.processwire.com/modules/template-engine-twig/

The Modules makes it easy to not only use Twig in PW but also specify folders to which it reads the twig templates, and also injects Processwire objects into it, which is why i can easily make reference to the Pages object, another useful feature in this module is that you can use your existing template files to serve as the data provider which will supply the data to be used for twig template.

take for example, assuming I wanted the homepage to display the top six blog posts on it, TemplateEngineTwig will simply load the home.php ( Depending on what you set as the template), it is also important that your twig file bears the same name as your template name e.g home.php will render into home.twig here is an example to further explain my point.

home.php

<?php
 
  //Get the Top 6 Blog Posts
  $found=$pages->find("limit=6,include=hidden,template=blog-post,sort=-blog_date");
  $view->set("posts",$found);

 

The $view variable is the TemplateEngine which in this case would be Twig, the set method simply creates a variables posts which holds the data of the blog posts, the method allows our template 'blog.twig' to simply reference the 'posts' variable in Twig Context. Here is the content of the 'blog.twig' template

blog.tpl

{% extends 'layout/blog.twig' %}
{% block content %}
   <div class="block_articles col-md-5 col-lg-5">
                    {% for post in posts %}
                    <div class="article_listing">
                        <span class="article_date"> {{post.published}}</span>
                        <h2 class="article_title">
                            <a href="{{post.url}}">{{post.title}}</a>
                        </h2>
                    </div>
		{% endfor %}
{% endblock %}

So home.php sets the data to be used in home.tpl once Twig processes the templates and generates the output, twig takes the output from the block and injects it in the appriopriate block in the layout, this makes Processwire templating more flexible and fun to work with. 

The major advantage this has; is that you can easily inherit layouts and provide contents for them with ease, without the need of running into confusions when handling complex layout issues,an example could be providing an administrator dashboard for users on the template side without allowing users into the Processwire back-end. You can also come up with several layouts and reusable templates.

 

Feel free to ask questions and any concerns  in this approach or any errors I might have made or overlooked.

Thanks

 

 

 

  • Like 20
  • Thanks 1

Share this post


Link to post
Share on other sites

Pls tell us, what is the advantage of twig templating over pure php and using the delayed output strategy with markup regions?

Share this post


Link to post
Share on other sites

I'm not using twig but latte but the main advantages are sophisticated template inheritence, filters, reusable blocks/partials, etc, and after all, more readable and maintainable code. Backdraws are the small overhead and some extra complexity, of course the latter disappears when you get used to it.

Markup regions is in my opinion a very simple templating implementation. This works fine if you don't need any of those extra features, and in case of template engines you can more easily find help if you are stuck.

Speaking of myself, I made only my first PW project with pure PHP about 3 years ago, then all with latte.

  • Like 3

Share this post


Link to post
Share on other sites
6 hours ago, pideluxe said:

Pls tell us, what is the advantage of twig templating over pure php and using the delayed output strategy with markup regions?

"delayed output strategy" to me is the same as concatenation, it easily gets messy, hard and difficult to follow, not exactly easy to inherit templates or layouts.

It might work but it comes at the cost of readability and how easy it is in making mistakes.

 Twig Template makes it easier to read ,the only disadvantage I can bring up is that, you cannot instantiate a class inside the template, you must pass a reference of an object to it, then again, your template must not be tied to a code, it should simply take a value it expects and evaluate from that. It makes things really easy to read. 

As for performance it compiles down to php templates, it only compiles when the source has been changed. 

 

Share this post


Link to post
Share on other sites
6 hours ago, tpr said:

I'm not using twig but latte but the main advantages are sophisticated template inheritence, filters, reusable blocks/partials, etc, and after all, more readable and maintainable code. Backdraws are the small overhead and some extra complexity, of course the latter disappears when you get used to it.

Markup regions is in my opinion a very simple templating implementation. This works fine if you don't need any of those extra features, and in case of template engines you can more easily find help if you are stuck.

Speaking of myself, I made only my first PW project with pure PHP about 3 years ago, then all with latte.

I just looked at Latte and it's very clean to read, it looks so similar to Twig, I was using Smarty before but Twig is 100% OOP and extending it is cleaner compared to Smarty, I will give Natte Latte a shot later on. 

  • Like 1

Share this post


Link to post
Share on other sites
5 hours ago, Sephiroth said:

"delayed output strategy" to me is the same as concatenation

Delayed output is not about concatenation but storing rendered html in a variable to be output later on. Eg:

<?php
$output = wireRenderFile("./partials/partial.php", array('page' => $page));
//lost of other code :)
echo $output;

Ryan prefers concatenation to wireRenderFile. I prefer wireRenderFile to concatenation, for example.

  • Like 1

Share this post


Link to post
Share on other sites

I used delayed output and markup regions to make clear, they are just other concepts of templating (engines). Delayed output-strategy is about inheritance and markup regions are the so called blocks in twig. Templating in other languages/engines than php is about using other syntax and replacing php-tags with something else, e.g. {%...%} for twig... 

And for terms of reusability: If you are using different fields, you cannot reuse your twig-files, or am i missing something here?

Share this post


Link to post
Share on other sites

Twig doesn't know what a field is, all it expects are variables which are passed to the twig templates from the processwire php files based on the Twig module. Let's take for example the side template in twig, I can have an array of posts which will render the post in a list manner. All I simply need to do is get a list of post and set it with a variable name for that template and the twig template simply renders it based on the variable passed. Besides at the end of the day Twig is basically still compiles to php so it's nothing unique just another layer. 

Share this post


Link to post
Share on other sites
On 3/6/2018 at 9:09 PM, Sephiroth said:

{block name="content"}{/block}

Hello Sephiroth,

Thank you for your tutorial, it is very precious although this code above does not work for me.
I had

{% block content %}
{% endblock %}

which had no problems but did not display the block content.

Thank you for any help.

 

Sten

Share this post


Link to post
Share on other sites
On 6/10/2018 at 8:42 AM, Sten said:

Hello Sephiroth,

Thank you for your tutorial, it is very precious although this code above does not work for me.
I had

{% block content %}
{% endblock %}

which had no problems but did not display the block content.

Thank you for any help.

 

Sten

{block name="content"}{/block}

Sorry about that, that was a smarty template, my brain went into Smarty mode the correct syntax is what you provided, in your layout where you arr inheritting from, ensure you have something like this 
 

{%  block content %} {%  endblock %}

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By jds43
      Hello,
      Does anyone have experience with migrating content from Django to Processwire? Or are there any suggestions for achieving this?
    • By Mithlesh
      Hi there, 

      How to add Google Recaptcha V 3.0 in the processwire?

      My website has this module: Markup Google reCAPTCHA but I am not sure whether that is V.3 or any other, sharing the SS below:


      Let me know how to update or integrate the same

      Thanks
    • By Varun
      Hi, How to change favicon in Seavuel theme which we have purchased from https://codecanyon.net/item/seavuel-multilingual-hotel-website-with-cms-bootstrap-theme/19316343
    • By MoritzLost
      In this tutorial I want to write about handling special cases and change requests by clients gracefully without introducing code bloat or degrading code quality and maintainability. I'll use a site's navigation menu as an example, as it's relatable and pretty much every site has one. I'll give some examples of real situations and change requests I encountered during projects, and describe multiple approaches to handling them. However, this post is also about the general mindset I find useful for ProcessWire development, which is more about how to handle special cases and still keep your code clean by making the special case a normal one.
      The problem: Special cases everywhere
      Since ProcessWire has a hierarchical page tree by default, as a developer you'll usually write a function or loop that iterates over all children of the homepage and displays a list of titles with links. If the site is a bit more complex, maybe you additionally loop over all grandchildren and display those in drop-down menus as well, or you even use a recursive function to iterate over an arbitrary amount of nested child pages. Something like this:
      function buildRecursiveMenu(Page $root): string { $markup = ['<ul class="navigation">']; foreach ($root->children() as $child) { $link = '<a class="navigation__link" href="' . $child->url() . '">' . $child->title . '</a>'; $children = $child->hasChildren() ? buildRecursiveMenu($child) : ''; $markup[] = "<li class="navigation__item">{$link}{$children}</li>"; } $markup[] = '</ul>'; return implode(PHP_EOL, $markup); } But then the requests for special cases come rolling in. For example, those are some of the requests I've gotten from clients on my projects (by the way, I'm not saying the clients were wrong or unreasonable in any of those cases - it's simply something I needed to handle in a sensible way):
      The homepage has the company's name as it's title, but the menu link in the navigation should just say "Home". The first page in a drop-down menu should be the top-level page containing the drop-down menu. This was requested because the first click on the top-level item opens the sub-navigation instead of navigating to that page (espcially on touch devices, such as iPads, where you don't have a hover state!), so some visitors might not realize it's a page itself. Some top-level pages should be displayed in a drop-down menu of another top-level page, but the position in the page tree can't change because of the template family settings. The menu needs to contain some special links to external URLs. For one especially long drop-down menu, the items should be sorted into categories with subheadings based on a taxonomy field. In general, my solutions to those requests fall into three categories, which I'll try to elaborate on, including their respective benefits and downsides:
      Checking for the special case / condition in the code and changing the output accordingly (usually with hard-coded values). Separating the navigation menu from the page tree completely and building a custom solution. Utilizing the Content Management Framework by adding fields, templates and pages that represent special states or settings. Handling it in the code
      This is the simplest solution, and often the first thing that comes to mind. For example, the first request (listing the homepage as "Home" instead of it's title in the navigation) can be solved by simply checking the template or ID of the current page inside the menu builder function, and changing the output accordingly:
      // ... $title = $child->template->name === 'home' ? 'Home' : $child->title; $link = '<a class="navigation__link" href="' . $child->url() . '">' . $title . '</a>'; // ... This is definitely the fastest solution. However, there are multiple downsides. Most notably, it's harder to maintain, as each of those special cases increases the complexity of the menu builder function, and makes it harder to change. As you add more special conditions, it becomes exponentially harder to keep changing it. This is the breeding ground for bugs. And it's much harder to read, so it takes longer for another developer to pick up where you left (or, as is often cited, for yourself in six months). Also, now we have a hard-coded value inside the template, that only someone with access to and knowledge of the template files can change. If the client want's the link to say "Homepage" instead of "Home" at some point, they won't be able to change it without the developer. Also, each special case that is hidden in the code makes it harder for the client to understand what's going on in terms of template logic - thus increasing your workload in editorial support.
      That said, there are definitely some times where I would go with this approach. Specifically:
      For smaller projects that you know won't need to scale or be maintained long-term. If you are the only developer, and/or only developers will edit the site, with no "non-technical" folk involved. For rapid prototyping ("We'll change it later") Building a custom solution
      My initial assumption was that the main navigation is generated based on the page tree inside ProcessWire. But of course this isn't set in stone. You can just as easily forgo using the page tree hierarchy at all, and instead build a custom menu system. For example, you could add a nested repeater where you can add pages or links on a general settings page, and generate the menu based on that. There are also modules for this approach, such as the Menu Builder by @kongondo. This approach is not the quickest, but gives the most power to the editors of your site. They have full control over which pages to show and where. However, with great power comes great responsibility, as now each change to the menu must be performed manually. For example, when a new page is added, it won't be visible in the menu automatically. This is very likely to create a disconnect between the page tree and the menu (which may be what you want, after all). You may get ghost pages that are not accessible from the homepage at all, or the client may forgot to unpublish pages they don't want to have any more after they've removed them from the menu.
      I would only go with this approach if there are so many special cases that there hardly is a "normal case". However, even then it might not be the best solution. The direct relationship between the page tree, the menu structure and page paths are one of the strongest features of ProcessWire in my opinion. If many pages need to be placed in special locations without much structure in regards to what templates go where, maybe you only need to loosen up the template family settings. I have built one site without any template family restrictions at all - any page of any template can go anywhere. It's definitely a different mindset, but in this case it worked well, because it allowed the client to build custom sections with different page types grouped together.
      It's a trade-off, as it is so often, between flexibility and workload. Weigh those options carefully before you choose this solution!
      Utilizing the CMF
      This is the middle ground between the two options above. Instead of building a completely custom solution, you keep with the basic idea of generating a hierarchical menu based on the page tree, but add fields and templates that allow the editor to adjust how and where individual pages are displayed, or to add custom content to the menu. of course, you will still write some additional code, but instead of having hard-coded values or conditions in the template, you expose those to the client, thereby making the special case one of the normal cases. The resulting code is often more resilient to changing requirements, as it can not one handle that specific case that the client requested, but also every future change request of the same type. The key is to add fields that enable the client to overwrite the default behaviour, while still having sensible defaults that don't require special attention from the editor in most cases. I'll give some more examples for this one, as I think it's usually the best option.
      Example 1: Menu display options
      This is probably the first thing you thought of for the very first change request I mentioned (displaying the homepage with a different title). Instead of hard-coding the title "Home" in the template, you add a field menu_title that will overwrite the normal title, if set. This is definitely cleaner than the hard-coded value, since it allows the client to overwrite the title of any page in the menu.
      I'll only say this much in terms of downsides: Maybe the menu title isn't really what the client wanted - instead, perhaps they feel limited because the title is also displayed as the headline (h1) of the page. In this case, the sensible solution would be an additional headline field that will overwrite the h1, instead of the menu_title field. Which fields are really needed is an important consideration, because you don't want to end up with too many. If each page has fields for the title, a headline, a menu title and an SEO-title, it's much more complicated than it needs to be, and you will have a hard time explaining to the client what each field is used for.
      Another example in this category would be an option to "Hide this page in the menu". This could be accomplished by hiding the page using the inbuilt "hidden" status as well, but if it's hidden it won't show up in other listings as well, so separating the menu display from the hidden status might be a good idea if your site has lots of page listings.
      Example 2: "Menu link" template
      One solution that is quite flexible in allowing for custom links to pages or external URLs is creating a menu-link template that can be placed anywhere in the page tree. This templates can have fields for the menu title, target page and/or external target URL. This way, you can link to another top-level page or an external service inside a drop-down menu, by placing a Menu Link page at the appropriate position. This is also a clean solution, because the navigation menu will still reflect the page tree, making the custom links visible and easily editable by the editors.
      A minor downside is that those templates are non-semantical in the sense that they aren't pages with content of their own. You'll need to make sure not to display them in listings or in other places, as they aren't viewable. It may also require loosening up strict family rules - for example, allowing for Menu Link pages to be placed below the news index page, which normally can only hold news pages.
      Example 3: Drop-down menu override
      This one is a more radical solution to override drop-down menus. You add a repeater field to top-level pages, similar to the one mentioned as a custom solution, where you can add multiple links to internal pages or URLs. If the repeater is empty, the drop-down menu is generated normally, based on the sub-pages in the page tree. But if the repeater contains some links, it completely overrides the drop-down menu. It's similar to the fully custom solution in that as soon as you override a sub-menu for a top-level page, you have to manually manage it in case the page structure changes. But you can make that decision for each top-level page individually, so you can leave some of them as is and only have to worry about the ones that you have overwritten.
      Again, this offers sensible defaults with good customizability. A downside is that the mixed approach may confuse the client, if some changes to the page tree are reflected in the drop-down menu directly, while others don't seem to have any effect (especially if you have multiple editors working on a site).
      Finding the right solution
      So how do you choose between the approaches? It depends on the client, the requirements, and on what special cases you expect and want to handle. Sometimes, a special request can be turned down by explaining how it would complicate editorial workflows or have a negative impact on SEO (for example, if you risk having some pages not accessible from the homepage at all). Also, make sure you understand the actual reason behind a change request, instead of just blindly implementing the suggestion by the client. Often, clients will suggest solutions without telling you what the actual problem is they're trying to solve.
      For example: In one case, I implemented the drop-down override mentioned in example three. However, what the client really wanted was to have the top-level page as the first item in the drop-down menu (see the example requests mentioned above). So they ended up overwriting every single drop-down menu, making the menu harder to maintain. In this case, it would have been better to go with a more specialized solution, such as adding a checkbox option, or even handling it in the code, since it would have been consistent throughout the menu.
      Another example was mentioned above: If the client requests an additional "Menu title" field, maybe what they really need is a "Headline" field. I recommend reading Articulating Design Decisions by Tom Greever; it includes some chapters on listening to the client, finding out the real reason behind a change request, and responding appropriately. It's written from a design perspective, but is applicable to development as well, and since UX becomes more important by the day, the lines between the disciplines are blurred anyway.
      Conclusion
      I realize now this reads more like a podcast (or worse, a rant) than an actual tutorial, but hopefully I got my point across. ProcessWire is at is greatest if you utilize it as a Content Management Framework, creating options and interfaces that allow for customizability while retaining usability for the client / editor. I usually try to hit a sweet spot where the editors have maximum control over the relevant aspects of their site, while requiring minimal work on their part by providing sensible defaults. Above, I listed some examples of requests I've gotten and different solutions I came up with to handle those with custom fields or templates. Though in some cases the requirements call for a custom solution or a quick hack in the template code as well!
      What are some of the special requests you got? How did you solve them? I'd love to get some insights and examples from you. Thanks for reading!
    • By Mithlesh
      Changing it to null
×
×
  • Create New...