ZF2 add params to setPartial submenu - php

I use this partial to generate my submenu.
<?php foreach ($this->container as $page): ?>
<?php foreach ($page->getPages() as $child): ?>
<a href="<?php echo $child->getHref(); ?>" class="list-group-item">
<?php echo $this->translate($child->getLabel()); ?>
</a>
<?php endforeach; ?>
<?php endforeach; ?>
Which is called like this:
$this->navigation('navigation')->menu()->setPartial('partial/submenu')->render();
But when i render the menu the "$child->getHref()" renders the url without the needed "slug/id" parameter.
I tried to create the url with "$this->url()" in ZF1 you could pass the params in an array to the partial but in ZF2 that doesn't seem to work anymore.
Can anybody tell me how to add the params to the menu urls?
Thanks in advance!
PS!
I'm not referring to $this->Partial, i'm talking about $this->navigation('navigation')->menu()->setPartial('partial/submenu')->render() which apparently doesn't support a param array.

If I'm understanding your question, yes, you can pass params to partials. Example:
<?php echo $this->partial('partial.phtml', array(
'from' => 'Team Framework',
'subject' => 'view partials')); ?>
See http://framework.zend.com/manual/2.3/en/modules/zend.view.helpers.partial.html
I'm not sure this completely solves your issue, since you are not showing what the menu helper is. Is it your own view helper? Are you saying that setPartial method only accepts one argument?
All that said, have you considered Spiffy Navigation?
https://github.com/spiffyjr/spiffy-navigation

It's been sometime since this question was asked, however today I came across the same problem (using version 2.4).
If you have a segment route to be included within the menu that requires some parameters there is no way to pass these through to the navigation's view partial helper.
The change I've made allows a ViewModel instance to be passed to the menu navigation helper's setPartial() method. This view model will be the context for the navigation's partial template rendering; therefore we can use it to set the variables we need for the route creation and fetch them just like within other views using $this->variableName.
The change requires you to extend the Menu helper (or which ever navigation helper requires it).
namespace Foo\Navigation;
use Zend\Navigation\AbstractContainer;
use Zend\View\Model\ViewModel;
class Menu extends \Zend\View\Helper\Navigation\Menu
{
public function renderPartial($container = null, $partial = null)
{
if (null == $container) {
$container = $this->getContainer();
}
if ($container && $partial instanceof ViewModel) {
$partial->setVariable('container', $container);
}
return parent::renderPartial($container, $partial);
}
public function setPartial($partial)
{
if ($partial instanceof ViewModel) {
$this->partial = $partial;
} else {
parent::setPartial($partial);
}
return $this;
}
}
Because this extends the default implementation of the helper updated configuration is required in module.config.php to ensure the extend class is loaded.
'navigation_helpers' => [
'invokables' => [
'Menu' => 'Foo\Navigation\Menu',
],
],
The menu helper will then accept a view model instance.
$viewModel = new \Zend\View\Model\ViewModel;
$viewModel->setTemplate('path/to/partial/template')
->setVariable('id', $foo->getId());
echo $this->navigation()
->menu()
->setPartial($viewModel)
->render();
The only change in the actual partial script will require you to create the URL's using the URL view helper.
foreach ($container as $page) {
//...
$href = $this->url($page->getRoute(), ['id' => $this->id]);
//...
}

Related

How to use ActiveForm instance across Ajax requests in Yii 2?

For example we have this ActiveForm implementation in a sample view:
<?php $form = ActiveForm::begin(); ?>
<?=$form->field($model, 'first_name')->textInput(['maxlength' => true]); ?>
<?=$form->field($model, 'last_name')->textInput(['maxlength' => true]); ?>
<div id="additional-form-fields"></div>
<a href="#" id="load-additional-form-fields">
Load more fields
</a>
<?php ActiveForm::end(); ?>
Now, I want to add more ActiveField / ActiveForm fields inside this form and place them in the #additional-form-fields element with Ajax, I'd do a simple jQuery callback:
$('#load-additional-form-fields').click(function() {
$.get('/site/additional-fields', {}, function(data) {
$('#additional-form-fields').html( data );
});
});
And the action additional-fields inside SiteController would be something as:
public function actionAdditionalFields() {
$model = new User;
return $this->renderAjax('additional-fields', [
'model' => $model,
// I could pass a 'form' => new ActiveForm, here, but it's a big NO-NO!
]);
}
And this works perfectly, only if I don't use any other ActiveField fields inside this action's view:
<?=$form->field($model, 'biography')->textInput(['maxlength' => true]); ?>
<?=$form->field($model, 'country')->textInput(['maxlength' => true]); ?>
<?=$form->field($model, 'occupation')->textInput(['maxlength' => true]); ?>
Of course, I have to pass or instatiate $form somehow in this view, but it's NOT an option to use another ActiveForm::begin() / ActiveForm::end() anywhere inside this view since it will create another <form> tag and thus when I inject the Ajax response, I'll end up with with a <form> inside a <form> ...
Now, my question is as follows: Since I want to use ActiveForm, how can I share an instance of the ActiveForm through out multiple requests?
Is it doable / possible, if so, please help me realize how?
So far I have tried to put $form inside a session, but that's definitelly not working and not an option. Different than that, I've tried when passing parameters to renderAjax:
[
'model' => $model,
'form' => new ActiveForm,
]
In this case I get the following:
Form fields are created as they should with appopriate names and id's.
jQuery is loaded again (at the bottom of the response: <script src="..."> ... you get the idea)
I don't get the generated JavaScript for validation.
Is there anyway to share an instance of $form?
Okay, I have manage to do this, so I'll post the solution here and I'll open an issue on Github - might be useful in future versions.
1. Updates in yii2\widgets\ActiveForm.php
I've added a following property to the ActiveForm class:
/**
* #var boolean whether to echo the form tag or not
*/
public $withFormTag = true;
And I've changed run() method into this (check for // <-- added):
public function run()
{
if (!empty($this->_fields)) {
throw new InvalidCallException('Each beginField() should have a matching endField() call.');
}
$content = ob_get_clean();
if($this->withFormTag) { // <-- added
echo Html::beginForm($this->action, $this->method, $this->options);
} // <-- added
echo $content;
if ($this->enableClientScript) {
$id = $this->options['id'];
$options = Json::htmlEncode($this->getClientOptions());
$attributes = Json::htmlEncode($this->attributes);
$view = $this->getView();
ActiveFormAsset::register($view);
$view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
}
if($this->withFormTag) { // <-- added
echo Html::endForm();
} // <-- added
}
Thus if we instantiate a form like this:
$form = ActiveForm::begin([
'withFormTag' => false,
]);
It will not echo a <form> tag, but will render all ActiveField items and it will create their respective JavaScript/jQuery validators if $this->enableClientScript = true;.
2. Updates in my local view/file
After applying the previous fix in the base class, I needed to do the following in my view:
<?php $form = ActiveForm::begin([
'withFormTag' => false,
'id' => 'w0',
]); ?>
I had to pass the id parameter since every next instance of the ActiveForm class is incremented by 1, and I want my JavaScript/jQuery validators to be applied to the parent form, which by default starts from 0 -> w0.
And this is what did the trick!
Here's the Github issue as well: https://github.com/yiisoft/yii2/issues/12973

How did $data extracted from dataprovider when using clistview widget?

In yii framework demos, there is an blog demo. In this blog demo a Post controller has two different actions: index and view.
/**
* Lists all models.
*/
public function actionIndex()
{
$criteria=new CDbCriteria(array(
'condition'=>'status='.Post::STATUS_PUBLISHED,
'order'=>'update_time DESC',
'with'=>'commentCount',
));
if(isset($_GET['tag']))
$criteria->addSearchCondition('tags',$_GET['tag']);
$dataProvider=new CActiveDataProvider('Post', array(
'pagination'=>array(
'pageSize'=>Yii::app()->params['postsPerPage'],
),
'criteria'=>$criteria,
));
$this->render('index',array(
'dataProvider'=>$dataProvider,
));
}
/**
* Displays a particular model.
*/
public function actionView()
{
$post=$this->loadModel();
$comment=$this->newComment($post);
$this->render('view',array(
'model'=>$post,
'comment'=>$comment,
));
}
and index view is:
<?php $this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_view',
'template'=>"{items}\n{pager}",
)); ?>
and view view is:
<?php $this->renderPartial('_view', array(
'data'=>$model,
)); ?>
but both index and view use _view:
<div class="author">
posted by <?php echo $data->author->username . ' on ' . date('F j, Y',$data->create_time); ?>
</div>
<div class="content">
<?php
$this->beginWidget('CMarkdown', array('purifyOutput'=>true));
echo $data->content;
$this->endWidget();
?>
</div>
here is my question: I can understand the view assign the 'data' => $model, so in _view, $data is valid. In index action, the widget clistview is applied, but i cannot understand where is $data variable being set? I know the $data presents the current post(from dataprovider). I just cannot figure out how and where did yii did this?
Thanks for any help.
The above code first creates a data provider for the Post ActiveRecord class. It then uses CListView to display every data item as returned by the data provider. The display is done via the partial view named '_post'. This partial view will be rendered once for every data item. In the view, one can access the current data item via variable $data.
By using the itemView property of CListView which is used for rendering each data item. This property value will be passed as the first parameter to CController property renderpartial to render each data item.
public string renderPartial(string $view, array $data=NULL, boolean $return=false, boolean $processOutput=false)
public function renderPartial($view,$data=null,$return=false,$processOutput=false)
{
if(($viewFile=$this->getViewFile($view))!==false)
{
$output=$this->renderFile($viewFile,$data,true);
if($processOutput)
$output=$this->processOutput($output);
if($return)
return $output;
else
echo $output;
}
else
throw new CException(Yii::t('yii','{controller} cannot find the requested view "{view}".',
array('{controller}'=>get_class($this), '{view}'=>$view)));
}
Renders a view.
If $data is an associative array, it will be extracted as PHP variables and made available to the script The named view refers to a PHP script .the Script Which is resolved via getViewFile Used in the renderPartial method the script for getViewFile as shown below
public function getViewFile($viewName)
{
if(($theme=Yii::app()->getTheme())!==null && ($viewFile=$theme->getViewFile($this,$viewName))!==false)
return $viewFile;
$moduleViewPath=$basePath=Yii::app()->getViewPath();
if(($module=$this->getModule())!==null)
$moduleViewPath=$module->getViewPath();
return $this->resolveViewFile($viewName,$this->getViewPath(),$basePath,$moduleViewPath);
}
Looks for the view file according to the given view name.
renderItems is the abstract method defined in CBaseListView ClassFile
/**
* Renders the data items for the view.
* Each item is corresponding to a single data model instance.
* Child classes should override this method to provide the actual item rendering logic.
*/
abstract public function renderItems();
And This Method is Overriden by ClistView Class
CListView widget loops thru $dataProvider, and for each item it does something like that:
$this->renderPartial($itemView, array(
'data'=>$model,
));
Where $itemView is view file set in CListView config.
And that's it.
Edit: To clarify of how CListView iterates over dataprovider items: it is defined in CListView::renderItems, in short, the most important parts are:
// Get dataprovider data as array
$data=$this->dataProvider->getData();
...
// Get viewfile
$viewFile=$owner->getViewFile($this->itemView);
...
// Loop thru $data items
foreach($data as $i=>$item)
{
...
// Here data is assigned from dataprovider item
$data['data']=$item;
...
// Render view file
$owner->renderFile($viewFile,$data);
}

Codeigniter HMVC - Two modules passing the same variable name to their views and causing conflict

I have two modules in my installation. Both modules' controllers pass a variable called
$data['content']
to their views. Also, the first module's view runs the second module via
<?php echo Modules::run('module2'); ?>
and after that is supposed to display values from its $data['content'] variable. Unfortunately, that's when the first module's $data content is substituted with the second module's $data. This is pretty inconvenient for me, so I would like to know if there is a way to "protect" the $content variables and keep them only within their associated modules?
I would like to avoid renaming $data['content'] if possible. I've found a not-so-perfect solution in using
$data(__CLASS__)
but I am curious if it is possible not to change $data['content'].
class Some extends MX_Controller{
public function __construct(){parent::__construct();}
public function index(){
$this->load->view('template', array( //Primary template
'content' => 'some_index' // index view
));
}
public function _module_1(){
$this->load->view('module_1_view', array( //Module View : NO primary template
'' => '' <= no need to load view here, only data
));
}
public function _module_2(){
$this->load->view('module_2_view', array( //Module View : NO primary template
'' => '' <= no need to load view here, only data
));
}
}
-
template.php
<html>
<?php $this->load->view($content); ?>
</html>
-
some_index.php
<html>
//call modules
<?php echo Modules::run('some/_module_1'); ?>
<?php echo Modules::run('some/_module_2'); ?>
//call module from another class
<?php echo Modules::run('another_class/_module_1'); ?>
</html>

Creating a more flexible view in Zend Framework

Given an html/javascript 'widget' which needs to have certain fields customized before use. For example, the css class ids need to be unique as the widget may appear more than once on the same page.
Let's say I want to keep the markup (js/html) of the widget stored as a template so that I can fill in the values that need to be customized during resuse.
I know that Zend Framework's views give you at least part of this functionality, but each view is generally associated with a particular controller. Given that this widget could be created from any controller, yes still needs to be able to access some properties stored in a controller (or model). Where should I put the widget markup and how then do I fill in the custom values?
Can I create a custom view that can be reused within the same page (appear more than once) as well as on other pages? If so, how do I set that up?
Sounds like you need a ViewHelper http://framework.zend.com/manual/en/zend.view.helpers.html. Create a custom helper that will fetch the data from a model and just simply output it. This way it won't depend on any controller, can be called in either the layout or in any view script. Example:
// views/helpers/Widget.php
class Zend_View_Helper_Widget extends Zend_View_Helper_Abstract
{
protected $_model = null;
protected $_view = null;
public function widget()
{
$data = $this->_getDataFromModel();
return $this->_view->partial('widget.phtml', array('data' => $data));
}
public function setView(Zend_View_Interface $view)
{
if($this->_view === null) {
$this->_view = $view;
}
return $this->_view;
}
protected function _getDataFromModel()
{
$this->_model = $this->_getModel();
return $this->_model->getDataForWidget();
}
protected function _getModel()
{
if($this->_model === null) {
$this->_model = new Model_Widget(); // or whatever it's called
}
return $this->_model;
}
The partial script:
// views/scripts/widget.phtml
<div class="widget-class"><?php echo $this->data; ?></div>
And when you need it in your views just call it like <?php echo $this->widget(); ?>
Note that I'm rendering the widget in a separate partial view script, just to avoid having html/css in the helper itself.
Hope this helps to get you started :)
Zend_View_Helper_Partial
Example:
<?php echo $this->partial('partial.phtml', array(
'css_id' => 'foobar')); ?>
To run this from any other module:
<?php echo $this->partial('partial.phtml', 'partials_module', array(
'css_id' => 'foobar')); ?>
In your partial view script (partial.html) you would then have access to $this->css_id.

Best Practices: What's the Best Way for Constructing Headers and Footers?

What's the best way for constructing headers, and footers? Should you call it all from the controller, or include it from the view file? I'm using CodeIgniter, and I'm wanting to know what's the best practice for this. Loading all the included view files from the controller, like this?
class Page extends Controller {
function index()
{
$data['page_title'] = 'Your title';
$this->load->view('header');
$this->load->view('menu');
$this->load->view('content', $data);
$this->load->view('footer');
}
}
or calling the single view file, and calling the header and footer views from there:
//controller file
class Page extends Controller {
function index()
{
$data['page_title'] = 'Your title';
$this->load->view('content', $data);
}
}
//view file
<?php $this->load->view('header'); ?>
<p>The data from the controller</p>
<?php $this->load->view('footer'); ?>
I've seen it done both ways, but want to choose now before I go too far down a path.
Actually, after researching this quite a bit myself, I came to the conclusion that the best practice for including headers and footers in MVC is a third option - namely extending a base controller. That will give you a little more flexibility than the text's suggestion, particularly if you're building a very modular layout (not just header and footer, also sidebar panels, non-static menus, etc.).
First, define a Base_controller class, in which you create methods that append your page elements (header, footer, etc.) to an output string:
class Base_controller extends Controller
{
var $_output = '';
function _standard_header($data=null)
{
if (empty($data))
$data = ...; // set default data for standard header here
$this->_output .= $this->load->view('header', $data, true);
}
function _admin_header($data=null)
{
if (empty($data))
$data = ...; // set default data for expanded header here
$this->_output .= $this->load->view('admin_header', $data, true);
}
function _standard_page($data)
{
$this->_standard_header();
$this->_output .=
$this->load->view('standard_content', $data, true);
echo $this->_output; // note: place the echo statement in a
// separate function for added flexibility
}
function _page_with_admin_header($data)
{
$this->_admin_header($data);
$this->_output .=
$this->load->view('standard_content', $data, true);
echo $this->_output;
}
}
Then, in your page controllers, simply extend the base class and call your functions to build the page.
class Page_controller extends Base_controller
{
function index()
{
$data = ...; // Set content data here
$this->_standard_page($data);
}
function admin()
{
$data = ...; // Set content and header data here
$this->_page_with_admin_header($data);
}
}
Using a base controller, you can achieve very clean code in your individual page controllers AND have separate views for elements on the page (allowing code reuse in both views and controllers). All you need to do is define your common page 'sections' (what you might be tempted to call 'fragments') as functions in your base controller.
And if the base controller should start to grow uncontrollably (which can happen on large sites), you can rearrange some of its less-general functions by placing them in subclasses and letting the corresponding page controllers extend those instead of the original base controller.
Enjoy!
You could also try it this way -- define a default view template, which then pulls in the content based on a variable ('content' in my example) passed by the controller.
In your controller:
$data['content'] = 'your_controller/index';
// more code...
$this->load->vars($data);
$this->load->view('layouts/default');
Then define a default layout for all pages e.g. views/layouts/default.php
// doctype, header html etc.
<div id="content">
<?= $this->load->view($content) ?>
</div>
// footer html etc.
Then your views can just contain the pure content e.g. views/your_controller/index.php might contain just the variables passed from the controller/data array
<?= $archives_table ?>
<?= $pagination ?>
// etc.
More details on the CI wiki/FAQ -- (Q. How do I embed views within views? Nested templates?...)
I think the first way you are doing it is cleaner. Simply from a point of view of knowledge that is going to be rendered. Rather than having to enter the view file to find the rest.
It's bad practice to call views inside of other views. This could be a form of controller view mixing. The view function in CI allows you to pass a third parameter that causes it to return that view's output as a string. You can use this to create a compound view.
For example:
class Page extends Controller {
function index() {
$data['page_title'] = 'Your title';
$this->load->view('default_layout', array(
'header' => $this->load->view('header' , array(), true),
'menu' => $this->load->view('menu' , array(), true),
'content' => $this->load->view('content', $data , true),
'footer' => $this->load->view('footer' , array(), true),
));
}
}
default_layout.php
<? echo $header, $menu, $content, $footer; ?>
You may want to combine your header and footer to make a template like this.
class Page extends Controller {
function index() {
$data['page_title'] = 'Your title';
$this->load->view('default_template', array(
'menu' => $this->load->view('menu' , array(), true),
'content' => $this->load->view('content', $data , true),
));
}
}
default_template.php
<html><head></head><body><span>Some Header HTML</span> // this is your header html
<? echo $menu, $content; ?>
<span>some footer HTML</span></body></html> // this is your footer html

Categories