<p><strong>Figured it out!</strong></p>

<p>I went ahead and took at look at how Craft's <code>renderTemplate</code> method works which cleared it up for me. A few things...</p>

<ul>
<li>Craft caches CSS/JS resources inside of <code>getHeadHtml()</code> and <code>getFootHtml()</code> which are automatically injected into every template rendered through <code>renderTemplate</code>. Injecting these into my template fixed the JS problem.</li>
<li>I forgot to set both the <code>Content-Type</code> and <code>charset</code> headers. Not necessary in my case but it's something that's good to add anyway. </li>
<li>To keep things consistent with the way Craft does things I outputted everything into a buffer and terminated the request.</li>
</ul>

<p>Here's the working concept of my <code>renderPluginTemplate</code> method; I borrowed lots from Craft's <code>renderTemplate</code> function within the <code>BaseController</code>:</p>

<pre><code>protected function renderPluginTemplate($template, $variables = array())
  {

    // Grab the new template's content
    // ---------------------------------------------------------------------

    $oldPath = craft()-&gt;path-&gt;getTemplatesPath();
    $newPath = craft()-&gt;path-&gt;getPluginsPath() . 'plugin/templates';

    // Set template path to the plugin directory (temporarily)
    craft()-&gt;path-&gt;setTemplatesPath($newPath);

    // Attempt to fetch the template content/extension.
    if(!$templatePath = craft()-&gt;templates-&gt;findTemplate($template)) {
      throw new Exception(Craft::t('Could not find template with path: ' . $template));
    } else {
      $templateContent   = file_get_contents($templatePath);
      $templateExtension = IOHelper::getExtension($templatePath, 'html');
    }

    // Reset the template path
    craft()-&gt;path-&gt;setTemplatesPath($oldPath);

    // Set up the headers and render the template
    // ---------------------------------------------------------------------

    if($output = craft()-&gt;templates-&gt;renderString($templateContent, $variables)) {
      // Set the MIME type for the request based on the matched template's file extension (unless the
      // Content-Type header was already set, perhaps by the template via the {% header %} tag)
      if (!HeaderHelper::isHeaderSet('Content-Type')) {
        HeaderHelper::setContentTypeByExtension($templateExtension);
      }

      // Set the charset header
      HeaderHelper::setHeader(array('charset' =&gt; 'utf-8'));

      // Are we serving HTML or XHTML?
      if(in_array(HeaderHelper::getMimeType(), array('text/html', 'application/xhtml+xml'))) {
        // Are there any header/footer nodes in the queue?
        $headHtml = craft()-&gt;templates-&gt;getHeadHtml();
        $footHtml = craft()-&gt;templates-&gt;getFootHtml();

        if($headHtml) {
          if(($endHeadPos = mb_stripos($output, '&lt;/head&gt;')) !== false) {
            $output = mb_substr($output, 0, $endHeadPos).$headHtml.mb_substr($output, $endHeadPos);
          } else {
            $output .= $headHtml;
          }
        }
        if($footHtml) {
          if(($endBodyPos = mb_stripos($output, '&lt;/body&gt;')) !== false) {
            $output = mb_substr($output, 0, $endBodyPos).$footHtml.mb_substr($output, $endBodyPos);
          } else {
            $output .= $footHtml;
          }
        }
      }

      // Output it into a buffer, in case TasksService wants to close the connection prematurely
      ob_start();
      echo $output;

      // End the request
      craft()-&gt;end();
    }
  }
</code></pre>

CC BY-SA 4.0
2021-06-09T20:47:02.037Z
Thanks for that, Jon. For anyone interested in a Craft 3 Solution:

Set Headers: `Craft::$app->response->headers->set('charset', 'utf-8');`
To render: `$markup = Craft::$app->view->renderString($template, $variables);`
Head HTML: `$headHtml = Craft::$app->view->getHeadHtml();`
Body HTML: `$bodyHtml = Craft::$app->view->getBodyHtml();`
Max Strebel
CC BY-SA 3.0
2015-02-12T01:20:44.450Z
2015-02-12T01:20:44.450Z
Jon
<p>Right now I'm trying to accomplish routing users through a controller action before rendering the plugin's CP template...</p>

<p><strong>[Plugin]Plugin.php</strong></p>

<pre><code>// ...
public function registerCpRoutes()
  {
    return [
      'plugin/controller' =&gt; ['action' =&gt; 'plugin/controller/action']
    ];
  }
// ...
</code></pre>

<p>The idea here is that I want to perform validation checks in the controller before I serve up the view.</p>

<p>I'm running into two issues when it comes to rendering the template from the controller:</p>

<ol>
<li>The template path is set to the core template directory (which makes plugin templates inaccessible)</li>
<li>If I reset the template path to, say, the plugin's directory then I can no-longer extend core layouts (like <code>_layouts/cp</code>) within my template.</li>
</ol>

<p>My solution was to then just fetch the plugin's template as a string and then render it using the <code>renderString</code> method so that way I can use the template without modifying any path variables...</p>

<p><strong>[Plugin]Controller.php</strong></p>

<pre><code>// ...
protected function renderPluginTemplate($template, $variables = array())
  {
    $oldPath = craft()-&gt;path-&gt;getTemplatesPath();
    $newPath = craft()-&gt;path-&gt;getPluginsPath() . 'plugin/templates';

    craft()-&gt;path-&gt;setTemplatesPath($newPath);

    if(!$templatePath = craft()-&gt;templates-&gt;findTemplate($template)) {
      throw new Exception(Craft::t('Could not find template with path: ' . $template));
    } else {
      $templateContent = file_get_contents($templatePath);
    }

    craft()-&gt;path-&gt;setTemplatesPath($oldPath);

    echo craft()-&gt;templates-&gt;renderString($templateContent, $variables);
  }
// ...
</code></pre>

<p>This works <strong>for the most part</strong>. Now the layout isn't pulling in the right Javascript dependencies – no JS gets fired – so the panel content is hidden and the dropdown menus don't work.</p>

<p>There must be a better way to do this. I'd be super grateful if anyone could provide any insight.</p>

<p>Jon</p>

<plugin-development><controller><routing>
Rendering templates from plugin controller
# Rendering templates from plugin controller

(Asked by: username_0 on 2015-02-12)

Right now I'm trying to accomplish routing users through a controller action before rendering the plugin's CP template...
[Plugin]Plugin.php
// ...
public function registerCpRoutes()
  {
    return [
      'plugin/controller' => ['action' => 'plugin/controller/action']
    ];
  }
// ...

The idea here is that I want to perform validation checks in the controller before I serve up the view.
I'm running into two issues when it comes to rendering the template from the controller:

The template path is set to the core template directory (which makes plugin templates inaccessible)
If I reset the template path to, say, the plugin's directory then I can no-longer extend core layouts (like _layouts/cp) within my template.

My solution was to then just fetch the plugin's template as a string and then render it using the renderString method so that way I can use the template without modifying any path variables...
[Plugin]Controller.php
// ...
protected function renderPluginTemplate($template, $variables = array())
  {
    $oldPath = craft()->path->getTemplatesPath();
    $newPath = craft()->path->getPluginsPath() . 'plugin/templates';

    craft()->path->setTemplatesPath($newPath);

    if(!$templatePath = craft()->templates->findTemplate($template)) {
      throw new Exception(Craft::t('Could not find template with path: ' . $template));
    } else {
      $templateContent = file_get_contents($templatePath);
    }

    craft()->path->setTemplatesPath($oldPath);

    echo craft()->templates->renderString($templateContent, $variables);
  }
// ...

This works for the most part. Now the layout isn't pulling in the right Javascript dependencies – no JS gets fired – so the panel content is hidden and the dropdown menus don't work.
There must be a better way to do this. I'd be super grateful if anyone could provide any insight.
username_0

**Tags:** plugin-development, controller, routing


---

## 1 answers

### Answer by username_0 on 2015-02-12. Score: 7 | Accepted answer

Figured it out!
I went ahead and took at look at how Craft's renderTemplate method works which cleared it up for me. A few things...

Craft caches CSS/JS resources inside of getHeadHtml() and getFootHtml() which are automatically injected into every template rendered through renderTemplate. Injecting these into my template fixed the JS problem.
I forgot to set both the Content-Type and charset headers. Not necessary in my case but it's something that's good to add anyway. 
To keep things consistent with the way Craft does things I outputted everything into a buffer and terminated the request.

Here's the working concept of my renderPluginTemplate method; I borrowed lots from Craft's renderTemplate function within the BaseController:
protected function renderPluginTemplate($template, $variables = array())
  {

    // Grab the new template's content
    // ---------------------------------------------------------------------

    $oldPath = craft()->path->getTemplatesPath();
    $newPath = craft()->path->getPluginsPath() . 'plugin/templates';

    // Set template path to the plugin directory (temporarily)
    craft()->path->setTemplatesPath($newPath);

    // Attempt to fetch the template content/extension.
    if(!$templatePath = craft()->templates->findTemplate($template)) {
      throw new Exception(Craft::t('Could not find template with path: ' . $template));
    } else {
      $templateContent   = file_get_contents($templatePath);
      $templateExtension = IOHelper::getExtension($templatePath, 'html');
    }

    // Reset the template path
    craft()->path->setTemplatesPath($oldPath);

    // Set up the headers and render the template
    // ---------------------------------------------------------------------

    if($output = craft()->templates->renderString($templateContent, $variables)) {
      // Set the MIME type for the request based on the matched template's file extension (unless the
      // Content-Type header was already set, perhaps by the template via the {% header %} tag)
      if (!HeaderHelper::isHeaderSet('Content-Type')) {
        HeaderHelper::setContentTypeByExtension($templateExtension);
      }

      // Set the charset header
      HeaderHelper::setHeader(array('charset' => 'utf-8'));

      // Are we serving HTML or XHTML?
      if(in_array(HeaderHelper::getMimeType(), array('text/html', 'application/xhtml+xml'))) {
        // Are there any header/footer nodes in the queue?
        $headHtml = craft()->templates->getHeadHtml();
        $footHtml = craft()->templates->getFootHtml();

        if($headHtml) {
          if(($endHeadPos = mb_stripos($output, '')) !== false) {
            $output = mb_substr($output, 0, $endHeadPos).$headHtml.mb_substr($output, $endHeadPos);
          } else {
            $output .= $headHtml;
          }
        }
        if($footHtml) {
          if(($endBodyPos = mb_stripos($output, '')) !== false) {
            $output = mb_substr($output, 0, $endBodyPos).$footHtml.mb_substr($output, $endBodyPos);
          } else {
            $output .= $footHtml;
          }
        }
      }

      // Output it into a buffer, in case TasksService wants to close the connection prematurely
      ob_start();
      echo $output;

      // End the request
      craft()->end();
    }
  }

> **Comments:**
> **username_1**: Thanks for that, username_0. For anyone interested in a Craft 3 Solution:  Set Headers: `Craft::$app->response->headers->set('charset', 'utf-8');` To render: `$markup = Craft::$app->view->renderString($template, $variables);` Head HTML: `$headHtml = Craft::$app->view->getHeadHtml();` Body HTML: `$bodyHtml = Craft::$app->view->getBodyHtml();`  

---

