I've started to learn Smarty templating engine hoping it would allow me to do what i have a hard time to do with PHP built-in templates, but i'm encountering a similar problem.
Let's assume that i want to create reusable pieces of HTML code like, for example, an accordion menu. My template would look like :
Accordion.tpl :
<div class="Accordion">
{foreach from=$entries item=entry}
<div class="AccordionEntry">
<div class="AccordionTab">
{$entry.tab}
</div>
<div class="AccordionContent">
{$entry.content}
</div>
</div>
{/foreach}
</div>
This template will retrieve the variable "entries" assigned in the controller part :
$smarty = new Smarty();
$smarty->assign('entries', [
['tab' => 'tab_00', 'content' => 'content_00'],
['tab' => 'tab_01', 'content' => 'content_01'],
['tab' => 'tab_02', 'content' => 'content_02']
]);
$smarty->display('Accordion.tpl');
This will work fine.
However, what if i want to reuse this accordion template in multiple places ?
The data could be assigned this way :
$smarty->assign('leftMenuEntries', [
['tab' => 'tab_00', 'content' => 'content_00'],
['tab' => 'tab_01', 'content' => 'content_01'],
['tab' => 'tab_02', 'content' => 'content_02']
]);
$smarty->assign('rightMenuEntries', [
['tab' => 'tab_00', 'content' => 'content_00'],
['tab' => 'tab_01', 'content' => 'content_01'],
['tab' => 'tab_02', 'content' => 'content_02']
]);
Here is my problem : the template "Accordion.tpl" will always retrieve data from the variable "entries", but here i'm using "leftMenuEntries" and "rightMenuEntries", so oviously it will fail. Since the two accordions won't necessarily have the same entries, i'm forced to assign these entries to two different variables.
What can i do to make this work together please ?
Thanks for your help :)
When you {include} one template from another, you can "pass in" variables for use in that template (a bit like function parameters). So if you have an overall template for the page layout, rendered as e.g. $smarty->display('Homepage.tpl'), you can have multiple accordions within it like so:
{* pull entries out of $entries, as nothing else specified *}
{include file=Accordion.tpl}
{* pull entries out of $leftMenuEntries, which will be named as $entries inside the included file *}
{include file=Accordion.tpl entries=$leftMenuEntries}
{* the same, but this time we "pass in" $rightMenuEntries *}
{include file=Accordion.tpl entries=$rightMenuEntries}
Not sure if I understood correctly, but if you want to use the data with the same templating style multiple times, without assigning new variable and displaying to new template, you can possibly create Accordion.tpl with only this div, and include it in every template where you want the data.
{include file='Accordion.tpl'}
In the case above it will retrieve the variable with the same name, if it's assigned to the main template, no to Accordion.tpl
However, if the problem's core is in the overwriting arrays, I found that in array_merge topic in PHP.net:
<?php
// you have two arrays:
array1 = array (['0'] =>"blahblupp",
['1'] => "bluppblah" );
array2 = array (['0'] =>"tirili",
['1'] => "tralala" );
// and want following as a result:
result = array (['0'] =>"blahblupp",
['1'] => "bluppblah",
['2'] =>"tirili",
['3'] => "tralala" );
// following function does the addition:
function array_add($array1, $array2)
{
$result = $array1;
$h = sizeof($array1);
for ($i = 0; $i < sizeof($array2); $i++)
{
$result[$h] = $array2[$i];
$h++;
}
return $result;
}
?>
If it's still not your case, I will try to find another solution
Related
I have a modules array for my software, and I need to know how I would add to the array through PHP without the user having to directly add it (i.e. automated). I can do this with a simple array that looks like this:
$array = array( 'key'=>'value' );
however, my array looks like this:
$modules = array('Forums'=>array('file'=>)...
So, how could I add values to the array with a PHP function where say, a user clicks a button to add a new module, and all it asks for is the name of the module and the filename?
foreach($modules as $name => $module) if ($module['enabled']) {
require_once('include/scripts/'.$module['file']);
}
If the above were used to load the module, would #Darren's comment still apply?
What I commented is how to do it. I assume you're storing this array in a cache/session where it's semi-persistent right? What you want to do is append the item to the array. Say your array looks like this:
$modules = array(
'Forums' => array('file' => 'link/to/file.php', 'enabled' => TRUE),
.....etc
);
All you need to do is add it to the array:
$modules['Example_Module'] = array('file' => 'link/to/this/module', 'enabled' => TRUE);
Which will allow you to continue using that include code block you have.
See this: Example
It sticks to the structure you require.
If there any way to discover the variables required from a Twig template? Example, if I had:
Hello {{ user }}! You're {{ age }} years old, well done big man!
I'd be able to load this template and then gather each of the required variables, eventually allowing me to have something like:
Array ( [0] => user [1] => age )
The end goal of this is to be able to define a view and then have the system create a form based on the required variables in a template file.
Working Solution
Thanks to morg for pointing me towards tokenize I was able to get what I wanted using the following (I placed it in my controller for testing):
$lexer = new \Twig_Lexer(new \Twig_Environment());
$stream = $lexer->tokenize(new \Twig_Source('{{test|raw}}{{test2|raw|asd}}{{another}}{{help_me}}', null));
$variables = array();
while (!$stream->isEOF()) {
$token = $stream->next();
if($token->getType() === \Twig_Token::NAME_TYPE){
$variables[] = $token->getValue();
while (!$stream->isEOF() && $token->getType() !== \Twig_Token::VAR_END_TYPE) {
$token = $stream->next();
}
}
}
$variables = array_unique($variables);
This returns:
Array
(
[0] => test
[1] => test2
[2] => another
[3] => help_me
)
You'll notice I only get variables and not any of the functions (this is through design), although you could remove the nested while loop if you wish to get both variables and functions.
You can use the twig tokenizer for this.
$stream = $twig->tokenize($source, $identifier);
The tokenizer has a toString() Method, whose resulting string you can parse for
VAR_START_TYPE()
NAME_TYPE(varname)
VAR_END_TYPE()
Look at this for more detailed information.
You can try using preg_match_all('{{\s*(\w+)\s*}}', 'template {{string }} with {{ var}}', $matchesArray);. The $matchArray is structured as following:
Array(
0 => array(0 => '{{string }}', 1 => 'string'),
1 => array(0 => '{{ var}}', 1 => 'var')
)
Another way of doing this from inside PHP code is not elegant, but still more reliable than any regex will be:
$source = "My template string with {{ some }} parameters.";
$stream = $twig->tokenize(new \Twig_Source($source, "source"));
$matches = [];
preg_match_all(
"/NAME_TYPE\((.*)\)/", $stream->__toString(), $matches
);
if (count($matches) > 1) {
$params = array_unique($matches[1]);
} else {
$params = [];
}
This works by using Twig internal mechanisms to tokenize the template string and then extract parameters names with a regex.
Edit: The previous version of my answer used the parse method to create a tree of nodes, but it didn’t seem to work anymore, and matching on NAME_TYPE at the previous step seems more reliable, not sure if I missed something there…
I've got this input form: (Using the blade template engine with Laravel, but the html should be easy to understand from this and ultimately trivial)
{{ Form::text('amount[]', Input::old('amount')) }}
<?php echo Form::select('unit[]',
array(
'whole' => _('whole'),
'ml' => _('milliliter'),
'l' => _('liter'),
'dl' => _('deciliter'),
'mg' => _('milligram'),
'g' => _('gram'),
'kg' => _('kilogram'),
'tsp' => _('teaspoon'),
'tbs' => _('tablespoon'),
)) ?>
{{ Form::text('ingredient[]', Input::old('ingredient')) }}
I'm trying to format this to my database to return it in a string like this :
<li><span>1</span> liter red wine</li>
I'm considering making it a simpler form and eliminating the unit measurement forcing my users to type it in instead for flexibility, but I'll still have to cramp it all into one table for my database. The span tag is used in a jQuery to dynamically increase the number so is needed. I've been at this for quite a few days on and off but I can't crack how to do this.
Here is my formatting logic:
$amount = Input::get('amount[]');
$unit = Input::get('unit[]');
$ingredient = Input::get('ingredient[]');
for ( $i = 0, $c = count(Input::get('ingredient[]')); $i < $c; $i++ )
{
$ingredients .= '<li><span>'.$amount[$i].'</span>'.$unit[$i].' '.$ingredient[$i].'</li>';
}
and I send it using
$new = Recipe::create(array(
'title' => Input::get('title'),
'curiousity' => Input::get('curiousity'),
'ingredients' => $ingredients,
'steps' => Input::get('recipe')
));
I've tried numerous ways and I get errors like the $ingredients array not being defined or not being able to use [] in the variable. I tried defining the variable as an '$ingredients = '';' variable but that just produced an empty string. My problem must be in the logic.
Build your select list outside the form for brevity as well as (what I do anyway to keep controllers very slim) send the input to the model all at once.
$input = Input::all();
$new = Recipe::create($input);
Build the array for the ingredients elsewhere. In the model (perhaps?):
$ingredients = array(
'dbname1' => 'displayname1',
'dbname2' => 'displayname2'
);
And display it accordingly, then the form inputs should be sent over with the $input in an array that you can parse and then save to your db.
Other notes about Blade syntax. I'm not aware of a need to define the array brackets [].
{{Form::open()}}
{{Form::label('label1','Display Label 1')}}
{{Form::text('fieldname1',Input::old('fieldname1'))}}
With your ingredients array already built (your current syntax will produce a dropdown and I assume you want checkboxes)
{{Form::select('ingredientsFIELDNAME',$ingredients)}}
{{Form::close()}}
In your Input::all() array your ingredientsFIELDNAME field name will have an array if you've built it as checkbox instead of select. Hope this all makes sense.
Say I have an array in PHP that looks like so:
$values = Array(
'0' => 'value1',
'1' => 'value2',
'2' => 'value3'
)
I'd like to iterate through the array using Mustache but I'd like the associated value. This is what I'm hoping to do:
{{#values}}
{{the current value}}
{{/values}}
I hope there returned result would be:
value1
value2
value3
I've been getting around this by changing my structure to:
$values = Array(
'0' => array('value=' =>'value1'),
'0' => array('value=' =>'value2'),
'0' => array('value=' =>'value3'),
)
And call {{valule}} inside the Mustache iterator.
Should I be doing this a completely different way? I'm using a SplFixedArray in PHP and I'd like to iterate through the values using this method...
Thanks!
The implicit Iterator is the way to go for simple data. If your data is more complex then PHPs ArrayIterator does the job well.
Here is an example that I have working. Hope it is useful for somebody else.
$simple_data = array('value1','value2','value3');
$complex_data = array(array('id'=>'1','name'=>'Jane'),array('id'=>'2','name'=>'Fred') );
$template_data['simple'] = $simple_data;
$template_data['complex'] = new ArrayIterator( $complex_data );
$mustache->render('template_name', $template_data );
And in the template you could have
{{#simple}}
{{.}}<br />
{{/simple}}
{{#complex}}
<p>{{ id }} <strong>{{ name }}</strong></p>
{{/complex}}
You can use the implicit iterator feature of mustache for this:
https://github.com/bobthecow/mustache.php/tree/master/examples/implicit_iterator
{{#values}}
{{.}}
{{/values}}
Your original array probably needs numeric keys and now string though. It might work that way, but I haven't tested it.
I was working on a super old php framework, which was using smarty like syntax but double curly braces, kept me hanging for quite sometime, so the following made the loop run for me :) maybe it will help you too.
{{ #each link in bluelinks }}
<p><strong>{{ link }}</strong></p>
{{/each}}
In a Zend view I can apply a partial template to an iterable element as follows:
$this->partialLoop('template.phtml', $iterable);
However inside the template, only the elements of the $iterable are available, is there another way of passing extra data to the partial?
I use
$this->partialLoop('template.phtml', array(
'data' => $iterable,
'otherVariable' => $otherVariable
);
Warning & Edit:
To be completly honest, I made a mistake. I guess that the code I proposed won't work. I mistaken it for the partial() helper. It won't work because of this part of the helper's class:
foreach ($model as $item) {
// increment the counter variable
$this->partialCounter++;
$content .= $this->partial($name, $module, $item);
}
It will iterate over the whole array instead of the "data" key. I don't get how the answer could be accepted :D Thanks to Nikolaus Dulgeridis for pointing that out.
You can't even post any extra data through $this->view because the point of partial is that it creates "clean" view instance - so that the assigned variables won't collide with your existing variables.
Possible options
- Extend the view helper with methods to set custom variables
- Iterate the array and reformat it to
array(
array('data' => $item1, 'id' => 1, 'totalCount' => 10) ,
array('data' => $item2, 'id' => 2, 'totalCount' => 10) ,
array('data' => $item3, 'id' => 3, 'totalCount' => 10) ,
)
- Use Registry to store the values.
Zend_Registry::set('partialLoopCount', $count);
$this->partialLoop($viewScript, $data);
- Dump partialLoop and use partial() instead
I prefer this solution.
$count = count($data);
foreach ($data as $key => $value) {
echo $this->partial($viewScript, array('item' => $value, 'position' => $key, 'count' => $count));
}
Inside the partial, you can access all of your view variables with:
$this->partialLoop()->view->myVariable
where myVariable is a normal view variable ($this->view->myVariable in the controller or
$this->myVariable in the view, which it's actually the same thing).
Basically, you retrieve the PartialLoop() object, then the view which called it, and then the variable itself.
This, though, will probably impact performance (and I don't think it's really MVC friendly...)
But, hey: it works. :)
An example here:
Hardcode.nl == Joris Osterhaus
Later I found (insert in partial):
$this->getHelper('PartialLoop')->view->otherVariable;
You can access parent view variables by this way :
$this->ViewModel()->getCurrent()->getVariable('parentVariable');
Found at http://blog.dossantos.com.au/how-to-access-parent-view-variables-from-a-partial-loop-in-zf2
In controller
$this->view->otherVariable = 'otherVariable';
In "partial file" - template.phtml
$this->otherVariable
(ZendFramework-1.11.4-minimal)