![]() |
|
Snippets |
|
Simple helper for embeding flash objects using Unobtrusive Flash Objects (UFO). Download and install ufo.js to js/ufo.js and place your movies (*.swf) to web/swf, FlashHelper.php in your lib/helper directory.
<div id="flashcontent"> This text is replaced by the Flash movie. </div> <?php use_helper('Flash'); ?> <?php echo flash_object('flashcontent', array( 'movie'=>'movie.swf', 'width'=>'200', 'height'=>'300', 'bgcolor'=> '#FFFFFF'); ?>
<?php /** * Flash object helper - embeds Flash objects (files with the .swf extension) * * Uses Unobtrusive Flash Objects (UFO) * * @param string $id - The ID od the div which will be replaced by flash movie * @param mixed $options - Flash object options * @see http://www.bobbyvandersluis.com/ufo/ */ function flash_object($id, $options = array()) { sfContext::getInstance()->getResponse()->addJavascript('ufo.js'); $options = _parse_attributes($options); $absolute = false; if (isset($options['absolute'])) { unset($options['absolute']); $absolute = true; } if(isset($options['size'])) { list($options['width'], $options['height']) = split('x', $options['size'], 2); unset($options['size']); } if(!isset($options['majorversion'])) { $options['majorversion'] = 7; } if(!isset($options['build'])) { $options['build'] = 0; } // check for all required params foreach(array('movie', 'width', 'height', 'majorversion', 'build') as $required) { if(!isset($options[$required])) { throw new sfException("{FlashHelper} Required parameter \"$required\" is missing."); } } $options['movie'] = flash_path($options['movie'], $absolute); $opts = array(); foreach ($options as $key => $value) { $opts[] = $key . ': "' . $value . '"'; } sort($opts); // javascript variable name $name = $id . '_var'; $js = 'var ' . $name . " = {".join(', ', $opts)."};\n"; $js .= 'UFO.create('.$name.', "'.$id.'");'."\n"; return content_tag('script', "\n//".cdata_section("\n$js\n//")."\n", array('type' => 'text/javascript')); } /** * Returns the path to a flash swf movie. * * <b>Example:</b> * <code> * echo flash_path('mymovie'); * => /swf/mymovie.swf * </code> * * <b>Note:</b> The asset name can be supplied as a... * - full path, like "/swf/movie.swf" * - file name, like "movie.swf", that gets expanded to "/swf/movie.swf" * - file name without extension, like "movie", that gets expanded to "/swf/movie.css" * * @param string asset name * @param bool return absolute path ? * @return string file path to the flash movie file */ function flash_path($source, $absolute = false) { return _compute_public_path($source, 'swf', 'swf', $absolute); }
For the record, partials are wonderful. Self-sustained pieces of gui code, with access to all the symfony templating tools you need.
Sometimes however, even partials tend to be overkill. And sometimes, too, you have an annoying little piece of gui code that ought to go into your main template (indexSuccess.php or what have you), but you can't bear to put it there, because it would disturb the flow of an otherwise beautifully crafted template.
The truth is, every reuseable piece of gui code that isn't a full blown template really fits the partial paradigm, but sometimes it's just too much overhead, too much trouble, or just not necessary. Consider the following:
<table> <?php foreach ($vars as $key => $value): ?> <tr> <th><?php include(fragment_name('label')) ?></th> <td><?php include(fragment_name('input')) ?></td> </tr> <?php endforeach; ?> </table>
<table> <?php foreach ($vars as $key => $value): ?> <tr> <th><?php include(fragment_path('somemodule/label')) ?></th> <td><?php include(fragment_path('anothermodule/input')) ?></td> </tr> <?php endforeach; ?> </table>
The _label.php file could contain a few if statements... enough to generate the label for our input. It would look quite a bit uglier to place the code right in the th tag, but that's what we're really going for. _label.php would immediately have access to $key and $value, and any other variables already in scope, including typical templating variables provided by symfony. Additionally, there's no overhead to deal with, which helps if that foreach loop will execute 200 times (well, maybe that's a stretch). Finally, it just looks symfony-esque (at least to me)!
<?php /** * Get the path to a fragment. * The syntax is similar to that of include_partial. * To be used in conjunction with php's include function. * Variables in scope in the caller will be available in the execution of the fragment. * * <strong>Example</strong> * <code> * include(fragment_path('mypartial')) * </code> * * <strong>Example</strong> * <code> * include(fragment_path('mymodule/mypartial')) * </code> * * @param string fragment name * @return string path to the fragment * @see get_fragment, include_fragment, include_partial */ function fragment_path($templateName) { // partial is in another module? if (false !== ($sep = strpos($templateName, '/'))) { $moduleName = substr($templateName, 0, $sep); $templateName = substr($templateName, $sep + 1); } else { $moduleName = sfContext::getInstance()->getActionStack()->getLastEntry()->getModuleName(); } $fragmentName = fragment_name($templateName); // test for global fragment if ($moduleName === 'global') { $path = sfConfig::get('sf_app_template_dir'); } // if not global, or the global fragment doesn't exist if ($moduleName !== 'global' || !is_readable($path.'/'.$fragmentName)) { // get the full path to the file $path = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_template_dir_name'); } // return the full path to the partial return $path.'/'.$fragmentName; } /** * Translates a nice fragment name into the filename adhering to the naming convention for partials. * * <strong>Example</strong> * <code> * include(fragment_name('mypartial')) * </code> * * @param string fragment name * @return string fragment filename * @see fragment_path */ function fragment_name($templateName) { return '_'.$templateName.'.php'; }
Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php
Then you can use it with <?php use_helper('PartialPlus') ?>
I figured I'd post this little helper for using the tabbed pane JS from http://webfx.eae.net/dhtml/tabpane/tabpane.html in your templates.
Simply download the JS package from the site above and place it in your web/js folder. I chose to move the css files from the JS folder to my CSS folder, but you can do whatever you like.
tabsHelper.php
<?php /** * Helper for Javascript Tabbed Panes * * Example Usage * <code> * <?php use_helper('tabs') ?> * <?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?> * This is text of tab 1. This is text of tab 1. This is text of tab 1. * <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?> * This is text of tab 2. This is text of tab 2. This is text of tab 2. * <?php tabInit();?> * </code> * * @package Helpers * @author Jason Ibele * @version SVN: $Id: tabsHelper.php 4 2006-07-19 14:00:47Z jason.ibele $ */ $response = sfContext::getInstance()->getResponse(); $response->addJavascript('tab/tabpane.js'); $response->addStylesheet('tab/tab.webfx.css'); /** * Opens a new TabPane object and creates first tab * * @param string $mid JavaScript variable name to use for webFXTabPane object * @param string $id Main container div ID * @param string $page_id Name for div ID, each needs to be unique * @param string $H2_title The title for the tab * @param string $main_class Optional class name for main Div (note this must match original class definitions) * @param string $page_class Optional class name for page Div (note this must match original class definitions) */ function tabMainJS($mid, $id, $page_id, $H2_title, $main_class='tab-pane', $page_class='tab-page') { echo '<div class="'.$main_class.'" id="'.$id.'">'."\n\t"; echo '<script type="text/javascript">'."\n\t"; echo $mid.' = new WebFXTabPane( document.getElementById( "'.$id.'" ) );'."\n\t"; echo '</script>'."\n\t"; echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t"; echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t"; echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n"; } /** * Closes and existing pane div and opens a new one with required JS * * @param string $mid JavaScript variable to use for webFXTabPane object * @param string $page_id Name for div ID, each needs to be unique * @param string $H2_title The title for the tab * @param string $page_class Optional class name for page Div (note this must match original class definitions) */ function tabPageOpenClose($mid, $page_id, $H2_title, $page_class='tab-page') { echo '</div>'."\n\t"; echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t"; echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t"; echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n"; } /** * Initiates the javascript for tabs and closes the remaining divs * * @param string $mid JavaScript variable to use for webFXTabPane object * @param string $n selected index of tab you want to force as set */ function tabInit($mid='', $n='') { echo "\t".'</div>'."\n\t"; echo '<script type="text/javascript">'."\n\t"; echo 'setupAllTabs();'."\n\t"; if($n){ // n = selected index of tab you want to force as set echo $mid.'.setSelectedIndex('.$n.');'; } echo '</script>'."\n"; echo '</div>'."\n"; } ?>
template
<?php use_helper('tabs') ?> <!-- open the first tab --> <?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?><!-- General is the name of the tab --> This is text of tab 1. This is text of tab 1. This is text of tab 1. <!-- second tab --> <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?> <!-- Security is the name of the tab --> This is text of tab 2. This is text of tab 2. This is text of tab 2. <!-- third tab --> <?php tabPageOpenClose("tp1", "tabPage3", 'Example');?> <!-- Example is the name of the tab --> This is text of tab 3. This is text of tab 3. This is text of tab 3. <!-- close tabs and initiate the JS --> <?php tabInit();?>
Embedding images in emails as described in the symfony book has one drawback: it breaks the MVC paradigm by setting properties of the view (embedding the image) in the controller.
This is probably more obvious when embedding images such as company logos that are not part of a query result, but instead are 'static' visual enhancements of the view.
Besides being not good practice, this results in more code and potential mistakes when using several images, as you need to keep track of cid's both in the action and in the template.
This helper lets you treat images in emails similar to images in regular HTML views. There are no calls to $mail->addEmbeddedImage() needed in the action, embedding is done exclusively in the view, and you can forget about cid's.
Update: In the original version, an image used twice in the email would also be embedded twice, increasing email size. This bug is fixed.
Images are embedded like this:
<?php echo embedded_image($this, '/myImageDir/myImage.jpg' [, $altText [, $encoding [, $mimeType]]]); ?>
The parameters are:
$this: a reference to the current sfMailView
filename: the path to the image to be embedded
$altText: alternative name for attachment, default is ''
$encoding: encoding, default is base64
$mimeType: mime type, when omitted, mime type is determined using the file extension of the image
function embedded_image( $mailView, $fileName, $altText = '', $encoding = 'base64', $mimeType = null ) { // get list of files already embedded $embeddedList = $mailView->getParameter('embedList', array(), 'email_helper'); if (array_key_exists($fileName, $embeddedList)) { // if image was already embedded, use its old cid $embedString = $embeddedList[$fileName]; } else { // find mime type if ($mimeType == null) { $mimeMap = array( 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'png' => 'image/png' ); $ext = strtolower(array_pop(explode('.', $fileName))); if (array_key_exists($ext, $mimeMap)) { $mimeType = $mimeMap[$ext]; } else { $mimeType = 'image/jpeg'; } } // increment embedded image count $imgID = $mailView->getParameter('embedIndex', 0, 'email_helper') + 1; $mailView->setParameter('embedIndex', $imgID, 'email_helper'); $embedString = 'embID_' . $imgID; // add image to mail $mailView->getAttribute('mail')->addEmbeddedImage( $fileName, $embedString, $altText, $encoding, $mimeType ); // remember embedded file $embeddedList[$fileName] = $embedString; $mailView->setParameter('embedList', $embeddedList, 'email_helper'); } return '<img src="cid:' . $embedString . '" />'; }
This is helper is mostly inspired by Pagination navigation helper, but there are some differences:
function pagination($pager) { $uri = sfRouting :: getInstance()->getCurrentInternalUri(); $html = ''; if ($pager->haveToPaginate()) { $uri .= strstr($uri, '?') ? '&page=' : '?page='; if ($pager->getPage() != 1) { $html .= '<li>' . link_to('first', $uri . '1') . '</li>'; $html .= '<li>' . link_to('previous', $uri . $pager->getPreviousPage()) . '</li>'; } foreach ($pager->getLinks() as $page) { if ($page == $pager->getPage()) $html .= '<li><strong>' . link_to($page, $uri . $page) . '</strong></li>'; else $html .= '<li>' . link_to($page, $uri . $page) . '</li>'; } if ($pager->getPage() != $pager->getLastPage()) { $html .= '<li>' . link_to('next', $uri . $pager->getNextPage()) . '</li>'; $html .= '<li>' . link_to('last', $uri . $pager->getLastPage()) . '</li>'; } $html = '<ul class="pagination">' . $html . '</ul>'; } return $html; }
ul.pagination li { display: inline; list-style-type: none; padding-right: 1em; }
<?php echo use_helper('Pagination') ?> <?php echo pagination($pager) ?>
You sometimes need to access the __() helper for text translation outside of a template. There are two ways to do it.
You can include the I18NHelper.php manually in your action, or use the symfony command to include a helper (sfLoaded::LoadHElpers()):
sfLoader::LoadHelpers(array('I18N'));
The problem is that this will add the helper functions to the global namespace. Alternately, use the following syntax:
sfContext::getInstance()->getI18n()->__($text, $args, 'messages');
Note: prior to 1.0, you had to do this (now deprecated)
sfConfig::get('sf_i18n_instance')->__($text, $args, 'messages');
(updated 29/11/06)
In a template displaying a paginated list, you need to show the pager navigation. Create a PaginationHelper.php in lib/helper:
<?php function pager_navigation($pager, $uri) { $navigation = ''; if ($pager->haveToPaginate()) { $uri .= (preg_match('/\?/', $uri) ? '&' : '?').'page='; // First and previous page if ($pager->getPage() != 1) { $navigation .= link_to(image_tag('/sf/images/sf_admin/first.png', 'align=absmiddle'), $uri.'1'); $navigation .= link_to(image_tag('/sf/images/sf_admin/previous.png', 'align=absmiddle'), $uri.$pager->getPreviousPage()).' '; } // Pages one by one $links = array(); foreach ($pager->getLinks() as $page) { $links[] = link_to_unless($page == $pager->getPage(), $page, $uri.$page); } $navigation .= join(' ', $links); // Next and last page if ($pager->getPage() != $pager->getLastPage()) { $navigation .= ' '.link_to(image_tag('/sf/images/sf_admin/next.png', 'align=absmiddle'), $uri.$pager->getNextPage()); $navigation .= link_to(image_tag('/sf/images/sf_admin/last.png', 'align=absmiddle'), $uri.$pager->getLastPage()); } } return $navigation; }
In your templates, display the pagination links like that:
<?php echo use_helper('Pagination') ?> <?php echo pager_navigation($mypager, '@myrule') ?>
I have rewritten my original link_to_styled helper to accommodate more common scenarios. This helper will append a class name to the link if the current route matches the one present in the link.
You can specify within the $style_options parameter to match only the module name rather than the entire route. This is useful for top-level navigation links that correspond to your modules.
You can also specify a tag that will encapsulate your link and receive the class name rather than the <a> tag if the active-state requirements are met.
Additionally, any existing class names given to your links are maintained rather than overwritten with the active class name.
As others have suggested, I'm sure it could still be improved to allow for partial route matching, perhaps excluding query parameters.
NOTE: Thanks to Fabian Lange's suggestion, I reordered the parameters so that the style parameters come before the less-used html options.
Enjoy!
in lib/helper/StyleHelper.php:
/** * Automatically adds a class name to the link if the link route matches * the current module or route. * * <b>Style Options:</b> * - 'class' - the class name to append to the link, defaults to 'current' * - 'module_only' - append class name if the current module and link module match * - 'tag' - an html to encapsulate the link, which will receive the class name rather than the <a> tag * * <b>Examples:</b> * <code> * echo link_to_styled('Events', 'event/list', 'class=active', 'id=event_link', ); * if the current route is 'event/list', then: * => <a href="path/to/event/list/action" id="event_link" class="active">Events</a> * * echo link_to_styled('Events', 'event/list', 'class=active module_only=true tag=li', 'id=event_link'); * if the current module matches the module present in the route, then: * => <li class="active"><a href="path/to/event/list/action" id="event_link">Events</a></li> * </code> * * @param string name of the link, i.e. string to appear between the <a> tags * @param string 'module/action' or '@rule' of the action * @param array additional style options * @param array additional HTML compliant <a> tag parameters * @return string XHTML compliant <a href> tag * @see link_to */ function link_to_styled($text, $route = '', $style_options = '', $options = '') { static $context, $current_module_name; $options = _parse_attributes($options); $style_options = _parse_attributes($style_options); if (!isset($style_options['class'])) { $style_options['class'] = 'current'; } if (isset($style_options['module_only'])) { if (!isset($context)) { $context = sfContext::getInstance(); } if (!isset($current_module_name)) { $current_module_name = $context->getModuleName(); } list($current_route_name, $params) = $context->getController()->convertUrlStringToParameters($route); $is_current = $params['module'] == $current_module_name; } else { $current_route = sfRouting::getInstance()->getCurrentInternalUri(); $is_current = $current_route == $route; } if (isset($style_options['tag'])) { $tag_options = (true === $is_current) ? array('class'=>$style_options['class']) : array(); $return_string = content_tag($style_options['tag'], link_to($text, $route, $options), $tag_options); } else { if (true === $is_current) { $options['class'] = isset($options['class']) ? $options['class'] . ' ' . $style_options['class'] : $style_options['class']; } $return_string = link_to($text, $route, $options); } return $return_string; }
and in the template:
<?php use_helper('Style') ?> <ul id="navlist"> <!-- highlight this link if we are within the "events" module --> <li><?php echo link_to_styled('My Events', 'events/list', 'module_only=true') ?></li> </ul> <!-- or you could add the style to the li tag --> <?php echo link_to_styled('Event List', 'events/list', 'tag=li class=active') ?></li>
http://www.symfony-project.com/trac/ticket/540
Helper to work with http://tooltip.crtx.org Tooltip.js library (small js library that is using prototype and script.aculo.us, and so is the perfect match for symfony).
You will need to put that file into /helper/ directory, and put Tooltip.js (which you can get at the above-mentioned url) to the /sf/js/prototype/ directory.
Example of usage:
<?php use_helper('Tooltip') ?> <?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?> <div id="some_id">some text</div> <?php echo tooltip_div('some_id', 'css_class', array('style' => 'visibility: hidden'))?> Tooltip text. </div>
Code is "phpdocumentor ready".
TooltipHelper.php as follows
<?php require_once(sfConfig::get('sf_symfony_lib_dir').'/helper/JavascriptHelper.php'); /* * This file is part of the symfony package. * (c) 2006 Dmitry Parnas <parnas@rock.zp.ua> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ /** * TooltipsHelper. * * @package symfony * @subpackage helper * @author Dmitry Parnas <parnas@rock.zp.ua> * @version */ /* Helpers to work with Tooltip.js [http://tooltip.crtx.org] */ /** * Builds open div tag ready with inforamtion for Tooltip.js * * Example: * <code> * <?php echo tooltip_div('my_element_id', 'css_class_for_it', array('style' => 'visibility: hidden')) ?> * </code> * * @param string HTML id of the element this tooltip is for * @param css_class CSS class to skin tooltip in (if not default) * @param array Other attributes for the tag. You can also pass string suitable for _parse_attributes() * * @return string An HTML div string * */ function tooltip_div($element_id, $css_class = '', $options = array()) { $response = sfContext::getInstance()->getResponse(); $response->addJavascript('/sf/js/prototype/prototype'); $response->addJavascript('/sf/js/prototype/effects'); $response->addJavascript('/sf/js/prototype/Tooltip'); $options = _parse_attributes($options); $options['class'] = $css_class.' tooltip for_'.$element_id; return tag('div', $options, true); } /** * Builds script that sets optional settings for tooltips on the page. * * Example: * <code> * <?php echo tooltips_js('autoMoveToCursor=false showEvent=click', 'appear=true blindDown=true', 'blindUp=true', array('style.position' => 'absolute'), array('style.position' => 'absolute')) ?> * </code> * * @param array Tooltip.js settings. You can also pass string suitable for _parse_attributes() * @param array Ordered list of script.aculo.us effects you want to apply during tooltip show process. You can also pass string suitable for _parse_attributes() * @param array Ordered list of script.aculo.us effects you want to apply during tooltip hide process. You can also pass string suitable for _parse_attributes() * @param array Other settings for tooltip show process. You can also pass string suitable for _parse_attributes() * @param array Other settings for tooltip hide process. You can also pass string suitable for _parse_attributes() * * @return string An HTML script string. * */ function tooltips_js($options = array(), $show_effects = array(), $hide_effects = array(), $show_options = array(), $hide_options = array()) { $response = sfContext::getInstance()->getResponse(); $response->addJavascript('/sf/js/prototype/prototype'); $response->addJavascript('/sf/js/prototype/effects'); $response->addJavascript('/sf/js/prototype/Tooltip'); $options = _parse_attributes($options); $show_effects = _parse_attributes($show_effects); $hide_effects = _parse_attributes($hide_effects); $show_options = _parse_attributes($show_options); $hide_options = _parse_attributes($hide_options); $code = ''; foreach($options as $key => $value) { $value = _method_option_to_s($value); $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value; $code .= ' Tooltip.'.$key.' = '.$value."\n"; } $code .= _build_functions('show', $show_effects, $show_options); $code .= _build_functions('hide', $hide_effects, $hide_options); return javascript_tag($code); } function _build_functions($type, $effects = array(), $options = array()) { $code = ''; if($effects) // there is no reason to build showMethod if there is no effects defined { $code .= ' Tooltip.'.$type.'Method = function (tooltip, options)'."\n"; $code .= ' {'."\n"; foreach($effects as $key => $value) { $code .= ' Effect.'.ucfirst($key).'(tooltip, options)'."\n"; } if($options) { foreach($options as $key => $value) { $value = _method_option_to_s($value); $value = (is_bool($value)) ? ($value === false) ? 'false' : 'true' : $value; $code .= ' tooltip.'.$key.' = '.$value."\n"; } } $code .= ' }'."\n"; return $code; } } ?>
Gravatar has moved as a plugin with local caching functions see : http://trac.symfony-project.com/trac/wiki/sfGravatarPlugin
A common modification to a CRUD module is filtering records on the list page. There are probably several nice ways to do this, including drop-downs, forms, etc. depending on the situation. While some of these approaches are nice, they are often not minimal... We like minimal!
Say you have a book table, with a related author and genre. You want to filter the list of books by those related tables with links, paginate your list, and make it sortable via links in the header of the table.
To do this traditionally, you'll need lots of nasty little if statements in your template code, to check for all of these parameters (author_id, genre_id, title, num_pages, page, etc.) and combine them all together into a meaningful uri for your link_to function (something like "book/list?author_id=10&genre_id=5&sort=title&page=5"). We still want that link in the end, but generating it... How about a better way!
<!-- listSuccess.php --> <?php use_helpers('Filter', 'Pagination') ?> <h2>Books</h2> <h3>Filter By</h3> <table class="filters"> <tbody> <tr> <th>Author:</th> <td><?php echo filter_navigation(objects_for_filter($authors), 'author_id', $author_id) ?></td> </tr> <tr> <th>Genre:</th> <td><?php echo filter_navigation(objects_for_filter($genres), 'genre_id', $genre_id) ?></td> </tr> </tbody> </table> <hr /> <table class="list"> <thead> <tr> <th><?php link_to_unless($sort == 'id', 'Id', filter_url('sort', 'id')) ?></th> <th><?php link_to_unless($sort == 'title', 'Title', filter_url('sort', 'title')) ?></th> <th><?php link_to_unless($sort == 'author_id', 'Author', filter_url('sort', 'author_id')) ?></th> <th><?php link_to_unless($sort == 'genre_id', 'Genre', filter_url('sort', 'genre_id')) ?></th> </tr> </thead> <tbody> <?php foreach ($pager->getResults() as $book): ?> <tr> <td><?php echo $book->getId() ?></td> <td><?php echo $book->getTitle() ?></td> <td><?php echo $book->getAuthor()->getName() ?></td> <td><?php echo $book->getGenre()->getName() ?></td> </tr> <?php endforeach; ?> </tbody> </table> <?php echo pager_navigation($pager) ?>
NOTE: pager_navigation is covered at http://www.symfony-project.com/snippets/snippet/4, but needs to guess the uri, which is done at http://www.symfony-project.com/snippets/snippet/59.
NOTE: filter_navigation returns an unordered list, so you'll need css to display the <li> tags inline, and remove <ul> padding, margin, etc.
I'll leave it as an exercise to the reader to create the controller (action) code for this template, but it should be obvious.
<?php /** * Generate a url using the current internal uri, but replaces a param with a new value. * If the param is not in the current uri's query string, it is added instead. * * This is useful for a page that uses several filters to record sets, * and needs all the filters to work together, instead of blasting each * other away when a new link is clicked. * * <strong>Examples:</strong> * <code> * // with current uri => mymodule/myaction?author=10&genre=3 * * echo link_to('new author', filter_url('author', 5)); * // uri when clicked => mymodule/myaction?author=5&genre=3 * * echo link_to('new genre', filter_url('genre', 1)); * // uri when clicked => mymodule/myaction?author=10&genre=1 * * // with current uri => mymodule/myaction * * echo link_to('an author', filter_url('author', 10)); * // uri when clicked => mymodule/myaction?author=10 * </code> * * @param string the name of the parameter to replace * @param string the value to replace the current value with * @param boolean use route name * @return string the url with the parameter replaced * @see link_to */ function filter_url($param, $new_value, $with_route_name = false) { // fetch params from query string $params = _get_params(_get_query_string()); // replace param with new value $params[$param] = $new_value; return _get_uri($with_route_name) . '?' . _build_query_string($params); } /** * Removes a parameter from the current uri and returns the resulting url. * * @see filter_url */ function remove_filter_url($param, $with_route_name = false) { // fetch params from query string $params = _get_params(_get_query_string()); // remove param unset($params[$param]); return _get_uri($with_route_name) . '?' . _build_query_string($params); } /** * Generates an unordered list of links to filter the current record set by. * Multiple sets of filter_navigation links will work together, using the current uri. * * <strong>Examples:</strong> * <code> * echo filter_navigation(array(10=>'Jones', 12=>'Smith, J.', 13=>'Darby'), 'author_id', 13); * echo filter_navigation(objects_for_filter($authors), 'author_id', 13); * </code> * * @param array list of key=>value pairs of ids and strings * @param string the name of the parameter for this filter * @param string the selected id (or null, if none selected) * @param string the text to use for the "all" link * @see filter_url */ function filter_navigation($list, $param, $selected = null, $all_text = 'All') { $html = ''; $html .= content_tag('li', link_to_unless($selected === null, $all_text, remove_filter_url($param))); foreach ($list as $key => $value) { $html .= content_tag('li', link_to_unless($selected == $key, $value, filter_url($param, $key))); } return content_tag('ul', $html); } /** * Generates a simple list from a record set of propel objects. * Expects a getId function and a toString function. * * @param array objects to be converted to a list * @see filter_navigation */ function objects_for_filter($objects) { $list = array(); foreach ($objects as $object) { $list[$object->getId()] = $object->toString(); } return $list; } function _get_uri($with_route_name = false) { $internal_uri = sfRouting::getInstance()->getCurrentInternalUri($with_route_name); $ar = explode('?', $internal_uri); return ($with_route_name ? '@' : '') . $ar[0]; } function _get_query_string() { $internal_uri = sfRouting::getInstance()->getCurrentInternalUri(); $ar = explode('?', $internal_uri); return isset($ar[1]) ? $ar[1] : ''; } function _get_params($query_string) { // parse query string into associative array $params = array(); if ($query_string != '') { foreach (explode('&', $query_string) as $kvpair) { list($key, $value) = explode('=', $kvpair); $params[$key] = $value; } } return $params; } function _build_query_string($params) { // build list of key=value strings $ar = array(); foreach ($params as $key => $value) { $ar[] = $key . '=' . $value; } return implode('&', $ar); }
NOTE: Place this in apps/myapp/lib/helper/FilterHelper.php.
<?php echo link_to('new author', filter_url('author_id', 10)) ?> <?php echo url_for(filter_url('author_id', 10)) ?> <?php echo link_to_if($condition, 'new author', filter_url('author_id', 10)) ?> <?php echo link_to_unless($condition, 'new author', filter_url('author_id', 10)) ?> <?php echo button_to('new author', filter_url('author_id', 10)) ?>
<?php /** * Shortcut combining link_to and filter_url into single function. * * @see link_to * @see filter_url */ function link_to_filter($name, $param, $new_value, $options = array()) { return link_to($name, filter_url($param, $new_value), $options); } /** * Shortcut combining url_for and filter_url into single function. * * @see url_for * @see filter_url */ function filter_url_for($param, $new_value) { return url_for(filter_url($param, $new_value)); } /** * Shortcut combining link_to_if and filter_url into single function. * * @see link_to_if * @see filter_url */ function link_to_filter_if($condition, $name, $param, $new_value, $options = array()) { return link_to_if($condition, $name, filter_url($param, $new_value), $options); } /** * Shortcut combining link_to_unless and filter_url into single function. * * @see link_to_unless * @see filter_url */ function link_to_filter_unless($condition, $name, $param, $new_value, $options = array()) { return link_to_unless($condition, $name, filter_url($param, $new_value), $options); } /** * Shortcut combining button_to and filter_url into single function. * * @see button_to * @see filter_url */ function button_to_filter($name, $param, $new_value, $options = array()) { return button_to($name, filter_url($param, $new_value), $options); }
The problem seems to arise quite frequently: a page contains one Ajax link, but the remote function must update several divs on the page. Consider, for instance, the following template:
<h1>First zone to update</h1> <div id="first_zone"> Hello there </div> <h1>Second zone to update</h1> <div id="second_zone"> <p>How do you do, <strong>mate</strong>? </div> <h1>Ajax link</h1> <?php echo use_helper('Javascript') ?> <?php echo link_to_remote('click me', array( 'url' => 'test/ajax', 'update' => 'result', 'script' => true, )) ?> <div id="result"> </div>
What would the test/ajax action look like to update both the first_zone and the second_zone?
For the code of the action itself (executeAjax()), we'll ignore it since it really depends on what logic you put in your Ajax interaction. For this example, it will be empty.
The code of the template (ajaxSuccess.php) can be as follows:
<?php echo use_helper('Javascript') ?> <?php slot('first_update') ?> So you like clicking, uh? <?php end_slot() ?> <?php slot('second_update') ?> <p>I'd like to test quotes (like "). </p> <p>And <strong>tags</strong>, too.</p> <?php end_slot() ?> <?php echo javascript_tag( update_element_function('first_zone', array( 'content' => get_slot('first_update'), )) . update_element_function('second_zone', array( 'content' => get_slot('second_update'), )) ) ?>
Once rendered, the HTML code sent