ProgressBar для CakePHP в стиле WinRAR

Я так люблю CakePHP, что часто мучаю его так, как и не догадывались его создатели. Некоторые задачи у меня выполняются по несколько часов так как приходится обрабатывать огромные таблицы. При этом возникают проблемы - Request Time-out, который появляется, если долго ничего не выдавать браузеру. Я раньше выводил точки, потом номер обрабатываемой записи. Для того, чтобы просмотреть, сколько уже сделано, надо было скроллить в самый низ, который постоянно исчезал.

Сегодня мне это наконец надоело и я сделал привычный ProgressBar.

Хочу, чтобы можно было писать

PHP:
  1. $countCustomers = count($customers);
  2. for ($i=0; $i<$countCustomers; $i++) {
  3.     $this->jsProgressBar($i/$countCustomers);
  4.  
  5.     $this->Customer->process($customers[$i]);
  6. }

и мне выводился красивый ProgressBar.
loading2.gif
Обратите внимание на "Loading..." сверху. Это показывает, что страница ещё грузится.

А ещё чтобы можно было писать

PHP:
  1. $this->jsProgressBar($i/$countCustomers, sprintf('Customer %s/%s', $i+1, $countCustomers));

и под ProgressBar выводилось что-то вроде "Customer 17/83".

А чтобы совсем отбить желание что-то хотеть, так чтобы можно было выводить несколько ProgressBar

PHP:
  1. $countCustomers = count($customers);
  2. for ($i=0; $i<$countCustomers; $i++) {
  3.     $this->jsProgressBar($i/$countCustomers, sprintf('Customer %s/%s', $i+1, $countCustomers));
  4.  
  5.     $orders = $this->Customer->orders($customers[$i]);
  6.     $countOrders = count($orders);
  7.    
  8.     for ($j=0; $j<$countOrders; $j++) {
  9.         $this->jsProgressBar($i/$countOrders, sprintf('Order %s/%s', $j+1, $countOrders), 'orders');
  10.         $this->Customer->Order->process($orders[$j]);
  11.     }
  12. }

Примерно так:
loading.gif

Чтобы добиться такой красоты, добавьте функцию в app_controller.php

PHP:
  1. /**
  2. * Shows and updates progress bar for long-processing actions
  3. *
  4. * @param float $ratio 0<ratio<1; 0=0%, 1=100%
  5. * @param string $status optional. If you have something to show under progress bar
  6. * @param string $name name of the progress bar if you need more than one
  7. */
  8. function jsProgressBar($ratio=0, $status=null, $name='default') {
  9.     static $inited = array();
  10.     $percents = round(100*$ratio);
  11.  
  12.     // common for all progress bars
  13.     if (empty($inited)) {
  14.         e('
  15.             <style>
  16.                 div.progressbar {
  17.                     border:1px solid #06d;
  18.                     width: 75%
  19.                 }
  20.                 div.progressbar div {
  21.                     height:1.2em;
  22.                     line-height:1.2em;
  23.                     background-color:#03a;
  24.                     border-right:1px solid #03a;
  25.                     text-align:right; color:#fff;
  26.                     width:0;
  27.                     overflow:hidden;
  28.                     padding-right: 5px
  29.                 }
  30.             </style>
  31.             <body>
  32.         ');
  33.     }
  34.  
  35.     // init current progress bar if needed
  36.     if (!in_array($name, $inited)) {
  37.         e('
  38.             <div class="progressbar"><div id="progressbar-'.$name.'"></div></div>
  39.             <div class="statusbar" id="statusbar-'.$name.'"></div>
  40.         ');
  41.         flush();ob_flush();
  42.  
  43.         $inited[] = $name;
  44.     }
  45.  
  46.     // set current position
  47.     if ($status===null) {
  48.         $statusScript = '';
  49.     } else {
  50.         $statusScript = 'document.getElementById("statusbar-'.$name.'").innerHTML = "'.$status.'";';
  51.     }
  52.  
  53.     e('
  54.         <script>
  55.             var progress = document.getElementById("progressbar-'.$name.'");
  56.             var percent = '.$percents.'+"%";
  57.             progress.innerHTML = percent;
  58.             progress.style.width = percent;
  59.             '.$statusScript.'
  60.         </script>
  61.     ');
  62.     flush();ob_flush();
  63. }

Идея в том, чтобы выводить скрипт, который будет менять значение и форматирование определённого div.

Кстати, если вывести какой-то текст, то $this->redirect в контроллере уже не будет работать как и все проявления header() вроде $this->Session->setFlash(). Поэтому, если нужен setFlash, то делайте его до jsProgressBar.

А чтобы сделать redirect, я написал ещё одну маленькую приятность.

PHP:
  1. function jsRedirect($url) {
  2.     printf('<script>window.location="%s"</script>', Router::url($url, true));
  3.     exit;
  4. }

URL в неё можно передавать в том же формате, как и для обычного $this->redirect, то есть подходит как '/customers/', так и array('action'=>'index'). В конце вместо

PHP:
  1. $this->redirect('/', null, true);

пишите

PHP:
  1. $this->jsRedirect(array('action'=>'index'));


Понравилось?

  1. Подпишись через RSS
  2. Расскажи о http://php.southpark.com.ua друзьям.
    Все способы хороши: ICQ, E-mail, свой блог, комментарий в чужом блоге или сообщение на форуме
  3. Добавь статью на news2.ru, Хабрахабр или в закладки

Огромное спасибо!

И не стесняйтесь комментировать - у меня стоит плагин, который убирает rel="nofollow" у людей, которые написали больше 5 комментариев.

RSS feed | Trackback URI

5 комментариев »

2007-12-02 21:27:40

Спасибо огромное. Добавьте пожалуйста на http://cake-php.ru/wiki/

 
Comment by Сергей Subscribed to comments via email
2007-12-08 23:15:03

Полезная вещь! Спасибо. :-)

 
Comment by Павел Subscribed to comments via email
2007-12-17 23:31:48

Вещь оч хорошая, все бы хорошо, кроме одного - трудно представить куда что вставлять для обработки записей из бд. Т.к я как начинающий и только осваиваю cakephp (1 вечер) - не понимаю какие файлы надо создавать, что в них писать и как использовать(модели, view, контроллеры). Делал стандартный пример с блогом - получилось, а вот с процессом вашим пока не очень понимаю, может распишете, на почту, поподробней? Буду благодарен в двойне))))

 
2008-02-04 14:37:19

[...] 2. Проблемы с долгими скриптами Известная ошибка Request timeout возникает, если не передавать никаких данных в браузер долгое время (вроде 30 секунд). Решается тем, что периодически выводится какой-то текст. [...]

 
Comment by V0id Subscribed to comments via email
2008-07-02 22:46:25

Спасибо, полезная и интерестная статья.. Надо будет как-то испробовать, и поиздеваться над кодом маленько)
Я когда-то сталкивалкивался с такой проблемой.. Нужно было крон-скрипт написать, для обработки данных в БД. Когда тестил пользовался браузером. Есть и другое решение..

Например:
set_time_limit(0); // отключаем лимит обработки скрипта
ignore_user_abort(false); // должно ли отсоединение клиента вызывать прерывание выполнения скрипта
ini_set('output_buffering','Off'); // и собственно та нужная нам функция

ini_set('output_buffering','Off') - ею мы говорим, чтобы PHP не буферизовал данные, а сразу выдавал их браузеру. Правда не все хостинги позволяют это.
Да, чуть не забыл, output_buffering = off в php.ini лечит эту проблему

ЗЫ: если где ошибся - поправте =)

 
Имя (required)
E-mail (required - never shown publicly)
URL
Текст комментария
You may use <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> in your comment.