Custom Widgets

Hi,

I was wondering what the correct, or more appropriate way of adding widgets to a web application is through Phalcon. For example, I have a simple widget next to the logo that allows an admin to change what company they are in through a select box. My current code is as follows:

Adding it to the library seems silly. Is it possible to create a widgets directory in the app folder, stick all of the widget classes there, and have a folder called widgets in the views directory?

use Mac\CompanyChanger\CompanyChanger;
/**
* Elements component
*/
$di->set('companyChanger', function(){
    return new CompanyChanger();
});
<?php

namespace Mac\CompanyChanger;

use Phalcon\Mvc\User\Component,
    Phalcon\Forms\Form,
    Phalcon\Forms\Element\Select,
    Phalcon\Forms\Element\Hidden,
    Phalcon\Forms\Element\Submit,
    Phalcon\Validation\Validator\Identical;

/**
* Mac\Elements\CompanyChanger
*
*   Helps build the company menu changer next to the logo
*/

class CompanyChanger extends Component
{

    protected $_compCount;

    protected $_companies;

    protected $_keyfob;

    protected $_userCompany;

    public function __construct()
    {
        //Cached APC key for companies
        $this->_keyFob = 'companies' . $this->user->id;

        //Companies count
        $this->_compCount = $this->modelsCache->get($this->_keyFob)->count();

        //Companies array
        $this->_companies = $this->modelsCache->get($this->_keyFob);

        foreach($this->_companies as $company) {
            $storeTemp[$company->companies->getCompanyId()] = $company->companies->getCompanyName();
        }

        $this->_companies = $storeTemp;

        $this->_keyFob = 'userData' . $this->user->id;

        //Grab user company
        $this->_userCompany = $this->modelsCache->get($this->_keyFob)->company->getCompanyName();
    }

    public function render()
    {
        $this->view->form = $this->getForm();

        $this->view->partial('layouts/CompanyChanger', array('compCount' => $this->_compCount, 'companies' => $this->_companies, 'userCompany' => $this->_userCompany));
    }

    protected function getForm()
    {
        $form = new Form();

        //company
        $company = new Select('company', $this->_companies);

        $form->add($company);

        //CSRF
        $csrf = new Hidden('csrf');

        $csrf->addValidator(
            new Identical(array(
                'value' => $this->security->getSessionToken(),
                'message' => 'CSRF validation failed'
            ))
        );

        $form->add($csrf);

        $form->add(new Submit('Change', array(
            'class' => 'btn btn-success'
        )));

        return $form;
    }
}
{% if compCount > 1 %}
    <form id="application_name" method="post">
        <div id="application-name-select">
            <span id="applcation-text">Working with</span>
            <div id="company-name2">
                <span id="company-name-link">{{ userCompany }}</span>
            </div>
            <div id="company-select">
                {{ form.render('company') }}
            </div>
        </div>
        {{ form.render('csrf', ['value': security.getToken()]) }}
        <div id="company-submit">
            {{ form.render('Change') }}
        </div>
    </form>
{% else %}
    <p id="application_name">Working with {{ userCompany }}</p>
{% endif %}

Sorry this is old but I found it via search as it nearly does what I'm trying (and failing) to do.

Phalcon, in your 3rd link there, you have a VOLT template where you add {{ elements.getMenu() }} which is fine - I have the same arrangement working here too.

The getMenu function though contains a mix of HTML and PHP elements. Is it possible to move the HTML into a view/partial and have that rendered by VOLT also?

I've tried a number of combinations of tricks to try to make this work but haven't managed to do so. Not running in an object context, can't call non station methods etc.

I'm sure I'm missing something but have only just started looking at Phalcon so please excuse my ignorance.

edited Mar '14

Yes you can, this is simple replacement. There could be typo because i am writing in this textfield not in IDE

than this:

        echo '<div class="nav-collapse">';
        $controllerName = $this->view->getControllerName();
        foreach ($this->_headerMenu as $position => $menu) {
            echo '<ul class="nav ', $position, '">';
            foreach ($menu as $controller => $option) {
                if ($controllerName == $controller) {
                    echo '<li class="active">';
                } else {
                    echo '<li>';
                }
                echo Phalcon\Tag::linkTo($controller.'/'.$option['action'], $option['caption']);
                echo '</li>';
            }
            echo '</ul>';
        }
        echo '</div>';

you can use

$view = new \Phalcon\Mvc\View\Simple();
$view->setViewsDir("views/partials/");
echo $view->render("menu",array(
    "controllerName" =>$this->view->getControllerName(),
    "_headerMenu" => $this->headerMenu,
    ));

then in your /app/views/partials/menu.phtml you have posibitlity to use

<div class="nav-collapse">
<?php foreach($_header_menu as $position=>$option) { ?>
    <ul class="nav<?= $position ?>">
                <?php foreach ($menu as $controller => $option) { ?>
                <?php if ($controllerName == $controller) { ?>
                    <li class="active">
                <?php } else { ?>
                    echo '<li>';
                <?php } ?>
                <?php echo Phalcon\Tag::linkTo($controller.'/'.$option['action'], $option['caption']); ?>
                </li>
            }
    </ul>
<? } ?>
</div>

This is simple replacement, but you should think about more useful directory structure to not mix partials from controllers from partials from plugins

Thanks so much for this - getting much closer now :)

The one snag left though is that it will only render a .phtml file, if I remove that and just have a volt file, it complains that it cannot find the view file.

I copied this from my bootstrap to see if registering the engines again would help (I'm using .volt files for rendering from controller/actions ok):

$view->registerEngines(array('.phtml' => 'Phalcon\Mvc\View\Engine\Php', '.volt' => 'Phalcon\Mvc\View\Engine\Volt'));

But then I got the error "A dependency injector container is required to obtain the application services".

So yes, nearly there, the glitch is simply that when rendering a partial in this way, the system does not seem to recognise .volt files even though it does when rendering from a controller/action.

edited Mar '14

Did you try?

$view = new \Phalcon\Mvc\View\Simple();
$view->setViewsDir("views/partials/");
$di=$this->getDI(); //or $di = new \Phalcon\DI\FactoryDefault();
$view->setDi($di);
$view->registerEngines(array(
    ".phtml" => "Phalcon\Mvc\View\Engine\Php",
    ".volt" => "Phalcon\Mvc\View\Engine\Volt"
));
echo $view->render("menu",array(
    "controllerName" =>$this->view->getControllerName(),
    "_headerMenu" => $this->headerMenu,
    ));

Hallelujah! Thanks doit76

It only worked with this line but it did work and I can now render volt partials from the library.

$di = new \Phalcon\DI\FactoryDefault();

Cheers.