Author: dr Date: Tue Dec 18 10:16:45 2007 New Revision: 7019 Log: - Add first draft of tree/yui article.
Added: docs/articles/2007-12-20-tree-yui.txt (with props) docs/articles/files/article-2007-12-20-tree-yui.tar.gz (with props) docs/articles/images/2007-12-20-menu1.png (with props) docs/articles/images/2007-12-20-menu2.png (with props) docs/articles/images/2007-12-20-menu3.png (with props) docs/articles/images/2007-12-20-sprite.gif (with props) Added: docs/articles/2007-12-20-tree-yui.txt ============================================================================== --- docs/articles/2007-12-20-tree-yui.txt (added) +++ docs/articles/2007-12-20-tree-yui.txt [iso-8859-1] Tue Dec 18 10:16:45 2007 @@ -1,0 +1,682 @@ +====================================== +The Tree component and YUI integration +====================================== + +:Author: Derick Rethans +:Date: 2007-12-20 10:39 + +.. contents:: Table of Contents + +With the 2007.2 of eZ Components comes a new component: Tree_. This component +allows you to create, manipulate and query tree structures in many ways. +Besides this basic functionality the component also allows you to render the +tree structure in different ways, with different visualisers. The +ezcTreeVisitorPlainText_ class creates a simple representation of the tree +structure, that can be used to display the structure on a console, and the +ezcTreeVisitorGraphViz_ class creates GraphViz_ compatible output to generate +an image representing your tree structure. A tree is often a good +representation of the structure of a website as well, and therefore generating +a menu out of it for your website is not such a strange idea. There are many +different ways of doing so, but one of the nicer widgets is `YUI's`_ `menu +widget`_. The Tree component also contains a YUI menu visualizer to easy +creating menus for your website. The menu you see on http://ezcomponents.org/ +is also based on the Tree component combined with the YUI menu widget for +example. This article demonstrates on how you can use the Tree component +together with YUI to create a menu tool bar. + +.. _Tree: /docs/tutorials/Tree +.. _ezcTreeVisitorPlainText: /s/ezcTreeVisitorPlainText +.. _ezcTreeVisitorGraphViz: /s/ezcTreeVisitorGraphViz +.. _GraphViz: http://www.graphviz.org/ +.. _`YUI's`: http://developer.yahoo.com/yui/ +.. _`menu widget`: http://developer.yahoo.com/yui/menu/ + +YUI Introduction +================ + +The *Yahoo User Interface library* contains many widgets, and even many +different kinds of menus. The Tree component's YUI visualization visitor +(ezcTreeVisitorYUI_) deals with the "Website Top Nav With Submenus Built From +Markup" menu only - at least for now. An example__ of this can be found on the +YUI `menu widget`_ documentation site. The YUI widgets depend on a specific +set of JavaScript files, that you can either copy to your local server or +request directly from the Yahoo website. YUI also comes with a predefined +stylesheet that **has** to be used as well, while it still allows re-styling +with CSS. There is a guide on "skinning__" available on the YUI website, although +it is a bit limited in what it explains. There is also all sorts of options +available that change the behavior of the menu slightly. + +.. _ezcTreeVisitorYUI: /s/ezcTreeVisitorYUI +__ http://developer.yahoo.com/yui/examples/menu/topnavfrommarkup_source.html +__ http://developer.yahoo.com/yui/menu/#skinref + + +The Menu +======== + +The menu that we will be using in this article is the same one as we +use on the `eZ Components`_ website. It currently consists of a two +dimensional menu. The top-level describes the section (Overview, +Documentation, Download, Community, Support, Resources, Search and About), and +the level underneath different pages (such as under Documentation: +Installation Guide, Tutorials and API Reference). + +.. _`eZ Components`: http://ezcomponents.org/ + +There are several ways of storing a tree structure with the Tree component. +You can either use an `XML file`__ as tree definition, or one of the three +database__ tree backends. As we will not have to manipulate the tree structure +in our application, we will simply be using the ezcTreeXml class for tree +storage in this article. + +__ /s/ezcTreeXml +__ /s/ezcTreeDb + +Defining the structure +---------------------- + +Because we are using an XML file for the tree definition, it is important to +know the allowed structure of the XML. Our tutorial__ already describes the +format with a Relax-NG schema, but that can be somewhat hard to read. Below +you will find a Relax-NG-Compressed schema:: + + default namespace = "http://components.ez.no/Tree" + namespace etd = "http://components.ez.no/Tree/data" + + start = + element tree { + attribute prefix { xsd:ID }?, + node? + } + node = + element node { + attribute id { xsd:ID }, + element etd:data { text }?, + node* + } + +With normal words, this schema means: + +#. The root node is the "tree" element, as defined by the "start" definition +#. The "tree" element can have an optional attribute called "prefix" +#. The "tree" element can have an optional "node" definition +#. The "node" definition says that the "node" element requires the attribute "id" +#. The "node" element can have an optional "data" element from the namespace "etd" +#. The "node" element can have zero to many "node" elements as children + +__ /s/Tree + +As we're first looking at the structure only, we'll leave out the data parts +for now. A part of the structure of our site as XML file looks like the +following:: + + <?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE tree PUBLIC "" ""> + <tree xmlns="http://components.ez.no/Tree" xmlns:etd="http://components.ez.no/Tree/data"> + <node id="root"> + <node id="overview"> + <node id="requirements"></node> + <node id="license"></node> + <node id="roadmap"></node> + </node> + <node id="docs"> + <node id="install"></node> + <node id="tutorials"></node> + <node id="api"></node> + </node> + <node id="resources"> + <node id="news"></node> + <node id="articles"></node> + <node id="presentations"></node> + </node> + <node id="search"> + </node> + </node> + </tree> + +We can load the tree structure with the following code:: + + <?php + ini_set( 'include_path', '/home/derick/dev/ezcomponents/trunk:.' ); + require 'Base/src/ezc_bootstrap.php'; + + // load the tree + $tree = new ezcTreeXml( 'tree.xml', new ezcTreeXmlInternalDataStore() ); + +Fetching data then goes with:: + + // show the data in one of the nodes + $docs = $tree->fetchNodeById( 'docs' ); + echo $docs->data, "\n"; + ?> + +As we don't *have* any data associated with the tree yet, the last line will +actually throw an exception now:: + + ezcTreeDataStoreMissingDataException: The data store does not have data stored + for the node with ID 'docs'. in + /home/derick/dev/ezcomponents/trunk/Tree/src/stores/xml_internal.php on line 80 + +Adding data +----------- + +The default XML-based data store that comes with the Tree component is +accessed through the ezcTreeXmlInternalDataStore__ class. This data store +stores the data as elements of the nodes, like:: + + <node id="search"> + <etd:data>Search page</etd:data> + </node> + +However, for our website that is not enough, as we also need to know what type +of document belongs to the node. In our case, that can be a PHP script, or a +rendered reStructuredText__ file. For the eZ Components website we solve this +by using a database store (ezcTreeDbDataStore__) as this can store the data in +multiple columns of the same database table. However, in this tutorial we will +be extending the ezcTreeXmlInternalDataStore class instead to allow for +multiple "fields" of data. + +__ /s/ezcTreeXmlInternalDataStore +__ http://docutils.sourceforge.net/rst.html +__ /s/ezcTreeDbDataStore + +Extending the data store +~~~~~~~~~~~~~~~~~~~~~~~~ + +To extend the data store, we simply create a class that inherits the +ezcTreeXmlInternalDataStore class. Then we can use this while loading the +tree:: + + <?php + ini_set( 'include_path', '/home/derick/dev/ezcomponents/trunk:.' ); + require 'Base/src/ezc_bootstrap.php'; + + class ezcaTreeXmlDataStore extends ezcTreeXmlInternalDataStore + { + } + + // load the tree + $tree = new ezcTreeXml( 'tree.xml', new ezcaTreeXmlDataStore() ); + +As you can see from the example above, we created the ezcaTreeXmlDataStore +class. Please note that the prefix of this class is not "ezc", but "ezca". We +encourage that all classes *not* part of the eZ Components do *not* use the +"ezc" prefix to prevent naming clashes. In this case we use "ezca", which +stands for "eZ Components Article". When we run this example, nothing really +has changed because we didn't reimplement the methods of the data store yet - +i.e. the same exception is thrown when we want to load the data for a node +with:: + + // show the data in one of the nodes + $docs = $tree->fetchNodeById( 'docs' ); + echo $docs->data, "\n"; + ?> + +The interface ezcTreeDataStore__ describes the methods that need to be +implemented for each data store. +Because the data is embedded with XML data stores, we do not have to +reimplement the deleteDataForAllNodes() and deleteDataForNodes() methods. +They do not have any function because the data is already deleted as soon as a +node is removed from the three. It +still leaves three other methods to reimplement that deal with fetching +and storing data. The method for storing data is actually only needed when the +tree is manipulated by the script as well - we're not doing that so we won't +reimplement this either. One of the two left over methods is now +fetchDataForNodes(). This method simply loops over a ezcTreeNodeList__ and +runs fetchDataForNode() for each of the nodes in the list. We can therefore +get away by just reimplementing this one method. + +__ /s/ezcTreeDataStore +__ /s/ezcTreeNodeList + +Instead of trying to parse complex XML structures, we're going to cheat a +little bit. We will just store both bits of information (type of page and name) in +the same text element, separated by a semicolon (:). Below is an example of +how a node with its data attached then looks like:: + + <node id="resources"> + <etd:data>rst:Resources</etd:data> + </node> + <node id="search"> + <etd:data>php:Search</etd:data> + </node> + +The implementation of the fetchDataForNode() method is copied from the +original file. The 2nd last line used to read:: + + $node->data = $dataElem->firstChild->data; + +In the reimplementation it looks like:: + + $node->data = split( ':', $dataElem->firstChild->data ); + +Here is the script again, with the reimplemented method in place and the +last line changed to allow dumping an array:: + + <?php + ini_set( 'include_path', '/home/derick/dev/ezcomponents/trunk:.' ); + require 'Base/src/ezc_bootstrap.php'; + + class ezcaTreeXmlDataStore extends ezcTreeXmlInternalDataStore + { + /** + * Retrieves the data for the node $node from the data store and assigns it + * to the node's 'data' property. + * + * @param ezcTreeNode $node + */ + public function fetchDataForNode( ezcTreeNode $node ) + { + $id = $node->id; + $elem = $this->dom->getElementById( "{$node->tree->prefix}{$id}" ); + $dataElem = $elem->getElementsByTagNameNS( 'http://components.ez.no/Tree/data', 'data' )->item( 0 ); + if ( $dataElem === null ) + { + throw new ezcTreeDataStoreMissingDataException( $node->id ); + } + $node->data = split( ':', $dataElem->firstChild->data ); + $node->dataFetched = true; + } + } + + // load the tree + $tree = new ezcTreeXml( 'tree.xml', new ezcaTreeXmlDataStore() ); + + // show the data in one of the nodes + $docs = $tree->fetchNodeById( 'docs' ); + var_dump( $docs->data ); + ?> + +Of course, for this to work, we also need to modify the tree.xml file as well, +to include data for the node in question:: + + ... + <node id="docs"> + <etd:data>rst:Documentation</etd:data> + ... + </node> + ... + +The output of the script is now:: + + array(2) { + [0]=> + string(3) "rst" + [1]=> + string(13) "Documentation" + } + +The full example can be found in the archive with examples with the filename +``extend1.php``. + +Example renderings +------------------ + +To render the tree as a visually appeasing structure, there are a few +classes. They implement the visitor pattern and can be used to walk over a +tree and generate pretty output. The ezcTreeVisitorPlainText__ visitor renders +a tree for a console based output. It also works fine for this tutorial. In +the following example we use this visitor class to generate our output. We +modify the above script and replace the "show the data in one of the nodes" +part with:: + + // display the tree + $visitor = new ezcTreeVisitorPlainText(); + $tree->accept( $visitor ); + echo (string) $visitor; + ?> + +This produces the following output:: + + root + ââoverview + â âârequirements + â ââlicense + â ââroadmap + ââdocs + â ââinstall + â ââtutorials + â ââapi + ââresources + â âânews + â ââarticles + â ââpresentations + ââsearch + +This example's source code can be found in the archive with examples with the +filename ``visitor1.php``. + +Unfortunately, it does not seem to use the pretty names for all the nodes. In +order to solve that, we need to subclass the ezcTreeVisitorPlainText__ class. +We override the visit() method of this class as follows:: + + class ezcaTreeVisitorPlainText extends ezcTreeVisitorPlainText + { + public function visit( ezcTreeVisitable $visitable ) + { + if ( $visitable instanceof ezcTreeNode ) + { + if ( $this->root === null ) + { + $this->root = $visitable->data[1]; + } + + $parent = $visitable->fetchParent(); + if ( $parent ) + { + $this->edges[$parent->data[1]][] = $visitable->data[1]; + } + } + return true; + } + } + +The original class had instead of the ``->data[1]`` bits ``->id``. +When we modify the original script to use to new inherited visitor, +ezcaTreeVisitorPlainText, the output turns into the more pleasent display +below:: + + Homepage + ââOverview + â ââPHP Requirements + â ââLicense + â ââRoadmap + ââDocumentation + â ââInstallation Guide + â ââTutorials + â ââAPI Reference + ââResources + â ââNews Archive + â ââArticles and Publications + â ââPresentation Slides + ââSearch + +The full example can be found in the archive with examples with the filename +``extend2.php``. + +__ /s/ezcTreeVisitorPlainText +__ /s/ezcTreeVisitorPlainText + + +Adding the menu to the site +=========================== + +The YUI visitor works quite similar to the plain text visitor. Before we get +there however, we need to integrate the YUI library into the website. + +Adding the YUI bits +------------------- + +First of all, you need to include the correct JavaScript. We run them directly +from the Yahoo servers, but of course you can also just put them locally. The +lines below should be placed in the "head" section of your HTML file:: + + <!-- Dependencies --> + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo-dom-event/yahoo-dom-event.js"></script> + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/container/container_core-min.js"></script> + <!-- Source File --> + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/menu/menu-min.js"></script> + +In the same "head" section you should also place the default CSS style sheets +for the YUI menu:: + + <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.3.1/build/menu/assets/skins/sam/menu.css"/> + +The two snippets above only make YUI accessible to you, but they do not +actually do anything until you add some JavaScript to attach the YUI menu +classes/JavaScript to the menu. + +Rendering the menu +------------------ + +The PHP code to generate the menu in the way YUI wants, is again very simple. +We just use the ezcTreeVisiterYUI__ class instead of the +ezcTreeVisitorPlainText class that we used before:: + + // display the tree + $visitor = new ezcTreeVisitorYUI( 'menu' ); + $tree->accept( $visitor ); + echo (string) $visitor; + ?> + +There is one other change, and that is the argument to ezcTreeVisitorYUI's +constructor. This is the XHTML ID that is set on the top-level element that is +used for rendering the menu. You will need to use the same ID when attaching +the YUI JavaScript to the menu. + +__ /s/ezcTreeVisiterYUI + +However, there is one other thing that we need to fix first. By default data +stores return only a string, but we've modified the data store to return an +array. Because of this the script will now output lots of warnings like:: + + Warning: htmlspecialchars() expects parameter 1 to be string, array given + in /home/derick/dev/ezcomponents/trunk/Tree/src/visitors/yui.php on line 101 + +In order to work around this, we need to extend the visitor class and override +the formatData() method. We do that with the following code:: + + class ezcaTreeVisitorYUI extends ezcTreeVisitorYUI + { + protected function formatData( $data, $highlight ) + { + $data = $data[1]; + $data = htmlspecialchars( $data ); + return $data; + } + } + + // display the tree + $visitor = new ezcaTreeVisitorYUI( 'menu' ); + $tree->accept( $visitor ); + echo (string) $visitor; + ?> + +The script now outputs the XHTML content that the YUI JavaScript wants for +rendering the menu. You can find this script in the archive with examples with +the filename ``yui2.php``. + +To apply the YUI library and style sheets, we need to put one more thing in +the head section of the website. This code attaches the YUI menu to the XHTML +tag with the ID that we used before ("menu"). In the code below this "menu" ID +returns in two places:: + + <script type="text/javascript"> + YAHOO.util.Event.onContentReady('menu', function () { + var oMenu = new YAHOO.widget.MenuBar('menu', { autosubmenudisplay: true, showdelay: 200 }); + + oMenu.render(); + }); + </script> + +When we tie everything together, we end up with the following code to render +the menu YUI style:: + + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.3.1/build/menu/assets/skins/sam/menu.css"/> + + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo-dom-event/yahoo-dom-event.js"></script> + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/container/container_core-min.js"></script> + <script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/menu/menu-min.js"></script> + + <script type="text/javascript"> + YAHOO.util.Event.onContentReady('overview', function () { + var oMenu = new YAHOO.widget.MenuBar("menu", { autosubmenudisplay: true, showdelay: 200 }); + + oMenu.render(); + }); + </script> + </head> + <body class="yui-skin-sam"> + <?php + ini_set( 'include_path', '/home/derick/dev/ezcomponents/trunk:.' ); + require 'Base/src/ezc_bootstrap.php'; + + class ezcaTreeXmlDataStore extends ezcTreeXmlInternalDataStore + { + public function fetchDataForNode( ezcTreeNode $node ) + { + $id = $node->id; + $elem = $this->dom->getElementById( "{$node->tree->prefix}{$id}" ); + $dataElem = $elem->getElementsByTagNameNS( 'http://components.ez.no/Tree/data', 'data' )->item( 0 ); + if ( $dataElem === null ) + { + throw new ezcTreeDataStoreMissingDataException( $node->id ); + } + $node->data = split( ':', $dataElem->firstChild->data ); + $node->dataFetched = true; + } + } + + // load the tree + $tree = new ezcTreeXml( 'tree.xml', new ezcaTreeXmlDataStore() ); + + class ezcaTreeVisitorYUI extends ezcTreeVisitorYUI + { + protected function formatData( $data, $highlight ) + { + $data = $data[1]; + $data = htmlspecialchars( $data ); + return $data; + } + } + + // display the tree + $visitor = new ezcaTreeVisitorYUI( 'menu' ); + $tree->accept( $visitor ); + echo (string) $visitor; + ?> + </body> + </html> + +When rendered in the browser, it looks like: + +.. image:: ../../images/articles/2007-12-20-menu1.png + + +Styling +------- + +Styling the menu is done with CSS. The YUI widget's default CSS all use the +*yui-skin-sam* "name space". Overriding just specific classes of YUI widgets +is therefore quite easy. There are however a *lot* of different elements that +needs restyling - so some advanced CSS skills might be required to get it how +you want it to look. An excellent tool to assist you with this, is FireBug__. + +__ http://www.getfirebug.com/ + +As an example, we're going to style the menu bar in the "eZ Components" +colour: indigo. The first step is to change the border around the menu, and +the background of the menu. +We do that by overriding the following CSS:: + + .yui-skin-sam .yuimenubar { + border: 1px solid #55517b; + background: #55517b; + } + + .yui-skin-sam .yuimenubaritemlabel { + border-color: #55517b; + } + + .yui-skin-sam .yuimenu .bd { + background: #55517b; + } + +As the text is no longer readable, we change the text colour to white, and +also change the font to a non-serif font:: + + .yui-skin-sam .yuimenubaritemlabel, + .yui-skin-sam .yuimenuitemlabel + { + font-family: sans-serif; + color: white; + } + +By now, the menu looks like: + +.. image:: ../../images/articles/2007-12-20-menu2.png + +Two things are left now. First of all, we need to change the background and +text colour of selected items. This we simply can do with the following CSS:: + + .yui-skin-sam .yuimenubaritem a.selected { + background: #ffffff; + color: #55517b; + } + +The second thing is that we need to change the colour of the arrow on the +right side of each menu to white for non-selected menu items. YUI uses a +special trick for rendering images in their widgets. Instead of having a +little image file for each element, they instead use a "sprite" file +containing **all** the elements. From this they just cut out the part they +need. Some information about this techinique can be found here__. As we want +to change the arrow images, I created my own "sprite" to be used for those two +elements. The sprite file is here__. + +The sprite file defines two arrows, that we are placing with CSS on the +correct position:: + + .yui-skin-sam .yuimenubarnav .yuimenubaritemlabel .submenuindicator { + background-position:5px -21px; + height:8px; + left:auto; + margin-top:-3px; + right:8px; + text-indent:8px; + top:50%; + width:16px; + } + + .yui-skin-sam .yuimenuitemlabel .submenuindicator, + .yui-skin-sam .yuimenuitemlabel .checkedindicator, + .yui-skin-sam .yuimenubaritemlabel .submenuindicator { + background:transparent url(2007-12-20-sprite.gif) no-repeat scroll 0%; + overflow:hidden; + position:absolute; + } + + .yui-skin-sam .yuimenubarnav a.selected .submenuindicator { + background:transparent url(2007-12-20-sprite.gif) repeat-x scroll 5px -5px; + } + + +__ http://mattberseth.com/blog/2007/09/using_css_image_sprites_with_t.html +__ ../../images/articles/2007-12-20-sprite.gif + + +The end result of all the CSS tweaking looks like: + +.. image:: ../../images/articles/2007-12-20-menu3.png + +All the CSS overrides from above, can be found in the "ezc.css" file in the +examples download. We still need to hook it into the HTML file though, which +we do by adding the following line **after** the already existing stylesheet +line for the default YUI styles:: + + <link rel="stylesheet" type="text/css" href="ezc.css"/> + +Conclusion +========== + +In this article we showed how to integrate the Tree component with the Yahoo! +UI Library's menu widget by generating the XHTML content that the YUI widget +expects. From there on we explored on how the menu widget can be styled +according to your wishes. There is plenty of other things that can be +configured as well with the YUI menu, but that falls out of the scope of this +article. See the `YUI documentation`__ for more information. + +__ http://developer.yahoo.com/yui/menu + +Resources +========= + +* `Tree component documentation`__ +* `Yahoo! User Interface Library Menu`__ +* `CSS sprites explanation`__ + +__ /s/Tree +__ http://developer.yahoo.com/yui/menu +__ http://mattberseth.com/blog/2007/09/using_css_image_sprites_with_t.html Propchange: docs/articles/2007-12-20-tree-yui.txt ------------------------------------------------------------------------------ svn:eol-style = native Added: docs/articles/files/article-2007-12-20-tree-yui.tar.gz ============================================================================== Binary file - no diff available. Propchange: docs/articles/files/article-2007-12-20-tree-yui.tar.gz ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: docs/articles/images/2007-12-20-menu1.png ============================================================================== Binary file - no diff available. Propchange: docs/articles/images/2007-12-20-menu1.png ------------------------------------------------------------------------------ svn:mime-type = image/png Added: docs/articles/images/2007-12-20-menu2.png ============================================================================== Binary file - no diff available. Propchange: docs/articles/images/2007-12-20-menu2.png ------------------------------------------------------------------------------ svn:mime-type = image/png Added: docs/articles/images/2007-12-20-menu3.png ============================================================================== Binary file - no diff available. Propchange: docs/articles/images/2007-12-20-menu3.png ------------------------------------------------------------------------------ svn:mime-type = image/png Added: docs/articles/images/2007-12-20-sprite.gif ============================================================================== Binary file - no diff available. Propchange: docs/articles/images/2007-12-20-sprite.gif ------------------------------------------------------------------------------ svn:mime-type = image/gif -- svn-components mailing list svn-components@lists.ez.no http://lists.ez.no/mailman/listinfo/svn-components