pug-php / pug

Pug template engine for PHP
https://www.phug-lang.com
MIT License
391 stars 42 forks source link

Can't use array member in jade / pug inline attributes directly? #86

Closed dzpt closed 7 years ago

dzpt commented 8 years ago

I want to show array member in foreach loop. It works fine except inline code combination like this:

each i in ModuleAtMenu
   li: a.indent(href='?act=module&module=' + i.module_key)=i.module_title

But this one is ok

each i in ModuleAtMenu
   li: a.indent(href=i.module_key)=i.module_title
kylekatarnls commented 8 years ago

The javascript expressions evaluation (with multiple operations: here concatenation and property getter) is not yet possible, the project js-phpize should fix that in the future but it is not yet ready to be used in concatened attributes.

For the moment, you have to use PHP syntax:

each i in ModuleAtMenu
   li: a.indent(href='?act=module&module=' . $i['module_key'])=i.module_title

Or if i is an object:

each i in ModuleAtMenu
   li: a.indent(href='?act=module&module=' . $i->module_key)=i.module_title

An elegant solution could be to add a method getLink() to the i module and call it with the getter short-cut:

each i in ModuleAtMenu
   li: a.indent(href=i.link)=i.module_title
dzpt commented 8 years ago

Thanks for the reply, what's the different between using php syntax $i['module_key'] vs i[module_key] , i.module_key or #{i.module_key} ?

Like this example, i found out it works with '?act=module&module=#{i.module_key}'

ps: Sorry for asking but i can't find any syntax document here

kylekatarnls commented 8 years ago

Hi,

Indeed, '?act=module&module=#{i.module_key}' is a good solution. The problem is we can support both PHP syntax and JS syntax.

To use the exact same syntax as Pug-js, you can directly call the node module to compile your templates, but the point is you first have to dump you data as JSON before you pass it to the view.

Pug-php allows you to keep your PHP data untouched, and call objet methods, example:

p=$bob->sayHello()

This mean no loss of recursive structures, and method can be called only on the fly since Pug-php compile templates into PHP code and not JS code. So for every syntaxes we need to do a translation from JS to PHP. i.module_key is a JS syntaxe, and it must be translated first to PHP, your code will render:

'?act=module&module=<?php echo $i['module_key']; ?>'

This become tricky with concatenation, because with '?act=module&module=' + i.module_key, the + can be addition or concatenation in JS, but it's addition only in PHP, . is getter in JS, concatenation in PHP. So we cannot handle easily both PHP and JS syntaxes in the pug template.

This is what I try to improve with js-phpize and the expressionLanguage option (modes JS, PHP or auto).

For the last possibility in your question (the interpolation), it's an option given by Pug-js, and we implemented it to match its API. As you can do "a$b" or "a" . $b in PHP, you can do "a#{b}" or "a" . b in Pug-php. This is equivalent.

dzpt commented 8 years ago

I really appreciate your help. I understand now.

The last thing i care is about the performance, does it get faster or take less memory if i use php variable directly like $i['module_key'] instead of js / mark up string?

I'm thinking about using one consistent syntax (php or js) to optimize the code.

kylekatarnls commented 8 years ago

In production, you should use cache, and the best of all is to compile all your templates during the deployment with:

$pug->cacheDirectory('path/to/your/templates/directory');

See https://github.com/pug-php/pug#cache

If you follow this best pratice, it means users only load pure PHP compiled cached versions of the templates, and so, as they never trigger any Pug compilation, you don't care about compilation performance.

dzpt commented 8 years ago

I know using cache, but i just want to use only one kind of synta for avoiding messing. Because javascript syntax doesn't work sometimes, like examples bellow.

it works with php syntax and every function if in_array($g['group_id'],$item['module_group'])

but doesn't work like this if in_array(g.group_id,item.module_group)

or this if item.module_group.indexOf(g.group_id) >= 0

but also work for this (??!) if in_array(g[group_id],item[module_group])

As i understand, it uses only variable calling by js syntax like object ~> array access. I can't use any function of js, but on the example two, it didn't work while mixing php function in_array vs js accessor g.group_id, but why switch to write as g[group_id] like the last one, it works? It made me confused

kylekatarnls commented 8 years ago

This one is really not a good option:

if in_array(g[group_id], item[module_group])

In this case, only var names are transformed like this:

if in_array($g[group_id], $item[module_group])

But with a strict E_NOTICE report in PHP you will get errors because group_id are recognized as constants and PHP will translate into the equivalent string because the constant is not found.

For example, try this:

- define('group_id', 'module_group')
- define('module_group', 'group_id')
if in_array(g[module_group], item[group_id])

This will works! Because we create the missing constants, this is the mess.

For this one:

if item.module_group.indexOf(g.group_id) >= 0

It will never work, we can't have both the PHP functions (like in_array) and the JS prototypes (like .indexOf).

If you want pure JS syntax, use pug-cli. Pug-php is made to get "php everywhere". And if you want to disable all JS to PHP translation done for help to translate basic templates from one language to the other, you can set expressionLanguage to php:

$pug = new Pug(array(
  'expressionLanguage' => 'php',
));

With this option, expressions are keeped as it, this mean you should prepend every variable with $, get properties with -> and array entries with [].

But I know there are possible improvement in syntaxes detections and I work on it.

fdorantesm commented 8 years ago

@Tom29 When you pass params to PHP functions you can't pass pug objects.

Like:

    ul.list.no
        each post in posts
            li.post
                .equalize.not
                    .wp1-3.caption
                        -> img(src=media("post/"+post.thumb), alt="")
                    .wp2-3.body.cF
                        .wrp.r
                            .info
                                .title.b.tu= post.title
                                .user= post.name
                                .date.i= post.date
                            article.mt20.mb20.i #{post.intro}
                            .ab.b0
                                   ->     a(href=base(post.slug)).cp0.bcF.more. More

You must to pass string value, and post.slug (example) is not a string.

If you need concat a string to PHP:

Instead: `$post->slug and media("post/!{post.thumb}) or ^media("post/"+$post->thumb)if an object, like @kylekatarnls said.

Neither concat with + in PHP context.

div(style="background:url("+img('image.jpg')+");another:properties;")

Wrong:

div(style="background:url("+img(value+another_value)+");another:properties;")
div(style="background:url("+img($asset->image)+");another:properties;")

Correct using $ scope.

Other ways:

``` "&url="+item->value+"...

If you use . as scope it's like it was javascript/Java objects:

value.charAt(0)

dzpt commented 7 years ago

@kylekatarnls How's about this example :(

each item in groupList
   option(value=item.group_id, selected=(item['group_id']==se_groupid))=item.title

-> Doesn't work

each item in groupList
   option(value=item.group_id, selected=($item['group_id']==$se_groupid))=item.title

Works.

Here is groupList array:

Array
(
    [0] => Array
        (
            [group_id] => 1
            [title] => Group A
        )

    [1] => Array
        (
            [group_id] => 2
            [title] => Group B
        )

    [2] => Array
        (
            [group_id] => 3
            [title] => Group C
        )

    [3] => Array
        (
            [group_id] => 4
            [title] => Group D
        )
)

se_groupid = 4.

I thought this is really simple comparing operator and it might work. I print both item.group_id and se_groupid, it equals, but it seems in the comparing, javascript engine can't do the comparison

kylekatarnls commented 7 years ago

This is exactly the same explication, there is no javascript engine, so we do quick transformation of js-like expressions to turn it into PHP code. If you see a simple way ot make it work, feel free to open a pull request to propose your solution.

Else, you will have to wait for the js-phpize (pug sub-project to handle js expressions) improvements.

kylekatarnls commented 7 years ago

Hi, js-phpize refactorization done!

Please can you try to composer update, set expressionLanguage to js and tell me if it fix your bug?

Thanks!

dzpt commented 7 years ago

@kylekatarnls Hello there, i've done some few quick tests, and it have really weird errors.

$TPL->share('list',array(1,2,3,4));
$TPL->share('logged',array('id'=>1,'name'=>'name'));
$TPL->share('LANG',array('id'=>1,'name'=>'name'));
each i in list
    if i==logged.id
        h1 matched
    else
        h1 failed

~> it works

each i in list
    if i==logged.id
        h1 matched
    else
        h1 failed
div=LANG.name

( ! ) Fatal error: Uncaught exception 'JsPhpize\Parser\Exception' with message 'Unexpected . in on line 1 near from LANG.' in

If i change LANG to lang for avoiding case sensitive, it can run but the result is wrong, i never equals to logged.id (?!). I think js phpize doesn't parse properly.

dzpt commented 7 years ago

@kylekatarnls how was that? have you checked it?

kylekatarnls commented 7 years ago

Indeed, uppercase names are reserved for constants. And the js mode is not yet compatible in conditions such as if, I must work on it.

So, this syntax you reported first is available with the expressionLanguage js mode:

li: a.indent(href='?act=module&module=' + i.module_key)=i.module_title

This is what is now fixed.

For a stable behaviour that handle conditions, attributes dans tag contents, you should use for the moment the expressionLanguage php mode and use PHP syntax in templates:

each $i in $list
    if $i == $logged['id']
        h1 matched
    else
        h1 failed
div=$LANG['name']

Thanks,

dzpt commented 7 years ago

@kylekatarnls If i change LANG to lang div=lang.namewill not show anything. and if i switch expressionLanguage to php , each $i in $list doesn't work, it prints $i in $list but each i in list does. So i think it should be fix for the consistency in syntax

kylekatarnls commented 7 years ago

Yes, I must admit that's an inconsistency, but I will not do big evolutions on pug-php until the next major version that will be a complete refactorization.

kylekatarnls commented 7 years ago

Hi, there is a new option now available to use the native pugjs engine (until the 3.0):

$pug = new Pug(array(
    'pugjs' => true
));
echo $pug->render('
-
  var list = ["Uno", "Dos", "Tres",
          "Cuatro", "Cinco", "Seis"]
each item in list
  li= item
');

This is a pure wrapper solution with no PHP support in templates (all run with node.js).

kylekatarnls commented 7 years ago

You can update to 3.0.0-alpha2, it should be fixed. Please don't hesitate to test the new pug-php 3 and give us your feedback.