Talesoft / tale-jade

A complete and fully-functional implementation of the Jade template language for PHP
http://jade.talesoft.codes
MIT License
88 stars 10 forks source link

Support for dynamic includes #72

Open zorca opened 8 years ago

zorca commented 8 years ago

Hi, Torben! I create a frontend tool for building OnePage websites without using Gulp, PHP only. And I need dynamic includes for sections, which are located in a specific folder, named app/sections. The number and names of include files is unknown, but they will be called in accordance with the mask: (section-number)(section-name).jade. Whether such inclusion in the current version of your library? I would be grateful for a code example.

TorbenKoehn commented 8 years ago

Well, there exists a major problem with dynamic includes.

You'd need to also compile all files that could be possibly included and have them in the same structure to give you the ability to use PHP's include inside the generated PHTML to include files dynamically.

What include does right now is compiling the included file (no matter if it's jade or something else) and directly render it into the generated PHTML file (Which is the only way to generate good stand-alone PHTML files)

I will tell you how I solved it in a CMS I'm currently writing. I'm pre-rendering included modules.

As an example, I have menues that can use different templates

id     | name         | template
----------------------------------
1      | main-menu    | default
2      | footer-menu  | foot

When I want to use them inside Jade, I render them first and pass the rendered HTML to Jade


$menu = $menuDb->findOne(1);

$menuHtml = $renderer->render('menues/menu-'.$menu->template, ['menu' => $menu]);

$html = $renderer->render('one-column', ['menuHtml' => $menuHtml]);

echo $html;

This happens through some abstraction of course.

In one-column.jade you'd have

doctype html
html
    head
        title My Title
    body

        nav!= $menuHtml

This also gives you neater caching-possibilities than dynamic includes would.

Notice the ! to avoid HTML-escaping.

Another approach would be to use some kind of static helper, function or closure inside jade


echo $renderer->render('one-column', [
    'renderMenu' => function ($name, array $args = null) use ($renderer) {

        return $renderer->render('menues/menu-'.$name, $args);
    }
]);

//or

class MenuHelper
{

      public static function render($name, array $args = null)
      {

           $renderer = StaticSource::getStaticRenderer();

            return $renderer->render('menues/menu-'.$name, $args);
      }
}

You could use those two like this

nav!= $renderMenu($menuName)

//or

nav!= MenuHelper::render($menuName)
zorca commented 8 years ago

Thanks for the detailed response! I'm working on a simple CMS that uses a flat-files as a database. I like Grav CMS, but it is too complicated to use. Other options considered are also not very fit. And I am interested in your developments in this direction.

My temporary solution for dynamic includes:

mixin sections(...sectionsList)
    each $sectionsItem, $sectionsKey in $sectionsList
        section(id=$sectionsItem)
            -
                $sectionsItemFile = APP . 'sections/_' . $sectionsKey . '.jade';
                if(file_exists($sectionsItemFile)) $sectionsItemContent = file_get_contents($sectionsItemFile);
                $__value = isset($sectionsItemContent) ? $sectionsItemContent : false;
                $__jade = new \Tale\Jade\Renderer();
                echo $__jade->compile($__value, '"', true); unset($__value); unset($__jade);
TorbenKoehn commented 8 years ago

I am actually thinking about a way I could realize dynamic includes by re-using the compiler inside the jade file.

This would not be possible for jade files generated with the standAlone-option, though, since those are designed to run as single PHP files.

jaydenseric commented 8 years ago

Is there a way I can get the include path to work from a variable string?

The value of the string is not going to be "dynamic", as in every parse the result should be the same.

Ideally this:

include my-path/template.jade

Would work the same as something like this:

- $includePath = 'my-path/template.jade';

include $includePath

The use case is for these sorts of component mixins:

mixin generic-content($includePath)
  .generic-content
    include $includePath

section
  +special-heading('First Section')
  +generic-content('content/first-section.jade')
section
  +special-heading('Other section')
  +generic-content('content/other-section.jade')

In such a contrived example the value is less obvious. We store all our content in a JSON style PHP array as component mixin key/value props, and blobs of HTML are difficult to manage in a string value.

TorbenKoehn commented 8 years ago

I will implement dynamic includes as soon as I got time for it, they will behave just like you expect it.

In the mean-time, roll your own!


$args = [/*...*/];

$args['includeFile'] = function($path) use ($renderer, &$args) {

    return $renderer->render($path, $args)
};

$renderer->render('index', $args);
mixin generic-content($includePath)
    .generic-content
        != $includeFile($includePath)

section
    +special-heading('First Section')
    +generic-content('content/first-section')
section
    +special-heading('Other section')
    +generic-content('content/other-section')

This doesn't get invalid when I implement the actual dynamic includes, you can simply replace them slowly later on.

The problem is that at the point where the Compiler does the actual include (All included/extended files get parsed in the same compilation process because of things like shared blocks and variable scopes), variables aren't evaluated yet. The compiler neither knows which variables are there nor does it know which values they might have and it has no way of finding out.

You might think include #{$dynamicPath}.jade could render to something like <?php include($dynamicPath.'.phtml'); ?> (and it could, easily), but the problems I tried to explain above still exists

Regardless on where or how you set that variable, the compiler will never know what value it has. Only the runtime PHTML does. It would need a way to pre-compile all possible files that could be included in there so that the include-line there actually includes PHTML-templates that exist and are renderered correctly already. Even if the value is always the same, it doesn't change the fact that the compiler doesn't know what it is.

I will probably fall back to the method I've shown you here (Just in a hard-coded, stable and standard way) if the include contains an interpolation and it will only work outside stand_alone-mode.