PageRenderTime 45ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/atk4/lib/Grid/Basic.php

https://github.com/git86/todo
PHP | 870 lines | 668 code | 53 blank | 149 comment | 72 complexity | 4ab0719097f48488526be4b479647c1d MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php // vim:ts=4:sw=4:et:fdm=marker
  2. /**
  3. * This is a Basic Grid implementation, which produces fully
  4. * functional HTML grid capable of filtering, sorting, paginating
  5. * and using multiple column formatters.
  6. *
  7. * @link http://agiletoolkit.org/doc/grid
  8. *//*
  9. ==ATK4===================================================
  10. This file is part of Agile Toolkit 4
  11. http://agiletoolkit.org/
  12. (c) 2008-2011 Romans Malinovskis <atk@agiletech.ie>
  13. Distributed under Affero General Public License v3
  14. See http://agiletoolkit.org/about/license
  15. =====================================================ATK4=*/
  16. class Grid_Basic extends CompleteLister {
  17. public $columns;
  18. protected $no_records_message="No matching records to display";
  19. public $last_column;
  20. public $sortby='0';
  21. public $sortby_db=null;
  22. public $not_found=false;
  23. public $displayed_rows=0;
  24. private $totals_title_field=null;
  25. private $totals_title="";
  26. public $totals_t=null;
  27. public $totals_value_na = '-';
  28. public $data=null;
  29. /**
  30. * Inline related property
  31. * If true - TAB key submits row and activates next row
  32. */
  33. protected $tab_moves_down=false;
  34. /**
  35. * Inline related property
  36. * Wether or not to show submit line
  37. */
  38. protected $show_submit=true;
  39. private $record_order=null;
  40. public $title_col=array();
  41. /**
  42. * $tdparam property is an array with cell parameters specified in td tag.
  43. * This should be a hash: 'param_name'=>'param_value'
  44. * Following parameters treated and processed in a special way:
  45. * 1) 'style': nested array, style parameter. items of this nested array converted to a form of
  46. * style: style="param_name: param_value; param_name: param_value"
  47. * 2) OBSOLTE! wrap: possible values are true|false; if true, 'wrap' is added
  48. * use style/white-space property or simply format_wrap()
  49. *
  50. * All the rest are not checked and converted to a form of param_name="param_value"
  51. *
  52. * This is a tree-like array with the following structure:
  53. * array(
  54. * [level1]=>dataset_row=array(
  55. * [level2]=>field=array(
  56. * [level3]=>tdparam_elements=array(
  57. * param_name=>param_value
  58. * )
  59. * )
  60. * )
  61. * )
  62. */
  63. protected $tdparam=array();
  64. public $js_widget='ui.atk4_grid';
  65. public $js_widget_arguments=array();
  66. function init(){
  67. parent::init();
  68. //$this->add('Reloadable');
  69. $this->api->addHook('pre-render',array($this,'precacheTemplate'));
  70. $this->sortby=$this->learn('sortby',@$_GET[$this->name.'_sort']);
  71. }
  72. function defaultTemplate(){
  73. return array('grid','grid');
  74. }
  75. /**
  76. * Returns the ID value of the expanded content on the basis of GET parameters
  77. */
  78. function getExpanderId(){
  79. return $_GET['expanded'].'_expandedcontent_'.$_GET['id'];
  80. }
  81. function addColumn($type,$name=null,$descr=null){
  82. if($name===null){
  83. $name=$type;
  84. $type='text';
  85. }
  86. if($descr===null)$descr=ucwords(str_replace('_',' ',$name));
  87. $this->columns[$name]=array(
  88. 'type'=>$type,
  89. );
  90. if(is_array($descr))
  91. $this->columns[$name]=array_merge($this->columns[$name],$descr);
  92. else
  93. $this->columns[$name]['descr']=$descr;
  94. $this->last_column=$name;
  95. $subtypes=explode(',',$type);
  96. foreach($subtypes as $subtype){
  97. if(method_exists($this,$m='init_'.$subtype))$this->$m($name,$descr);
  98. }
  99. return $this;
  100. }
  101. function getColumn($column){
  102. $this->last_column=$column;
  103. return $this;
  104. }
  105. function hasColumn($column){
  106. return isset($this->columns[$column]);
  107. }
  108. function removeColumn($name){
  109. unset($this->columns[$name]);
  110. if($this->last_column==$name)$this->last_column=null;
  111. return $this;
  112. }
  113. function addButton($label,$name=null,$return_button=false){
  114. $button=$this->add('Button','gbtn'.count($this->elements),'grid_buttons');
  115. $button->setLabel($label);
  116. if($return_button)return $button;
  117. return $button;
  118. }
  119. function addQuickSearch($fields,$class='QuickSearch'){
  120. return $this->add($class,null,'quick_search')
  121. ->useGrid($this)
  122. ->useFields($fields);
  123. }
  124. function makeSortable($db_sort=null){
  125. // Sorting
  126. $reverse=false;
  127. if(substr($db_sort,0,1)=='-'){
  128. $reverse=true;
  129. $db_sort=substr($db_sort,1);
  130. }
  131. if(!$db_sort)$db_sort=$this->last_column;
  132. if($this->sortby==$this->last_column){
  133. // we are already sorting by this column
  134. $info=array('1',$reverse?0:("-".$this->last_column));
  135. $this->sortby_db=$db_sort;
  136. }elseif($this->sortby=="-".$this->last_column){
  137. // We are sorted reverse by this column
  138. $info=array('2',$reverse?$this->last_column:'0');
  139. $this->sortby_db="-".$db_sort;
  140. }else{
  141. // we are not sorted by this column
  142. $info=array('0',$reverse?("-".$this->last_column):$this->last_column);
  143. }
  144. $this->columns[$this->last_column]['sortable']=$info;
  145. return $this;
  146. }
  147. function makeTitle(){
  148. $this->title_col[]=$this->last_column;
  149. return $this;
  150. }
  151. function format_number($field){
  152. }
  153. function format_text($field){
  154. $this->current_row[$field] = $this->current_row[$field];
  155. }
  156. function format_shorttext($field){
  157. $text=$this->current_row[$field];
  158. //TODO counting words, tags and trimming so that tags are not garbaged
  159. if(strlen($text)>60)$text=substr($text,0,28).' <b>~~~</b> '.substr($text,-28);;
  160. $this->current_row[$field]=$text;
  161. $this->tdparam[$this->getCurrentIndex()][$field]['title']=$this->current_row[$field.'_original'];
  162. }
  163. function format_html($field){
  164. $this->current_row[$field] = htmlspecialchars_decode($this->current_row[$field]);
  165. }
  166. function format_boolean($field){
  167. if($this->current_row[$field] && $this->current_row[$field]!='N'){
  168. $this->current_row[$field]='<div align=center><i class="atk-icon atk-icons-nobg atk-icon-basic-check"></i></div>';
  169. }else $this->current_row[$field]='';
  170. }
  171. function init_money($field){
  172. @$this->columns[$field]['thparam'].=' style="text-align: right"';
  173. }
  174. function init_real($field){
  175. @$this->columns[$field]['thparam'].=' style="text-align: right"';
  176. }
  177. function init_fullwidth($field){
  178. @$this->columns[$field]['thparam'].=' style="width: 100%"';
  179. }
  180. function format_fullwidth($field){}
  181. function format_money($field){
  182. $m=(float)$this->current_row[$field];
  183. $this->current_row[$field]=number_format($m,2);
  184. if($m<0){
  185. $this->setTDParam($field,'style/color','red');
  186. }else{
  187. $this->setTDParam($field,'style/color',null);
  188. }
  189. $this->setTDParam($field,'align','right');
  190. }
  191. function format_totals_number($field){
  192. return $this->format_number($field);
  193. }
  194. function format_totals_money($field){
  195. return $this->format_money($field);
  196. }
  197. function format_totals_real($field){
  198. return $this->format_real($field);
  199. }
  200. function format_totals_text($field){
  201. // This method is mainly for totals title displaying
  202. if($field==$this->totals_title_field){
  203. $this->setTDParam($field,'style/font-weight','bold');
  204. //$this->current_row[$field]=$this->totals_title.':';
  205. }
  206. else $this->current_row[$field]=$this->totals_value_na;
  207. }
  208. function format_time($field){
  209. $this->current_row[$field]=date($this->api->getConfig('locale/time','H:i:s'),
  210. strtotime($this->current_row[$field]));
  211. }
  212. function format_date($field){
  213. if(!$this->current_row[$field])$this->current_row[$field]='-'; else
  214. $this->current_row[$field]=date($this->api->getConfig('locale/date','d/m/Y'),
  215. strtotime($this->current_row[$field]));
  216. }
  217. function format_datetime($field){
  218. if(!$this->current_row[$field])$this->current_row[$field]='-'; else
  219. $this->current_row[$field]=date($this->api->getConfig('locale/datetime','d/m/Y H:i:s'),
  220. strtotime($this->current_row[$field]));
  221. }
  222. function format_timestamp($field){
  223. if(!$this->current_row[$field])$this->current_row[$field]='-';
  224. else{
  225. $format=$this->api->getConfig('locale/timestamp',$this->api->getConfig('locale/datetime','d/m/Y H:i:s'));
  226. $this->current_row[$field]=date($format,strtotime($this->current_row[$field]));
  227. }
  228. }
  229. function format_nowrap($field){
  230. $this->tdparam[$this->getCurrentIndex()][$field]['style']='nwhite-space: nowrap';
  231. }
  232. function format_wrap($field){
  233. $this->tdparam[$this->getCurrentIndex()][$field]['style']='white-space: wrap';
  234. }
  235. function format_template($field){
  236. if(!($t=$this->columns[$field]['template'])){
  237. throw new BaseException('use setTemplate() for field '.$field);
  238. }
  239. $this->current_row[$field]=$t
  240. ->set($this->current_row)
  241. ->trySet('_value_',$this->current_row[$field])
  242. ->render();
  243. }
  244. function format_widget($field, $widget, $params=array(), $widget_json=null){
  245. $class=$this->name.'_'.$field.'_expander';
  246. $params=array(
  247. 'class'=>$class."_".$field." $widget lister_cell"
  248. )+$params;
  249. $this->js(true)->_tag('.'.$class.'_'.$field)->_load($widget)->activate($widget_json);
  250. /*
  251. $this->api->add('jUI')->addWidget($widget)->activate('.'.$class.'_'.$field,$widget_json);
  252. */
  253. $this->tdparam[$this->getCurrentIndex()][$field]=$params;
  254. if(!$this->current_row[$field]){
  255. $this->current_row[$field]=$this->columns[$field]['descr'];
  256. }
  257. }
  258. function format_expander_widget($field,$column){
  259. return $this->format_expander($field,$column);
  260. }
  261. function format_expander($field, $column){
  262. $class=$this->name.'_'.$field.'_expander';
  263. if(!@$this->current_row[$field]){
  264. $this->current_row[$field]=$column['descr'];
  265. }
  266. // TODO:
  267. // reformat this using Button, once we have more advanced system to bypass rendering of
  268. // sub-elements.
  269. // $this->current_row[$field]=$this->add('Button',null,false)
  270. // ->
  271. //
  272. @$this->current_row[$field]='<input type="checkbox" class="button_'.$field.' '.$class.'"
  273. id="'.$this->name.'_'.$field.'_'.$this->current_row[$column['idfield']?$column['idfield']:'id'].'"
  274. rel="'.$this->api->getDestinationURL($column['page']?$column['page']:'./'.$field,
  275. array('expander'=>$field,
  276. 'cut_page'=>1,
  277. 'expanded'=>$this->name,
  278. // TODO: id is obsolete
  279. 'id'=>$this->current_row[$column['idfield']?$column['idfield']:'id'],
  280. $this->columns[$field]['refid'].'_id'=>$this->current_row[$column['idfield']?$column['idfield']:'id']
  281. )
  282. ).'"
  283. /><label for="'.$this->name.'_'.$field.'_'.$this->current_row[$column['idfield']?$column['idfield']:'id'].'">'.
  284. $this->current_row[$field].'</label>';
  285. }
  286. function init_expander_widget($field){
  287. @$this->columns[$field]['thparam'].=' style="width: 40px; text-align: center"';
  288. return $this->init_expander($field);
  289. }
  290. function init_expander($field){
  291. @$this->columns[$field]['thparam'].=' style="width: 40px; text-align: center"';
  292. $this->js(true)->find('.button_'.$field)->button();
  293. if(!isset($this->columns[$field]['refid'])){
  294. // TODO: test
  295. $refid=$this->getController();
  296. if($refid)$refid=$refid->getModel();
  297. //if($refid)$refid=$refid->entity_code;
  298. if($refid)$refid=$refid->getEntityCode();//Get Protected property Model::entity_code
  299. if($refid){
  300. $this->columns[$field]['refid']=$refid;
  301. }else{
  302. if($this->dq)
  303. $refid=$this->dq->args['table'];
  304. if(!$refid)$refid=preg_replace('/.*_/','',$this->api->page);
  305. $this->columns[$field]['refid']=$refid;
  306. }
  307. }
  308. $class=$this->name.'_'.$field.'_expander';
  309. $this->js(true)->_selector('.'.$class)->_load('ui.atk4_expander')->atk4_expander();
  310. }
  311. function _getFieldType($field){
  312. return 'line';
  313. }
  314. function _inlineUpdate($field,$id,$value){
  315. $this->dq->set($field,$value)->where($this->dq->args['table'].'.id',$id)->do_update();
  316. }
  317. function format_inline($field, $idfield='id'){
  318. /**
  319. * Formats the InlineEdit: field that on click should substitute the text
  320. * in the columns of the row by the edit controls
  321. *
  322. * The point is to set an Id for each column of the row. To do this, we should
  323. * set a property showing that id should be added in prerender
  324. */
  325. $val=$this->current_row[$field];
  326. $this->current_row[$field]='<span id="'.($s=$this->name.'_'.$field.'_inline_'.
  327. $this->current_row['id']).'" >'.
  328. '<i style="float: left" class="atk-icon atk-icons-red atk-icon-office-pencil"></i>'.
  329. $this->current_row[$field].
  330. '</span>';
  331. $this->js(true)->_selector('#'.$s)->click(
  332. $this->js()->_enclose()->_selectorThis()->parent()->atk4_load($this->api->getDestinationURL(null,array($s=>true)))
  333. );
  334. if($_GET[$s]){
  335. // clicked on link
  336. $this->api->stickyGET($s);
  337. $f=$this->owner->add('Form',$s,null,array('form_empty','form'));
  338. $ff=$f->addField($this->_getFieldType($field),'e','');
  339. $ff->set($val);
  340. $ff->js('blur',$this->js()->atk4_grid('reloadRow',$this->current_row['id']));
  341. $ff->js(true)->css(array('width'=>'100%'));
  342. $_GET['cut_object']=$f->name;
  343. if($f->isSubmitted()){
  344. $this->_inlineUpdate($field,$this->current_row['id'],$f->get('e'));
  345. $this->js()->atk4_grid('reloadRow',$this->current_row['id'])->execute();
  346. }
  347. $f->recursiveRender();
  348. }
  349. }
  350. function format_nl2br($field) {
  351. $this->current_row[$field] = nl2br($this->current_row[$field]);
  352. }
  353. function format_order($field, $idfield='id'){
  354. $n=$this->name.'_'.$field.'_'.$this->current_row[$idfield];
  355. $this->tdparam[$this->getCurrentIndex()][$field]=array(
  356. 'id'=>$n,
  357. 'style'=>array(
  358. 'cursor'=>'hand'
  359. )
  360. );
  361. $this->current_row[$field]=$this->record_order->getCell($this->current_row['id']);
  362. }
  363. function init_link($field){
  364. $this->setTemplate('<a href="<?'.'$_link?'.'>"><?'.'$'.$field.'?'.'></a>');
  365. }
  366. function format_link($field){
  367. $this->current_row['_link']=$this->api->getDestinationURL('./details',array('id'=>$this->current_row['id']));
  368. return $this->format_template($field);
  369. /*
  370. $this->current_row[$field]='<a href="'.$this->api->getDestinationURL($field,
  371. array('id'=>$this->current_row['id'])).'">'.
  372. $this->columns[$field]['descr'].'</a>';
  373. */
  374. }
  375. function _performDelete($id){
  376. $this->dq->where($this->dq->args['table'].'.id',$id)->do_delete();
  377. }
  378. function format_delete($field){
  379. if(!$this->dq)throw new BaseException('delete column requires $dq to be set');
  380. if($id=@$_GET[$this->name.'_'.$field]){
  381. // this was clicked
  382. $this->_performDelete($id);
  383. $this->js()->univ()->successMessage('Deleted Successfully')->getjQuery()->reload()->execute();
  384. }
  385. return $this->format_confirm($field);
  386. }
  387. function init_button($field){
  388. @$this->columns[$field]['thparam'].=' style="width: 40px; text-align: center"';
  389. $this->js(true)->find('.button_'.$field)->button();
  390. }
  391. function setButtonClass($class){
  392. $this->columns[$this->last_column]['button_class']=$class;
  393. }
  394. function init_delete($field){
  395. $this->columns[$field]['button_class']='red';
  396. $g=$this;
  397. $this->api->addHook('post-init',array($this,'_move_delete'),array($field));
  398. /*function() use($g,$field){
  399. });
  400. */
  401. return $this->init_confirm($field);
  402. }
  403. function _move_delete($field){
  404. if($this->hasColumn($field))$this->addOrder()->move($field,'last')->now();
  405. }
  406. function init_confirm($field){
  407. @$this->columns[$field]['thparam'].=' style="width: 40px; text-align: center"';
  408. $this->js(true)->find('.button_'.$field)->button();
  409. }
  410. function init_prompt($field){
  411. @$this->columns[$field]['thparam'].=' style="width: 40px; text-align: center"';
  412. $this->js(true)->find('.button_'.$field)->button();
  413. }
  414. function format_button($field){
  415. $this->current_row[$field]='<button type="button" class="'.$this->columns[$field]['button_class'].'button_'.$field.'" '.
  416. 'onclick="$(this).univ().ajaxec(\''.$this->api->getDestinationURL(null,
  417. array($field=>$this->current_row['id'],$this->name.'_'.$field=>$this->current_row['id'])).'\')">'.
  418. (isset($this->columns[$field]['icon'])?$this->columns[$field]['icon']:'').
  419. $this->columns[$field]['descr'].'</button>';
  420. }
  421. function format_confirm($field){
  422. $this->current_row[$field]='<button type="button" class="'.$this->columns[$field]['button_class'].' button_'.$field.'" '.
  423. 'onclick="$(this).univ().confirm(\'Are you sure?\').ajaxec(\''.$this->api->getDestinationURL(null,
  424. array($field=>$this->current_row['id'],$this->name.'_'.$field=>$this->current_row['id'])).'\')">'.
  425. (isset($this->columns[$field]['icon'])?$this->columns[$field]['icon']:'').
  426. $this->columns[$field]['descr'].'</button>';
  427. }
  428. function format_prompt($field){
  429. $this->current_row[$field]='<button type="button" class="'.$this->columns[$field]['button_class'].'button_'.$field.'" '.
  430. 'onclick="value=prompt(\'Enter value: \');$(this).univ().ajaxec(\''.$this->api->getDestinationURL(null,
  431. array($field=>$this->current_row['id'],$this->name.'_'.$field=>$this->current_row['id'])).'&value=\'+value)">'.
  432. (isset($this->columns[$field]['icon'])?$this->columns[$field]['icon']:'').
  433. $this->columns[$field]['descr'].'</button>';
  434. }
  435. function format_checkbox($field){
  436. $this->current_row[$field] = '<input type="checkbox" id="cb_'.
  437. $this->current_row['id'].'" name="cb_'.$this->current_row['id'].
  438. '" value="'.$this->current_row['id'].'"'.
  439. ($this->current_row['selected']=='Y'?" checked ":" ").//'" onclick="'.
  440. //$this->onClick($field).
  441. '/>';
  442. $this->setTDParam($field,'width','10');
  443. $this->setTDParam($field,'align','center');
  444. }
  445. function format_image($field){
  446. $this->current_row[$field]='<img src="'.$this->current_row[$field].'"/>';
  447. }
  448. function addRecordOrder($field,$table=''){
  449. if(!$this->record_order){
  450. $this->record_order=$this->add('RecordOrder');
  451. $this->record_order->setField($field,$table);
  452. }
  453. return $this;
  454. }
  455. function staticSortCompare($row1,$row2){
  456. if($this->sortby[0]=='-'){
  457. return strcmp($row2[substr($this->sortby,1)],$row1[substr($this->sortby,1)]);
  458. }
  459. return strcmp($row1[$this->sortby],$row2[$this->sortby]);
  460. }
  461. function setStaticSource($data){
  462. $this->data=$data;
  463. if($this->sortby){
  464. usort($this->data,array($this,'staticSortCompare'));
  465. }
  466. return $this;
  467. }
  468. function setSource($table,$db_fields=null){
  469. parent::setSource($table,$db_fields);
  470. //we always need to calc rows
  471. $this->dq->calc_found_rows();
  472. return $this;
  473. }
  474. function processSorting(){
  475. if($this->sortby){
  476. $desc=false;
  477. $order=$this->sortby_db;
  478. if(substr($this->sortby_db,0,1)=='-'){
  479. $desc=true;
  480. $order=substr($order,1);
  481. }
  482. if($order)$this->dq->order($order,$desc);
  483. }
  484. }
  485. function execQuery(){
  486. $this->processSorting();
  487. return parent::execQuery();
  488. }
  489. function setTemplate($template){
  490. // This allows you to use Template
  491. $this->columns[$this->last_column]['template']=$this->add('SMlite')
  492. ->loadTemplateFromString($template);
  493. return $this;
  494. }
  495. function getFieldStyle($field,$id){
  496. /**
  497. * Returns the structured string with row styles. Used along with getRowContent()
  498. * in row redrawing
  499. */
  500. $style=$this->tdparam[$this->getCurrentIndex()][$field];
  501. $tdparam=null;
  502. if(is_array($style)){
  503. // now we should convert style elements' names to JS compatible
  504. $tdparam=array();
  505. foreach($style as $key=>$value){
  506. switch($key){
  507. //case 'background-color':$tdparam[]="$key:$value";break;
  508. case 'style': case 'css':
  509. // style is a nested array
  510. foreach($value as $k=>$v){
  511. $tdparam[]="$k::$v";
  512. }
  513. break;
  514. default:
  515. $tdparam[]="$key::$value";
  516. }
  517. }
  518. }
  519. return (is_array($tdparam)?join($tdparam,'<s>'):'');
  520. }
  521. function getRowTitle(){
  522. $title=' ';
  523. foreach($this->title_col as $col){
  524. $title.=$this->current_row[$col];
  525. }
  526. if($title==' '){
  527. return "Row #".$this->current_row['id'];
  528. }
  529. return substr($title,1);
  530. }
  531. function getFieldContent($field,$id){
  532. /**
  533. * Returns the properly formatted field content.
  534. * Used firstly with Ajax::reloadExpandedField()
  535. */
  536. // *** Getting required record from DB ***
  537. $idfield=$this->dq->args['fields'][0];
  538. if($idfield=='*'||strpos($idfield,',')!==false)$idfield='id';
  539. $this->dq->where($idfield,$id);
  540. //we should switch off the limit or we won't get any value
  541. $this->dq->limit(1);
  542. $row_data=$this->api->db->getHash($this->dq->select());
  543. // *** Initializing template ***
  544. $this->precacheTemplate(false);
  545. // *** Rendering row ***
  546. $this->current_row=(array)$row_data;
  547. $row=$this->formatRow();
  548. // *** Returning required field value ***
  549. return $row[$field];
  550. }
  551. function formatRow(){
  552. // Support for StdObject grids
  553. if(!is_array($this->current_row))$this->current_row=(array)$this->current_row;
  554. if(!$this->columns)throw new BaseException('No column defined for grid');
  555. foreach($this->columns as $tmp=>$column){
  556. $this->current_row[$tmp.'_original']=@$this->current_row[$tmp];
  557. if($this->safe_html_output)
  558. $this->current_row[$tmp]=htmlspecialchars($this->current_row[$tmp]);
  559. $formatters = explode(',',$column['type']);
  560. foreach($formatters as $formatter){
  561. if(!$formatter)continue;
  562. if(method_exists($this,$m="format_".$formatter)){
  563. $this->$m($tmp,$column);
  564. }else throw new BaseException("Grid does not know how to format type: ".$formatter);
  565. }
  566. // setting cell parameters (tdparam)
  567. $this->applyTDParams($tmp);
  568. if($this->current_row[$tmp]=='')$this->current_row[$tmp]='&nbsp;';
  569. }
  570. return $this->current_row;
  571. }
  572. function applyTDParams($field,$totals=false){
  573. // setting cell parameters (tdparam)
  574. $tdparam=@$this->tdparam[$this->getCurrentIndex()][$field];
  575. $tdparam_str='';
  576. if(is_array($tdparam)){
  577. // wrap is replaced by style property
  578. unset($tdparam['wrap']);
  579. if(is_array($tdparam['style'])){
  580. $tdparam_str.='style="';
  581. foreach($tdparam['style'] as $key=>$value)$tdparam_str.=$key.':'.$value.';';
  582. $tdparam_str.='" ';
  583. unset($tdparam['style']);
  584. }
  585. //walking and combining string
  586. foreach($tdparam as $id=>$value)$tdparam_str.=$id.'="'.$value.'" ';
  587. if($totals)$this->totals_t->set("tdparam_$field",trim($tdparam_str));
  588. else $this->row_t->trySet("tdparam_$field",trim($tdparam_str));
  589. }
  590. }
  591. function setTotalsTitle($field,$title="Total:"){
  592. $this->totals_title_field=$field;
  593. $this->totals_title=$title;
  594. return $this;
  595. }
  596. function formatTotalsRow(){
  597. foreach($this->columns as $tmp=>$column){
  598. $formatters = explode(',',$column['type']);
  599. $all_failed=true;
  600. foreach($formatters as $formatter){
  601. if(method_exists($this,$m="format_totals_".$formatter)){
  602. $all_failed=false;
  603. $this->$m($tmp);
  604. }
  605. }
  606. // setting cell parameters (tdparam)
  607. $this->applyTDParams($tmp,true);
  608. if($all_failed)$this->current_row[$tmp]=$this->totals_value_na;
  609. }
  610. }
  611. function updateTotals(){
  612. parent::updateTotals();
  613. foreach($this->current_row as $key=>$val){
  614. if ((!empty($this->totals_title_field)) and ($key==$this->totals_title_field)) {
  615. $this->totals[$key]=$this->totals_title;
  616. }
  617. }
  618. }
  619. /**
  620. * Adds paginator to the grid
  621. * @param $ipp row count per page
  622. * @param $name if set, paginator will get the name specified. Useful for saving
  623. * different page numbers for different filtering conditions
  624. */
  625. function addPaginator($ipp=25,$name=null){
  626. // adding ajax paginator
  627. $this->paginator=$this->add('Paginator', $name, 'paginator', array('paginator', 'ajax_paginator'));
  628. $this->paginator->region($this->name);
  629. $this->paginator->cutObject($this->name);
  630. $this->paginator->ipp($ipp);
  631. $this->current_row_index=$this->paginator->skip-1;
  632. return $this;
  633. }
  634. function precacheTemplate($full=true){
  635. // pre-cache our template for row
  636. // $full=false used for certain row init
  637. $row = $this->row_t;
  638. $col = $row->cloneRegion('col');
  639. $row->set('row_id','<?$id?>');
  640. $row->trySet('odd_even','<?$odd_even?>');
  641. $row->del('cols');
  642. if($full){
  643. $header = $this->template->cloneRegion('header');
  644. $header_col = $header->cloneRegion('col');
  645. $header_sort = $header_col->cloneRegion('sort');
  646. if($t_row = $this->totals_t){
  647. $t_col = $t_row->cloneRegion('col');
  648. $t_row->del('cols');
  649. }
  650. $header->del('cols');
  651. }
  652. if(count($this->columns)>0){
  653. foreach($this->columns as $name=>$column){
  654. $col->del('content');
  655. $col->set('content','<?$'.$name.'?>');
  656. if(isset($t_row)){
  657. $t_col->del('content');
  658. $t_col->set('content','<?$'.$name.'?>');
  659. $t_col->trySet('tdparam','<?tdparam_'.$name.'?>style="white-space: nowrap"<?/?>');
  660. $t_row->append('cols',$t_col->render());
  661. }
  662. // some types needs control over the td
  663. $col->set('tdparam','<?tdparam_'.$name.'?>style="white-space: nowrap"<?/?>');
  664. $row->append('cols',$col->render());
  665. if($full){
  666. $header_col->set('descr',$column['descr']);
  667. $header_col->trySet('type',$column['type']);
  668. if(isset($column['sortable'])){
  669. $s=$column['sortable'];
  670. // calculate sortlink
  671. $l = $this->api->getDestinationURL(null,array($this->name.'_sort'=>$s[1]));
  672. $header_sort->trySet('order',$column['sortable'][0]);
  673. $sicons=array('vertical','top','bottom');
  674. $header_sort->trySet('sorticon',$sicons[$column['sortable'][0]]);
  675. $header_sort->set('sortlink',$l);
  676. $header_col->set('sort',$header_sort->render());
  677. }else{
  678. $header_col->del('sort');
  679. $header_col->tryDel('sort_del');
  680. }
  681. if($column['thparam']){
  682. $header_col->trySet('thparam',$column['thparam']);
  683. }else{
  684. $header_col->tryDel('thparam');
  685. }
  686. $header->append('cols',$header_col->render());
  687. }
  688. }
  689. }
  690. $this->row_t = $this->api->add('SMlite');
  691. $this->row_t->loadTemplateFromString($row->render());
  692. if(isset($t_row)){
  693. $this->totals_t = $this->api->add('SMlite');
  694. $this->totals_t->loadTemplateFromString($t_row->render());
  695. }
  696. if($full)$this->template->set('header',$header->render());
  697. // for certain row: required data is in $this->row_t
  698. //var_dump(htmlspecialchars($this->row_t->tmp_template));
  699. }
  700. function render(){
  701. if($this->js_widget){
  702. $fn=str_replace('ui.','',$this->js_widget);
  703. $this->js(true)->_load($this->js_widget)->$fn($this->js_widget_arguments);
  704. }
  705. if(($this->dq&&$this->dq->foundRows()==0)||(!isset($this->dq)&&empty($this->data))){
  706. $def_template = $this->defaultTemplate();
  707. //$not_found=$this->add('SMlite')->loadTemplate($def_template[0])->cloneRegion('not_found');
  708. //$this->template->set('no_records_message',$this->no_records_message);
  709. //$this->template->del('rows');
  710. //$this->template->del('totals');
  711. //$this->template->set('header','<tr class="header">'.$not_found->render().'</tr>');
  712. $this->totals=false;
  713. $this->template->del('full_table');
  714. // return true;
  715. }else{
  716. $this->template->del('not_found');
  717. }
  718. parent::render();
  719. }
  720. public function setWidth( $width ){
  721. $this->template->set('container_style', 'margin: 0 auto; width:'.$width.((!is_numeric($width))?'':'px'));
  722. return $this;
  723. }
  724. public function setNoRecords($message){
  725. $this->no_records_message=$message;
  726. return $this;
  727. }
  728. public function getCurrentIndex($idfield='id'){
  729. // TODO: think more to optimize this method
  730. if(is_array($this->data))return array_search(current($this->data),$this->data);
  731. // else it is dsql dataset...
  732. return $this->current_row[$idfield];
  733. }
  734. public function setTDParam($field,$path,$value){
  735. // adds a parameter. nested ones can be specified like 'style/color'
  736. $path=explode('/',$path);
  737. $current_position=&$this->tdparam[$this->getCurrentIndex()][$field];
  738. if(!is_array($current_position))$current_position=array();
  739. foreach($path as $part){
  740. if(array_key_exists($part,$current_position)){
  741. $current_position=&$current_position[$part];
  742. }else{
  743. $current_position[$part]=array();
  744. $current_position=&$current_position[$part];
  745. }
  746. }
  747. $current_position=$value;
  748. }
  749. public function setTabMovesDown($down=true){
  750. $this->tab_moves_down=$down;
  751. return $this;
  752. }
  753. public function setShowSubmit($show=true){
  754. $this->show_submit=$show;
  755. return $this;
  756. }
  757. /**
  758. * Sets inline properties.
  759. * @param $props - hash with properties: array('tab_moves_down'=>false/true,'show_submit'=>false/true,etc)
  760. * hash keys should replicate local properties names
  761. */
  762. public function setInlineProperties($props){
  763. foreach($props as $key=>$val){
  764. $this->$key=$val;
  765. }
  766. return $this;
  767. }
  768. function addOrder(){
  769. return $this->add('Order','columns')
  770. ->useArray($this->columns)
  771. ;
  772. }
  773. /**
  774. * Adds column on the basis of Model definition
  775. * If $type is passed - column type is replaced by this value
  776. */
  777. function addSelectable($field){
  778. $this->js_widget=null;
  779. $this->js(true)
  780. ->_load('ui.atk4_checkboxes')
  781. ->atk4_checkboxes(array('dst_field'=>$field));
  782. $this->addColumn('checkbox','selected');
  783. $this->addOrder()
  784. ->useArray($this->columns)
  785. ->move('selected','first')
  786. ->now();
  787. }
  788. function addFormatter($field,$formatter){
  789. /*
  790. * add extra formatter to existing field
  791. */
  792. if(!isset($this->columns[$field])){
  793. throw new BaseException('Cannot format nonexistant field '.$field);
  794. }
  795. $this->columns[$field]['type'].=','.$formatter;
  796. if(method_exists($this,$m='init_'.$formatter))$this->$m($field);
  797. return $this;
  798. }
  799. function setFormatter($field,$formatter){
  800. /*
  801. * replace current formatter for field
  802. */
  803. if(!isset($this->columns[$field])){
  804. throw new BaseException('Cannot format nonexistant field '.$field);
  805. }
  806. $this->columns[$field]['type']=$formatter;
  807. if(method_exists($this,$m='init_'.$formatter))$this->$m($field);
  808. $this->last_column=$field;
  809. return $this;
  810. }
  811. /* to reuse td params */
  812. function getAllTDParams(){
  813. return $this->tdparam;
  814. }
  815. }