![]() |
|
Snippets |
|
One link, multiple ajax div updates, the ultimate solution
I already proposed a solution in another snippet, but it seems I missed the simplest solution.
Let's summarize the probkem again: 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>
We want an ajax link to update the two zones. The solution is not to use the Ajax helpers, but rather call the prototype Ajax object directly:
<?php echo use_helper('Javascript') ?> <h1>Ajax link</h1> <?php echo link_to_function('click me', 'new Ajax.Request(\''.url_for('test/ajax').'\');return false')) ?>
The code of the action itself (executeAjax()) does some server stuff to prepare the data used to update the template. 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) just needs to be as follows:
<?php $sf_context->getResponse()->setContentType('text/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() ?> Element.update('first_zone', '<?php include_slot('first_update') ?>'); Element.update('second_zone', '<?php include_slot('second_update') ?>');
And that's it. Because the response content type is text/javascript, the Ajax object will eval it automatically (no need to mention evalScripts: true anymore).
And this will do exactly what we wanted: update both zones with different content, in a single remote call. The interest of using the slot helpers is that you don't need to worry about escaping the content passed to the JavaScript function, and it looks really nice in your favorite syntax-highlighting text editor.
It can be a hassle to write a package.yml for a plugin if it contains many files. Fortunately, with the help of the sfFinder class and a bit of programming, you can automate the generation of the <content> part in a batch.
Create a batch/package_generator.php with the following code:
<?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'frontend'); define('SF_ENVIRONMENT', 'prod'); define('SF_DEBUG', false); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); $line = array(); $lines = array(); $previous_dirs = array(); $files = sfFinder::type('file')->relative()->in('path/to/plugin/files'); // Prepare the list of files for nice hierarchy foreach($files as $file) { $file = str_replace('\\', '/', $file); $dirs = explode ('/', $file); $line['filename'] = array_pop($dirs); $temp_dirs = $dirs; foreach($dirs as $dir) { if($previous_dirs && $dir == $previous_dirs[0]) { array_shift($previous_dirs); array_shift($dirs); } } foreach($dirs as $dir) { $line['open'][] = $dir; } foreach($previous_dirs as $dir) { $line['close'][] = $dir; } $lines[] = $line; $line = array(); $previous_dirs = $temp_dirs; } $blanks = 0; ?> <?php foreach($lines as $line): ?> <?php if(isset($line['close'])): ?> <?php foreach($line['close'] as $dir): ?> <?php echo str_repeat(' ', --$blanks) ?></dir> <?php endforeach; ?> <?php endif; ?> <?php if(isset($line['open'])): ?> <?php foreach($line['open'] as $dir): $counter++ ?> <?php echo str_repeat(' ', $blanks++) ?><dir name="<?php echo $dir ?>"> <?php endforeach; ?> <?php endif; ?> <?php echo str_repeat(' ', $blanks) ?><file name="<?php echo str_replace('\\', '/', $line['filename']) ?>" role="data"/> <?php endforeach; ?>
Just run it with
> php batch/package_generator.php
and paste the output in your package.xml. That's all.
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 to the user will look like this:
<script type="text/javascript"> //<![CDATA[ $('first_zone').innerHTML = ' So you like clicking, uh?\n'; $('second_zone').innerHTML = ' <p>I\'d like to test quotes (like \"). </p>\n <p>And <strong>tags</strong>, too.</p>\n'; //]]> </script>
And this will do exactly what we wanted: update both zones with different content, in a single remote call. The interest of using the slot helpers is that you don't need to worry about escaping the content passed to the JavaScript function, and it looks really nice in your favorite syntax-highlighting text editor.
If you happen to do this a lot, maybe you will want to package the multiple updater into a helper. That's quite easy:
function update_elements_function($updates) { $res = ""; foreach($updates as $zoneName => $slotName) { $res .= update_element_function($zoneName, array('content' => get_slot($slotName))); } return javascript_tag($res); }
Then you could replace the call to the javascript_tag() in the ajaxSuccess.php template by a simpler:
<?php echo update_elements_function(array( 'first_zone' => 'first_update', 'second_zone' => 'second_update', )) ?>
Performancewise, if the Ajax response is small (below 512 Bytes), you'd better use the JSON approach.
Some applications want to offer various look-and-feels for each page. The online documentation already explains how to change the CSS from the server, and a bit of JavaScript will allow you to do it from the client side.
But sometimes this is not enough, and the need arises to have several sets of templates for each action. Something like:
mymodule/
templates/
indexSuccess.php # Template for index action, default theme
theme1/
indexSuccess.php # Template for index action, theme1
theme2/
indexSuccess.php # Template for index action, theme2
Thanks to the MVC architecture, this is very easy to do.
We'll suppose that theme is an attribute of the sfUser object. It contains a simple string. The way to determine the preferred theme for a given user will be left to your sagacity, as well as the way to extend the sfUser class to deliver this attribute. The interesting thing is to modify the view class to have it use the theme when the time comes to look for templates and layouts.
Create a myView.class.php file in the lib/ directory of your application, and write in:
<?php class myView extends sfPHPView { public function configure() { parent::configure(); // Grab the theme from the user (of from anywhere else) $theme = $this->getContext()->getUser()->getTheme(); // If there is a theme and if the theme feature is enabled if($theme && sfConfig::get('app_theme')) { // Look for templates in a $theme/ subdirectory of the usual template location if (is_readable($this->getDirectory().'/'.$theme.'/'.$this->getTemplate())) { $this->setDirectory($this->getDirectory().'/'.$theme); } // Look for a layout in a $theme/ subdirectory of the usual layout location if (is_readable($this->getDecoratorDirectory().'/'.$theme.'/'.$this->getDecoratorTemplate())) { $this->setDecoratorDirectory($this->getDecoratorDirectory().'/'.$theme); } } } }
To force symfony to use this view class instead of the default sfPHPView, create a module.yml in the application's config/ directory, and write in it:
all: view_class: my
The new view will look for themes only if you enabled the feature in your app.yml:
all: theme: on
Clear the cache, and the theme feature is ready.
It works like this: when a user has a defined theme, symfony will look for templates and layouts in a subdirectory named by this theme. For instance, if a user has a foobar theme, and that it requests the mymodule/myaction action, symfony will look for the template in:
apps/myapp/modules/mymodule/templates/foobar/myActionSuccess.php
and for the layout in:
apps/myapp/templates/foobar/layout.php
The beauty of this modification is that if the themed template doesn't exist, symfony will use the normal template as a fallback.
It must be pretty easy to package this into a plugin, so if you feel like doing it...
As enum is a MySQL specific type, you cannot have a column of type enum in your schema.yml (which is database-independent). One solution to simulate it is to create another table to store the different possible values. But the Model class offer an alternative solution that is probably more elegant.
Imagine you have a status column in you article table you want to be an enumerated list with values like (open, closed). Simply declare the status column as integer and then modify the model as follows:
In ArticlePeer.php:
class ArticlePeer... { static protected $STATUS_INTEGERS = array('open', 'closed'); static protected $STATUS_VALUES = null; static public function getStatusFromIndex($index) { return self::$STATUS_INTEGERS[$index]; } static public function getStatusFromValue($value) { if (!self::$STATUS_VALUES) { self::STATUS_VALUES = array_flip(self::$STATUS_INTEGERS); } $values = strtolower($value); if (!isset(self::STATUS_VALUES[$value]) { throw new PropelException(sprintf('Status cannot take "%s" as a value', $value)); } return self::STATUS_VALUES[strtolower($value)]; } }
In Article.php:
class Article { public function setStatusName($value) { $this->setStatus(ArticlePeer::getStatusFromValue($value)); } public function getStatusName() { return ArticlePeer::getStatusFromIndex($this->getStatus()); } }
(Original tip from Fabien)
The example is for a blog. The page that displays a post also proposes an AJAX form to add a comment. We want that when the validation of this form fails, it displays again in the page with an error message, and when the validation succeeds, the form is replaced byu the comment just posted.
The idea is to take advantage of the way the update option of the form_remote_tag() helper works. It accepts an associative array, where you can specify different zones to update in case of success and failure. The only problem is that for Prototype, a failure is a return code other than 2XX. So when we return the form showing the error message again, we need to set the status code to 404, for instance, for Prototype to choose to update the correct zone.
That, plus the usual use of partials here and there, and you have a working solution:
in modules/post/actions/action.class.php
// Display the form public function executeShow() { $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id')); }
in modules/post/templates/showSuccess.php
// Display question detail here ... // Beginning of Comment zone <div id="added_comment" style="display_none"> </div> <div id="add_comment"> <?php include_partial('comment/add_comment', array('post' => $post)) ?> </div>
in modules/comment/templates/_add_comment.php
<?php use_helper('Javascript', 'Validation') ?> <?php echo form_remote_tag(array( 'url' => 'comment/add', 'update' => array('success' => 'added_comment', 'failure' => 'add_comment'), 'script' => true, 'loading' => "Element.show('indicator')", 'success' => "Element.hide('indicator');Element.show('added_comment');Element.hide('add_comment');", 'failure' => "Element.hide('indicator');", )) ?> <?php echo input_hidden_tag('post_id', $post->getId()) ?> <?php echo form_error('body') ?> <label for="body">Your comment</label> <?php echo textarea_tag('body') ?> <?php echo submit_tag('Send') ?> </form>
in modules/comment/validate/add.yml
methods: post: [body] fillin: activate: Yes names: body: required: Yes required_msg: You must provide a comment validators: spamValidator spamValidator: class: sfRegexValidator param: match: No pattern: /http.*http/ match_error: Do not provide more than one URL - It is considered Spam
in modules/comment/actions/action.class.php
public function handleErrorAdd() { $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id')); $this->getResponse()->setStatusCode(404); return sfView::ERROR; } public function executeAdd() { $post = PostPeer::retrieveByPk($this->getRequestParameter('post_id')); $this->forward404Unless($post); $comment = new Comment(); $comment->setPost($post); $comment->setAuthor($this->getUser()->getAuthor()); $comment->setBody($this->getRequestParameter('body')); $comment->save(); $this->comment = $comment; }
in modules/comment/templates/addError.php
<?php include_partial('comment/add_comment', array('post' => $post)) ?>
in modules/comment/templates/addSuccess.php
Your comment has been added: <div class="comment"> <?php echo $comment->getBody() ?> </div>
As a bonus, the form is still there after a successful submission (but hidden), so with a few more lines of code, you can still provide a Digg-like "edit comment for the next 60 seconds" feature.
This is a bit of a hack, but at least it makes it possible to clear the cache across applications
sfToolkit::clearDirectory( sfConfig::get('sf_root_dir').'/cache/'.$app_name.'/templates/path/to/page/in/cache' );
It is not using an internal URI, of course, since for now an application cannot know another application's routing rules.
If you don't include any path to a page in cache, the whole template cache is cleared.
The problem is simple. I have a booking system for appartments (or whatever else). Reservations are stored in a Reservation table, and have a begin_date and an end_date column.
| Appartment | Reservation |
|---|---|
id |
id |
| ... | appartment_id |
begin_date |
|
end_date |
|
| ... |
I want to find the existing reservations for an appartment between two dates.
The data I have is $appartment_id, $begin_date and $end_date. I want reservations starting or ending between the two dates, or starting before the $begin_date and ending after the $end_date. That's how it would be translated into a SQL WHERE:
reservation.APPARTMENT_ID = $appartment_id AND (((reservation.START_DATE > $begin_date AND reservation.START_DATE < $end_date) OR (reservation.END_DATE > $begin_date AND reservation.END_DATE < $end_date)) OR (reservation.END_DATE > $end_date AND reservation.START_DATE < $begin_date))
Of course, I'd prefer to use Propel for that. Is it tricky? Not that much.
class Appartment extends BaseAppartment { public function findReservations($begin_date, $end_date) { $c = new Criteria(); $c->add(ReservationPeer::APPARTMENT_ID, $this->getId()); // Find reservations beginning between the search period $criterion1 = $c->getNewCriterion( ReservationPeer::START_DATE, $begin_date, Criteria::GREATER_THAN )->addAnd($c->getNewCriterion( ReservationPeer::START_DATE, $end_date, Criteria::LESS_THAN )); // Find reservations ending between the search period $criterion2 = $c->getNewCriterion( ReservationPeer::END_DATE, $begin_date, Criteria::GREATER_THAN )->addAnd($c->getNewCriterion( ReservationPeer::END_DATE, $end_date, Criteria::LESS_THAN )); // Find reservations beginning before the search period and ending after $criterion3 = $c->getNewCriterion( ReservationPeer::END_DATE, $end_date, Criteria::GREATER_THAN )->addAnd($c->getNewCriterion( ReservationPeer::START_DATE, $begin_date, Criteria::LESS_THAN )); // Combine all that with a OR $c->add($criterion1->addOr($criterion2)->addOr($criterion3)); return = ReservationPeer::doSelect($c); } }
If you are dealing with i18n and client side effects, you might need an equivalent for the format_number_choice() helper in Javascript. Here is a simplified version:
function format_number_choice(text_string, replace_hash, number) { pattern = new RegExp("\\["+number+"\\]\s?([^\|]*)"); function replace_in_string(text_string, replace_hash) { for(var i in replace_hash) text_string = text_string.replace(new RegExp(i), replace_hash[i]); return text_string; } matches=text_string.match(pattern); if(matches != null) return replace_in_string(matches[1], replace_hash); else { pattern = /\[else\]\s?([^\|]*)/; matches=text_string.match(pattern); if(matches != null) return replace_in_string(matches[1], replace_hash); else return "not found"; } }
Use it in your HTML code as follows:
<script language="JavaScript" type="text/javascript"> document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", {'Apple' : 'fruit'}, 0)) document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 1)) document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 2)) document.write(format_number_choice("[0] No apple |[1] One Apple |[2] Two Apples |[else] Many Apples", [], 3)) </script>
This will output:
No fruit One Apple Two Apples Many Apples
Now, just use enclose the first argument with the __() helper, and you have an internationalized format_number_choice() on the client side.
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)
Imagine that you have a table with the following columns:
MyTable ------- id col1 col2
If you want to retrieve the records having col1 greater than col2, the following doesn't work:
$c = new Criteria(); $c->add(MyTablePeer::COL1, MyTablePeer::COL2, Criteria::GREATER_THAN);
Instead, you need to add a custom condition to your Criteria:
$c = new Criteria(); $c->add(MyTablePeer::COL1, MyTablePeer::COL1.'>='.MyTablePeer::COL2, Criteria::CUSTOM);
When you need to update several records in a row, you don't have to loop over the result of a doSelect() call and do a save() for each object (which would make way too many queries).
Instead, you can use the BasePeer method doUpdate() as follows:
BasePeer::doUpdate($select_criteria, $update_criteria, $connection);
For instance:
$con = Propel::getConnection(); // select from... $c1 = new Criteria(); $c1->add(CommentPeer::POST_ID, $post_id); // update set $c2 = new Criteria(); $c2->add(CommentPeer::RATING, 5); BasePeer::doUpdate($c1, $c2, $con);
If course, if you are in a Peer class, you will need to use the self object:
$con = Propel::getConnection(); // select from... $c1 = new Criteria(); $c1->add(self::POST_ID, $post_id); // update set $c2 = new Criteria(); $c2->add(self::RATING, 5); BasePeer::doUpdate($c1, $c2, $con);
In an i18n application, if your template uses the input_date_tag() helper, the format of the date sent to the submit action will depend on the user culture. But then, how can you handle this date to, say, store it in a database in a culture independent format?
The answer lies in the sfI18N class:
$date= sfContext::getInstance()->getRequest()->getParameter('birth_date'); $user_culture = sfContext::getInstance()->getUser()->getCulture(); list($d, $m, $y) = sfI18N::getDateForCulture( $date, $user_culture );
Now you have the day, month and year of the date in the $d, $m and $y variables, and you can do whatever you want with them.
If you use a Propel date setter, you can even call it directly with:
$person->setBirthDate("$y-$m-$d");
The same applies for the sfI18N::getTimestampForCulture() method.
To modify the settings of a connection named 'propel':
$con = sfContext::getInstance()->getDatabaseConnection('propel'); $con->setConnectionParameter('username', 'foo'); $con->setConnectionParameter('password', 'bar');
This works for all the settings that can be defined in the databases.yml (more info in the sfPropelDatabase class source).
You must execute this code before the first query to the database. If you do that after a first query, it fails. This means that the best way to implement it is in a filter.
Say you look for the objects of class Foo being created between $from_date and $to_date. This should do the trick:
$c = new Criteria(); $criterion = $c->getNewCriterion(FooPeer::CREATED_AT , date('Y-m-d', $from_date), Criteria::GREATER_EQUAL ); $criterion->addAnd($c->getNewCriterion(FooPeer::CREATED_AT , date('Y-m-d', $to_date), Criteria::LESS_EQUAL )); $c->add($criterion); $shows = FooPeer::doSelect($c);
You can mimick the call of the symfony command line via a PHP script called from the browser. For instance, for the command
$ symfony clear-cache
...create a webclearcache.php file in your myproject/web/ directory (where the index.php is) with the following content:
<?php // as we are in the web/ dir, we need to go up one level to get to the project root chdir(dirname(__FILE__).DIRECTORY_SEPARATOR.'..'); include_once('/lib/symfony/pake/bin/pake.php'); $pake = pakeApp::get_instance(); try { $ret = $pake->run('/data/symfony/bin/pakefile.php', 'clear-cache'); } catch (pakeException $e) { print "<strong>ERROR</strong>: ".$e->getMessage(); } ?>
You can now clear the cache by calling:
http://myapp.example.com/webclearcache.php
Note: Beware that by letting web access to administration tools, you can compromise the safety of your website.
If you have not a PEAR symfony installation, but rather a couple of symlinks in the lib/ and data/ directories of your project, and if you didn't define shortcuts for the symfony command, you'll have to call manually pake (the tool used for the command line interface) and pass it the right pakefile (the configuration file containing the symfony commands).
This can be done that way:
$ cd /home/production/myproject
$ php /path/to/pake.php -f data/symfony/bin/pakefile.php propel-build-model
In systems where both PHP4 and PHP5 are installed, you probably need to use a PHP5 specific command:
$ php5 /usr/local/php5.1.2/lib/php/pake.php -f data/symfony/bin/pakefile.php propel-build-model
In the template where the object that users can vote on (here, a snippet) is displayed, add:
<?php echo use_helper('Javascript') ?> <?php $id = $snippet->getId() ?> <?php if($sf_user->canVoteFor($snippet)): ?> <span id="vote_for_<?php echo $id ?>" style="white-space:nowrap;"> rate this snippet: <?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $snippet->getAverageVote()): ?>rated<?php endif; ?>" id="vote_<?php echo $id ?>_<?php echo $i ?>"><?php echo link_to_remote(image_tag('spacer.gif', 'width=10 height=10'), array( 'url' => 'snippet/voteForSnippet?id='.$id.'&vote='.$i, 'update' => 'vote_for_'.$id, 'loading' => visual_effect('appear', 'indicator'), 'complete' => visual_effect('fade', 'indicator').visual_effect('highlight', 'vote_for_'.$id), ), array( 'onMouseOver' => 'highlight_stars('.$id.', '.$i.', true);', 'onMouseOut' => 'highlight_stars('.$id.', '.$i.', false);', )) ?></div><?php endfor; ?> <span id="indicator" style="display:none"> <?php echo image_tag('indicator.gif') ?></span> </span> <?php else: ?> <?php include_partial('voted', array('id' => $id, 'vote' => $snippet->getAverageVote())) ?> <?php endif; ?>
Of course, you have to define the rules about who can vote on what in the ->canVoteFor() method of the User object (in apps/myapp/lib/myUser.php).
The template uses a _voted.php partial:
<?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $vote): ?>rated<?php endif; ?>" id="vote_<?php echo $id ?>_<?php echo $i ?>"><img src="/images/spacer.gif" width="10" height="10" /></div><?php endfor; ?>
DO NOT add extra spaces to the long lines, or the stars get separated by blank spaces.
The mechanism that changes the aspect of stars relies on classes, so add the following styling to a CSS used in your template:
.rate { display:inline; width:10px; height:10px; margin:0; padding:0; background-image:url(/images/vote_star.gif); background-position: left 10px; } .rated { background-position: left 0px; } .ratehover { background-position: left 20px; }
The vote_star.gif file is a 10x30px image containing three versions of the star: voted, hovered, not voted.

The snippet/voteForSnippet action does something like:
public function executeVoteForSnippet() { $this->id = $this->getRequestParameter('id'); $snippet = SnippetPeer::retrieveByPk($this->id); if(!$snippet) { return sfView::NONE; } $vote = new Vote(); $vote->setUserId($this->getUser()->getUserId()); $vote->setSnippetId($snippet->getId()); $vote->setVote($this->getRequestParameter('vote')); $vote->save(); $this->vote = $vote->getSnippet()->getAverageVote(); }
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') ?>
Create a batch/load_data.php script with:
<?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..')); define('SF_APP', 'frontend'); define('SF_ENVIRONMENT', 'dev'); define('SF_DEBUG', true); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); // initialize database manager $databaseManager = new sfDatabaseManager(); $databaseManager->initialize(); $data = new sfPropelData(); $data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures'); ?>
Call it with:
$ cd batch
$ php load_data.php
It will load the data contained in all the .yml files of the data/fixtures/ directory.