Początkowo jako następny artykuł miało się tutaj pojawić podsumowanie tematu drzew wraz z przykładami praktycznego zastosowania. Jednym z tematów miało być wykorzystanie drzewa i elementu dijit.Tree w formie własnego elementu Zend_Form. Niestety, podczas prac trafiłem na pewien problem który mnie zablokował i za bardzo nie wiem jak do niego podejść w miarę prosty sposób. Tak więc tym razem zamiast gotowego rozwiązania będzie bardziej pytanie i prośba o pomoc.
Uwaga - zawarte fragmenty kodu są wersjami roboczymi, zapewne zawierają sporo błędów i można je napisać znacznie ładniej i zgodniej z standardami. Znajdują się one tutaj aby lepiej zobrazować problem i stanowić tylko i wyłącznie wskazówki i pomoc, tak więc proszę ich nie wykorzystywać bez zastanowienia i dopracowania, aby nie mieć później pretensji że coś nie działa lub co gorsza działa źle.
Kto interesuje się Dojo wie że w jego skład wchodzi widget dijit.Tree. Naturalna więc wydaje się próba ułatwienia sobie życia i próba stworzenia elementu formy który da użytkownikom możliwość szybkiego i wygodnego wyboru elementu za pomocą rozwijanego drzewa. Początkowo problem wydawał się banalny, i pierwszy “szkic” takiego elementu napisałem w około godzinkę.
Idea działania jest prosta - tworzymy zwykły ukryty element INPUT w którym będzie lądował identyfikator wybranego elementu drzewa. Do tego tworzymy element DIV który następnie zostanie przekształcony w element dijit.Tree.
/**
* Tree form element
*
* @uses Mao_View_Helper_FormDiv
*/
class Mao_Form_Element_Tree extends Zend_Dojo_Form_Element_ComboBox {
/**
* Use FormTree view helper
* @var string
*/
public $helper = 'FormTree';
}
/**
* Dojo Tree dijit
*
* @uses Zend_Dojo_View_Helper_Dijit
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Mao_View_Helper_FormTree extends Zend_Dojo_View_Helper_Dijit
{
/**
* Dijit being used
* @var string
*/
protected $_dijit = 'dijit.Tree';
/**
* HTML element type
* @var string
*/
protected $_elementType = 'text';
/**
* Dojo module to use
* @var string
*/
protected $_module = 'dijit.Tree';
/**
* dijit.form.ComboBox
*
* @param int $id
* @param mixed $value
* @param array $params Parameters to use for dijit creation
* @param array $attribs HTML attributes
* @param array|null $options Select options
* @return string
*/
public function formTree($id, $value = null, array $params = array(), array $attribs = array(), array $options = null)
{
$html = '';
if (!array_key_exists('id', $attribs)) {
$attribs['id'] = $id;
}
if (array_key_exists('store', $params) && is_array($params['store'])) {
// using dojo.data datastore
if (false !== ($store = $this->_renderStore($params['store']))) {
$params['store'] = $params['store']['store'];
if (is_string($store)) {
$html .= $store;
}
$html .= $this->_createFormElement($id, $value, $params, $attribs);
return $html;
}
unset($params['store']);
} elseif (array_key_exists('store', $params)) {
if (array_key_exists('storeType', $params)) {
$storeParams = array(
'store' => $params['store'],
'type' => $params['storeType'],
);
unset($params['storeType']);
if (array_key_exists('storeParams', $params)) {
$storeParams['params'] = $params['storeParams'];
unset($params['storeParams']);
}
if (false !== ($store = $this->_renderStore($storeParams))) {
if (is_string($store)) {
$html .= $store;
}
}
}
$html .= $this->_createFormElement($id, $value, $params, $attribs);
return $html;
}
throw new Exception('No dojo.data store provided.');
}
/**
* Create HTML representation of a dijit form element
*
* @param string $id
* @param string $value
* @param array $params
* @param array $attribs
* @param string|null $dijit
* @return string
*/
public function _createFormElement($id, $value, array $params, array $attribs, $dijit = null) {
if (! array_key_exists ( 'id', $attribs )) {
$attribs ['id'] = $id;
}
$attribs ['name'] = $id;
$attribs ['value'] = ( string ) $value;
$attribs ['type'] = $this->_elementType;
$attribs ['id'] = '_' . $id . '_';
$attribs = $this->_prepareDijit ( $attribs, $params, 'element', $dijit );
$attribs ['id'] = $id;
// atrybuty na potrzeby DIVa reprezentującego dijit.Tree
$divAttribs = $attribs;
// Ukrywamy element INPUT
$attribs ['type'] = 'hidden';
// Wyrzucamy ukryty input
$html = '<input'
. $this->_htmlAttribs($attribs)
. $this->getClosingBracket();
// Modyfikujemy atrybuty na potrzeby elementu DIV reprezentującego dijitTree
$divAttribs ['id'] = '_' . $id . '_';
$divAttribs ['name'] = '_' . $id . '_';
unset ( $divAttribs ['name'], $divAttribs ['value'] );
$html .= '<div '
. $this->_htmlAttribs( $divAttribs )
. $this->getClosingBracket()
. '</div>';
// Wyrzucamy kod JS odpowiadający za połączenie ukrytego inputa z wyświetlonym drzewkiem
$this->_renderJsHelper ( $id, $params );
return $html;
}
/**
* Render data store element
*
* Renders to dojo view helper
*
* @param array $params
* @return string|false
*/
protected function _renderStore(array $params)
{
if (!array_key_exists('store', $params) || !array_key_exists('type', $params)) {
return false;
}
$this->dojo->requireModule($params['type']);
$extraParams = array();
$storeParams = array(
'dojoType' => $params['type'],
'jsId' => $params['store'],
);
if (array_key_exists('params', $params)) {
$storeParams = array_merge($storeParams, $params['params']);
$extraParams = $params['params'];
}
if ($this->_useProgrammatic()) {
if (!$this->_useProgrammaticNoScript()) {
require_once 'Zend/Json.php';
$js = 'var ' . $storeParams['jsId'] . ' = '
. 'new ' . $storeParams['dojoType'] . '('
. Zend_Json::encode($extraParams)
. ");\n";
$this->dojo->addJavascript($js);
}
return true;
}
return '<div' . $this->_htmlAttribs($storeParams) . '></div>';
}
/**
* Render data store element
*
* Renders to dojo view helper
*
* @param string $id
* @param array $params
* @return string|false
*/
protected function _renderJsHelper($id, array $params) {
if ($this->_useProgrammatic ()) {
if (! $this->_useProgrammaticNoScript ()) {
require_once 'Zend/Json.php';
$js = '
dojo.subscribe("_' . $id . '_", "execute", function( data ){
dojo.byId("' . $id . '").value = ' . $params ['store'] . '.getIdentity( data.item );
});
';
$this->dojo->addJavascript ( $js );
}
return true;
}
return '<div' . $this->_htmlAttribs ( $storeParams ) . '></div>';
}
}
Powyższe 2 klasy załatwiają nam utworzenie wspomnianych elementów INPUT i DIV. Dodatkowo w metodzie _renderJsHelper znajduje się prosty kod JS odpowiedzialny za nasłuch zdarzeń w utworzonym widgecie dijitTree i w po wybraniu jakiegoś elementu wstawienie jego identyfikatora do pola INPUT (to ten fragment z dojo.subscribe).
Od strony formy wykorzystanie tego elementu wygląda mniej więcej tak:
class default_forms_TreeTest extends Mao_Form {
public function __construct($options = null) {
parent::__construct ( $options );
// Dojo-enable the form:
Zend_Dojo::enableForm ( $this );
$this->addElement ( 'Tree', 'tree_id',
array (
'label' => 'Tree',
'storeId' => 'tstr',
'storeType' => 'dojo.data.ItemFileReadStore',
'storeParams' => array(
'url' => '/mag/categories/categoriesTreeJson.json',
),
'dijitParams' => array(
'labelAttr' => 'name',
),
'value' => 4,
'style' => 'height: 200px; overflow: auto;',
)
);
$this->addElement ( 'SubmitButton', 'submit',
array (
'label' => 'Zapisz'
)
);
}
}
Natomiast wynikowy kod HTML:
[sourcecode]
<!-- W części JS generowanej przez Zend_Dojo -->
<script type="text/javascript">
//<![CDATA[
dojo.require("dijit.Tree");
var tstr = new dojo.data.ItemFileReadStore({"url":"\/mag\/categories\/categoriesTreeJson.json"});
//
dojo.subscribe("_tree_id_", "execute", function( data ){
// console.log( tstr.getIdentity( data.item ) );
dojo.byId("tree_id").value = tstr.getIdentity( data.item );
});
//]]>
</script>
<!-- I część formy -->
<dt><label for="tree_id" class="optional">Tree</label></dt>
<dd>
<input style="height: 200px; overflow: auto;" id="tree_id" name="tree_id" value="4" type="hidden">
<div style="height: 200px; overflow: auto;" id="_tree_id_" type="text"></div>
</dd>
[/sourcecode]
W sumie wszystko teoretycznie działa, brakuje jednak pewnej “małej” ale ważnej funkcjonalności - przy wyświetlaniu formy powinno nastąpić automatyczne zaznaczenie elementu drzewa którego identyfikator znajduje się w polu INPUT, oraz rozwinięcie odpowiedniej gałęzi drzewa. Niestety twórcy dijit.Tree z jakiegoś powodu nie zaimplementowali do tego odpowiednich metod przez co trzeba to zrobić samemu. I tutaj mamy 2 problemy:
- lazy loading
- znalezienie wybranego elementu, rozwinięcie gałęzi do której on należy i zaznaczenie go
Problem pierwszy wynika z tego że dane do drzewa ładowane są asynchronicznie po stworzeniu elementu. Na szczęście problem ten jest dość prosty do rozwiązania z wykorzystaniem interwałów:
var _tree_id_timer;
dojo.addOnLoad( function () {
if ( dojo.byId("tree_id").value != "") {
_tree_id_timer = setInterval( function(){
if ( dijit.byId("_tree_id_").store._loadFinished ) {
// Dane do elementu zostały załadowane
clearInterval( _tree_id_timer );
// Czas znaleźć i zaznaczyć wybrany element
}
}, 100);
}
});
W skrócie, jeśli w pole input zawiera jakąś wartość, to tworzymy interwał który co określony czas sprawdza czy źródło danych wykorzystywane przez dijit.Tree zakończyło wczytywanie danych (dijit.byId(”_tree_id_”).store._loadFinished). Jeśli skończyło to czyścimy interwał (już nam nie będzie potrzebny) i przystępujemy do problemu drugiego.
Czytaj dalej tutaj (rozwija treść wpisu)
Czytaj dalej na blogu autora...
Zwiń
Czytaj na blogu autora...