Archivi tag: jquery

ZendX_JQuery e metodo addOnLoad con layout disabilitato

Zend Framework Logo

In un applicativo sviluppato su Zend Framework in cui è necessario un uso massiccio di ajax e jQuery spesso la libreria ZendX_JQuery non è sufficiente a coprire tutte le esigenze. Tempo fa avevo già riscontrato alcuni problemi sulle funzioni di Autocomplete di ZendX_JQeury. Oggi mi sono scontrato con il metodo OnLoad (che genera codice in $(document).ready(function(){ … }); )

Siamo nella situazione in cui da una pagina si caricano elenchi e svariate altre sottosezioni tramite le classiche chiamate jQuery. Prendiamo questa funzione Javascript come esempio:

function my_ChangeTab(url)
    {
        startLoadingBoxTopLeft('box_left');
        $.get( url ,
            function(data, textStatus)
            {
                stopLoadingBoxTopLeft('box_left');
                // load HTML data in box_left
                $('#box_left').html(data);
            }, 'html');
    }

Questa funzione carica il contenuto che restituisce url dentro un elemento (div) box_left.

Il mio problema nasce nel momento in cui url carica un form. Se in questo form abbiamo elementi di ZendX_JQuery (es: DatePicker, Autocomplete, ecc.) siamo fregati. Questi elementi infatti invocano internamente il metodo addOnLoad.

Vedi ad esempio ZendX_JQuery_View_Helper_DatePicker nel metodo datePicker (riga 71):

$this->jquery->addOnLoad($js);

Questo metodo carica, se correttamente configurato, nel head tag della pagina il codice $js. Questo codice serve per innescare le funzioni delle librerie jQuery UI per il DatePicker.

Tornando alla nostra situazione (vedi sopra) è comprensibile come il tutto non funzioni. La chiamata Javascript qui sopra infatti richiama solo il contenuto generato dall’action e non l’intero layout: nell’action infatti il layout sarà disabilitato:

$this->_helper->layout()->disableLayout();

Come risolvere?

Io mi sono arrangiato con un bel plugin:

<?php 
class My_Controller_Plugin_JQueryOnLoadWithoutLayout
    extends Zend_Controller_Plugin_Abstract
{
	protected $view;
 
	function __construct(){
		$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
        if (null === $viewRenderer->view) {
            $viewRenderer->initView();
        }
        $this->view = $viewRenderer->view;
	}
 
    public function  postDispatch(Zend_Controller_Request_Abstract $request)
    {
        // get Layout
        $layout = Zend_Layout::getMvcInstance();
 
        // elenco delle action registrate in OnLoad
        $actions = $this->view->jQuery()->getOnLoadActions();
        if( count($actions) > 0
            && !$layout->isEnabled()
          )
        {
            // create jQuery code
            $content = "";
            if(ZendX_JQuery_View_Helper_JQuery::getNoConflictMode() == true) {
                $content .= '$j(document).ready(function() {'."\n    ";
            } else {
                $content .= '$(document).ready(function() {'."\n    ";
            }
            foreach($actions AS $action)
            {
                $content .= $action . PHP_EOL;
            }
            $content .= '});'."\n";
 
            // add code to script tag
            $code = '<script type="text/javascript">' . PHP_EOL
                  . '//<![CDATA[' . PHP_EOL
                  . $content
                  . '//]]>' . PHP_EOL
                  . '</script>' . PHP_EOL;
 
            // OUTPUT
            $this->getResponse()->appendBody($code);
        }
    }
 
}
?>

Questo plugin modifica l’output a fine dispatch (postDispatch). Prima di tutto verifica se esistono righe di codice da inserire nella sezione OnLoad, poi verifica che il layout sia disabilitato (altrimenti quando è abilitato scriviamo 2 volte lo stesso codice) e infine genera l’apposito codice Javascript che viene aggiunto al Response.

In questo modo risolviamo il problema posto sopra. Ogni volta che carichiamo una “porzione” di pagina tramite Ajax (quindi il layout è disabilitato) e necessitiamo di codice in OnLoad (generato automaticamente, ad esempio, dagli elementi form di ZendX_JQuery) il suddetto plugin aggiungerà il codice generato per l’OnLoad in coda all’HTML restituito via Ajax.

Zend Form, jQuery and autocomplete

When a component in a Framework is inflexible and difficult to maintain I believe is time to change way. ZendX_JQuery is powerful but not in the jQuery UI Autocomplete. After a thorough discussion about Autocomplete now we are at a dead end and we can only wait a new release. I prefer to change my way and go ahead.

This is my different solution for Autocomplete UI in Zend Form (without ZendX_JQuery).

First of all I extended the Zend_Form with a general class My_Form where I can set all defaults values of decorators, filters, validators, etc. Then I added my createMyAutoComplete method:

class My_Form extends Zend_Form
{
    // autcomplete extension of idfield
    private $_acExtension = "_ac";
 
    /*
     *  Create My AutoComplete Elements
     */
        public function createMyAutoComplete($id, $source, $params=array())
        {
            $idAc = $id . $this->_acExtension; // support element id for autocomplete
            // Create autocomplete element
            $this->addElement('text', $idAc, $params);
            // Create autocomplete hidden element (for id field)
            $this->addElement('hidden', $id, array('decorators' => $this->_noElementDecorator));
 
            // Add my custom Javascript functions to onLoad
            $view = $this->getView();
            $view->jQuery()->addOnLoad("MyAutocomplete.initialize('".$id."', '".$source."', '".$this->_acExtension."');");
 
            // return id of element Autocomplete to manage in group, add params, ecc...
            return $idAc;
        }

Above you can see the createMyAutoComplete method to generate an autocomplete field and its support field id. The parameters are:

  • $id = the id of the element
  • $source = the source where to read data for autocomplete
  • $params = like the params of the Zend_Form (see below)

As we seen the method creates two elements, for example, if we set $id=”idcity” we will have:

  • idcity, hidden field (our real id)
  • idcity_ac, text field (support field, only for autocomplete feature)

Remember to enable jQuery in the view otherwise the $view->jQuery->addOnLoad() above does not work!

Then you can call this method in the Forms:

class Application_Form_User_City extends My_Form
{
	public function init()
	{
	         // Set the method for the display form to POST
	         $this->setAttrib('id', 'user_city_form')
				->setAttrib('class', 'f1n120');
 
	        // element: Description
	        $this->addElement('text', 'description', array(
	            'label'      => $this->translate('Description'),
	            'attribs'      => array('size' => 35),
	            'required'   => true,
	            'filters'    => array('StringTrim'),
	            'validators' => array('NotEmpty'),
	        ));
 
	       // element: City
               $idAc = $this->createMyAutoComplete( "idcity",
                                         "/utility/search.city",
                                         // set params of element
                                         array(
                                             'label'    => $this->translate('City'),
                                             'attribs'    => array('size' => 25),
                                             'required' => true,
                                             'ignore'   => true, // to ignore when submit
                                             'filters'    => array('StringTrim'),
                                             'validators' => array('NotEmpty'),
                                         )
                );
	}
}

So we can create quickly an AutoComplete element and its hidden field.
Note: Add ignore attrib not submit this field. So we’ll submit directly the id.

To finish I created a custom Javascript to manage the autocomplete element.
Include this one in your Javascript file:

    MyAutocomplete = {
        initialize: function(idelement, mysource, acext)
        {
            eac = '#' + idelement + acext;
            eac_id = '#' + idelement;
 
            $(eac).autocomplete({
                minLength: 0,
                source: mysource,
                focus: function(event, ui) {
                    $(eac).val(ui.item.label);
                    return false;
                },
                select: function(event, ui) {
                    $(eac).val(ui.item.label);
                    $(eac_id).val(ui.item.value);
                    return false;
                }
            })
            .data( "autocomplete" )._renderItem = function( ul, item ) {
                return $( "<li></li>" )
                    .data( "item.autocomplete", item )
                    .append( "<a>" + item.label + "</a>" )
                    .appendTo( ul );
            };
        }
    }

The ajax response from the source must be an array with two fields: label and value.
This js function prepare data for every single item (_renderItem) and set the the value on the hidden field (eac_id). Need 3 parameters:

  • idelement – the real id
  • source – where to read data
  • acext – the extension of the autocomplete field

This parameters are set automatically in the above My_Form class.
Try it!