How to create a ZF2 nested bootstrap dropdown menu

December 15th, 2016

To create a nested menu in ZF2 you will need to tell the ZF2 navigation service to use a partial containing code that creates new markup.

Here is a step by step example of how to output a nested menu in bootstraps markup in ZF2.

First you need to tell the navigation service to use a partial to render the menu, find where the navigation is called, typically this is the layout file found in

/module/Application/view/layout/layout.phtml

And looks something like:

echo $this->navigation('navigation')->menu();

To tell ZF2 navigation to use the partial myfile.phtml you simple append to the request setPartial and pass an array of arguments, the first argument contains the name of the partial file and the second the name of the module for example the first argument is ‘myfile.phtml’ and the second ‘default’ which is module Application.

echo $this->navigation('navigation')->menu()->setPartial(array('myfile.phtml','default'));

Note you would normally organise partials into their own folder to do this just prepend the folder name to the partial name for example ‘->setPartial(array(‘partial/myfile.phtml’,’default’)’

The ul class needs to be declared as ‘nav nav-bar’ which is used by bootstrap. Simply append this to the end as ->setUlClass(‘nav navbar-nav’) so your complete code should now look like

echo $this->navigation('navigation')->menu()->setPartial(array('myfile.phtml','default'))->setUlClass('nav navbar-nav');

Now create the partial file for the navigation service to use:

/module/Application/view/myfile.phtml

The partial will be used to render the navigation and this is where new logic to display the mark up should be contained.

The following example code will create a nested menu, with the correct mark up and classes that bootstrap recognises, add this to the partial created above.

<ul class="<?php echo $this->navigation()->menu()->getUlClass(); ?>">
 
    <?php call_user_func($selfRef = function($pages, $navigation, $level = 0, $id='lvl-%d-pos-%d') use (&$selfRef) {
 
        foreach ($pages as $pos=>$page){
 
            $page->setClass('nav-header');
            $liClasses          = array();
            $page->id           = sprintf($id, $level, $pos);
 
            /* manually check for ACL conditions */
            if(!$page->isVisible() || !$navigation->accept($page)) {
                continue;
            } 
 
            switch($level){
                case '0': 
                    if ($page->isActive()){
                        $liClasses[]    = 'active';
                    }
                    if($page->hasPages()){ 
                        $liClasses[]    = 'dropdown';
                        $page->setClass('dropdown-toggle');
                        $page->setLabel($page->getLabel().'&nbsp;<span class="caret"></span>');
                    } 
                break;
                default:
                    if($page->hasPages()){ 
                        $liClasses[]    = 'dropdown-submenu';
                    }
                break;
            }
 
            ?>
            <li id="<?php echo $page->id; ?>" <?php echo !empty($liClasses) ? 'class="'.implode(' ',$liClasses).'"' : ''; ?>>
                <a class="<?php echo $page->getClass(); ?>" href="<?php echo $page->getHref(); ?>" <?php if ($page->hasPages()) echo 'data-toggle="dropdown"'; ?>>
                    <?php echo $page->getLabel(); ?>
                </a>
                <?php if ($page->hasPages()) : ?>
                    <ul class="dropdown-menu" role="menu">
                        <?php $selfRef($page->getPages(), $navigation, $level+1, $id); // recursion ?>
                    </ul>
                <?php endif; ?>
            </li>
 
            <?php
        }
 
    }, 
    $this->container, 
    $this->navigation()
    );
 
?>
</ul>

The above code contains a simple function that loops through the pages checking if the current iteration has children or not.

If the iteration does not, a page link is rendered with the correct classes for that level. If the iteration does contain children additional markup is created the function then makes a recursive call to itself to render those pages when the call returns it generates closing markup.

When generating the markup the loop is aware of how deep within the menu structure it currently is. By being aware of the levels and positions CSS classes have been able to be applied at specific levels in addition to this extra markup such as carets has been injected into the top layer, indicating the presence of a drop down menu where present.

Happy coding!

VN:F [1.9.9_1125]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.9_1125]
Rating: +1 (from 1 vote)
1,130 views

Zend framework 2 DomPdf No block-level parent found. Not good. error message

July 25th, 2016

This error can be caused by many things, but the most notable is when attempting to reuse DomPdf to render more than one PDF.

This is to do with the way the service manager passes a single instance, and DOMPDF does not clean up after itself properly.

Attempting code like:

$html   = $this->getServiceLocator()->get('viewpdfrenderer')->getHtmlRenderer()->render($pdf);
$eng    = $this->getServiceLocator()->get('viewpdfrenderer')->getEngine();
 
$eng->load_html($html);
$eng->render();

Will fail, however despite the answer being very hard to find online the solution is rather simple, simply use the html renderer but request dompdf from the service manager, below I will render multiple PDF’s with different views, save them as files ready to be attached to an email.

 
        // first PDF
        $pdf = new PdfModel();
        $pdf->setTerminal(true);
        $pdf->setTemplate('template1');
        $pdf->setVariables(array(
          'foo'         => 'bar'
        ));
 
        $html                   = $this->getServiceLocator()->get('viewpdfrenderer')->getHtmlRenderer()->render($pdf);
        $dompdf                 = $this->getServiceLocator()->get('dompdf'); // get fresh instance everytime
 
        $dompdf->load_html($html);
        $dompdf->render();
        file_put_contents('file1.pdf', $dompdf->output());
 
 
 
 
        // second pdf
        $pdf = new PdfModel();
        $pdf->setTerminal(true);
        $pdf->setTemplate('template2');
        $pdf->setVariables(array(
          'foo'         => 'bar'
        ));
 
        $html                   = $this->getServiceLocator()->get('viewpdfrenderer')->getHtmlRenderer()->render($pdf);
        $dompdf                 = $this->getServiceLocator()->get('dompdf'); // get fresh instance everytime
 
        $dompdf->load_html($html);
        $dompdf->render();
        file_put_contents('file2.pdf', $dompdf->output());

Happy coding!

VN:F [1.9.9_1125]
Rating: 10.0/10 (2 votes cast)
VN:F [1.9.9_1125]
Rating: 0 (from 0 votes)
2,939 views

Zend framework 2 restful CMS

August 26th, 2014

Based on ZF2 Skeleton and the Album tutorial, this ZF2 application extends this further by migrating fully to Doctrine2 and adding comments for Albums as an association.

The source code is on GitHub and below is how to set the example up

Dynamic modules

There is provision for Dynamic Modules & layouts, seperating the various layers.

Restful service using put/get/post & delete

The default module has routing setup with a top bar navigation, along with a GUI to demonstrate an AJAX RESTful service.

A RESTful service is in place, that is contained within its own module to serve the Albums and comments objects as Create, Update, Delete and List to the default application.

CMS module

A CMS module is in place that uses identity persistance and Zend Auth to authenticate the users, the login forms and validation are all handled.

The CMS users can be created and managed, the CMS also has independent Navigation from the default route, along with CRUD services for the albums & comments services.

Form validation & Flash Messages

Twitter bootstrap has been intergrated into the CMS, so Flash Messages and form validation are all displaying within a bootstrap enviroment.

HOSTING STRUCTURE

  • Folder structure is /NAMEOFNEWPROJECT/public/
  • Public is the Document-root for the server.
  • Public should not be present prior to cloning from GIT, but setup in the host file
INITIAL SETUP
cd ../NAMEOFNEWPROJECT
git clone https://github.com/adriancallaghan/cmszend2.git NAMEOFNEWPROJECT  (will clone the project into the new project)
cd NAMEOFNEWPROJECT
mv config/autoload/examplelocal.php config/autoload/local.php
nano config/autoload/local.php (set database credientials)
php composer.phar self-update
php composer.phar install
./vendor/bin/doctrine-module orm:schema-tool:create
git remote rm origin
git init
git add *
git commit -m "first commit"
git remote add origin https://github.com/NEW-GIT-REPO-LOCATION.git
git push -u origin master
UPDATE USING GIT/COMPOSER & DOCTRINE2
git pull
php composer.phar install
./vendor/bin/doctrine-module orm:schema-tool:create./vendor/bin/doctrine-module orm:validate-schema
UPDATE DB IF NECESSARY (Caution it can cause the db to flush)
./vendor/bin/doctrine-module orm:schema-tool:update --force
Cms access

You need to add a username, active (true) boolean and (md5) password combination into the cmsuser table in order to be able to login to the cms. The password can be easily md5’d on the cmd line with something like

php -r 'echo md5("SOME PASS");'
VN:F [1.9.9_1125]
Rating: 10.0/10 (1 vote cast)
VN:F [1.9.9_1125]
Rating: +1 (from 1 vote)
9,106 views

ZF2 different layouts in modules

June 4th, 2013

To enable a different layout in a module, to your module.php for the module define a new layout like so

public function init(ModuleManager $mm)
    {
        $mm->getEventManager()->getSharedManager()->attach(__NAMESPACE__, 'dispatch', function($e) {
            $e->getTarget()->layout('admin/layout');
        });
    }

then within your module.config.php view_manager settings define the template

'view_manager' => array(
        'template_map' => array(
            'admin/layout'           => __DIR__ . '/../view/layout/cms.phtml',
        ),
        'template_path_stack' => array(
            __DIR__ . '/../view',
        ),
    ),

or you can define a direct path in your module.php without any template mapping i.e

public function init(ModuleManager $mm)
    {
        $mm->getEventManager()->getSharedManager()->attach(__NAMESPACE__, 'dispatch', function($e) {
            $e->getTarget()->layout('layout/admin');
        });
    }

This will load module/view/layout/admin.phtml when this module is run

VN:F [1.9.9_1125]
Rating: 6.1/10 (8 votes cast)
VN:F [1.9.9_1125]
Rating: +2 (from 4 votes)
16,426 views