PageRenderTime 65ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/atk4/lib/Form/Basic.php

https://github.com/mahimarathore/mahi
PHP | 409 lines | 299 code | 31 blank | 79 comment | 33 complexity | ebb6c6ae6f559cdaa5ba360146aa1bed MD5 | raw file
Possible License(s): AGPL-3.0, MPL-2.0-no-copyleft-exception
  1. <?php
  2. /***********************************************************
  3. ..
  4. Reference:
  5. http://agiletoolkit.org/doc/ref
  6. ==ATK4===================================================
  7. This file is part of Agile Toolkit 4
  8. http://agiletoolkit.org/
  9. (c) 2008-2013 Agile Toolkit Limited <info@agiletoolkit.org>
  10. Distributed under Affero General Public License v3 and
  11. commercial license.
  12. See LICENSE or LICENSE_COM for more information
  13. =====================================================ATK4=*/
  14. // Field bundle
  15. //if(!class_exists('Form_Field',false))include_once'Form/Field.php';
  16. /**
  17. * This class implements generic form, which you can actually use without
  18. * redeclaring it. Just add fields, buttons and use execute method.
  19. *
  20. * @author Romans <romans@adevel.com>
  21. * @copyright See file COPYING
  22. * @version $Id$
  23. */
  24. class Form_Basic extends View {
  25. protected $form_template = null;
  26. protected $form_tag = null;
  27. // Here we will have a list of errors occured in the form, when we tried to
  28. // submit it. field_name => error
  29. public $errors=array();
  30. // Those templates will be used when rendering form and fields
  31. public $template_chunks=array();
  32. // This array holds list of values prepared for fields before their
  33. // initialization. When fields are initialized they will look into this
  34. // array to see if there are default value for them.
  35. // Afterwards fields will link to $this->data, so changing
  36. // $this->data['fld_name'] would actually affect field's value.
  37. // You should use $this->set() and $this->get() to read/write individual
  38. // field values. You should use $this->setStaticSource() to load values from
  39. // hash, BUT - AAAAAAAAAA: this array is no more!!!
  40. public $data = array();
  41. public $bail_out = null; // if this is true, we won't load data or submit or validate anything.
  42. protected $loaded_from_db = false; // if true, update() will try updating existing row. if false - it would insert new
  43. public $onsubmit = null;
  44. public $onload = null;
  45. protected $ajax_submits=array(); // contains AJAX instances assigned to buttons
  46. protected $get_field=null; // if condition was passed to a form through GET, contains a GET field name
  47. protected $conditions=array();
  48. public $js_widget='ui.atk4_form';
  49. public $js_widget_arguments=array();
  50. public $default_exception='Exception_ValidityCheck';
  51. public $default_controller='Controller_MVCForm';
  52. public $dq = null;
  53. function init(){
  54. /**
  55. * During form initialization it will go through it's own template and
  56. * search for lots of small template chunks it will be using. If those
  57. * chunk won't be in template, it will fall back to default values.
  58. * This way you can re-define how form will look, but only what you need
  59. * in particular case. If you don't specify template at all, form will
  60. * work with default look.
  61. */
  62. parent::init();
  63. $this->getChunks();
  64. // After init method have been executed, it's safe for you to add
  65. // controls on the form. BTW, if you want to have default values such as
  66. // loaded from the table, then intialize $this->data array to default
  67. // values of those fields.
  68. $this->api->addHook('pre-exec',array($this,'loadData'));
  69. $this->api->addHook('pre-render-output',array($this,'lateSubmit'));
  70. $this->api->addHook('submitted',$this);
  71. $this->template_chunks['form']
  72. ->set('form_action',$this->api->url(null,array('submit'=>$this->name)));
  73. }
  74. protected function getChunks(){
  75. // commonly replaceable chunks
  76. $this->grabTemplateChunk('form_comment');
  77. $this->grabTemplateChunk('form_separator');
  78. $this->grabTemplateChunk('form_line'); // template for form line, must contain field_caption,field_input,field_error
  79. if($this->template->is_set('hidden_form_line'))
  80. $this->grabTemplateChunk('hidden_form_line');
  81. $this->grabTemplateChunk('field_error'); // template for error code, must contain field_error_str
  82. $this->grabTemplateChunk('field_mandatory');// template for marking mandatory fields
  83. // other grabbing will be done by field themselves as you will add them
  84. // to the form. They will try to look into this template, and if you
  85. // don't have apropriate templates for them, they will use default ones.
  86. $this->template_chunks['form']=$this->template;
  87. $this->template_chunks['form']->del('Content');
  88. $this->template_chunks['form']->del('form_buttons');
  89. $this->template_chunks['form']->set('form_name',$this->name.'_form');
  90. return $this;
  91. }
  92. function initializeTemplate($tag, $template){
  93. $template = $this->form_template?:$template;
  94. $tag = $this->form_tag?:$tag;
  95. return parent::initializeTemplate($tag, $template);
  96. }
  97. function defaultTemplate($template = null, $tag = null){
  98. if ($template){
  99. $this->form_template = $template;
  100. }
  101. if ($tag){
  102. $this->form_tag = $tag;
  103. }
  104. return array($this->form_template?:"form", $this->form_tag?:"form");
  105. }
  106. function grabTemplateChunk($name){
  107. if($this->template->is_set($name)){
  108. $this->template_chunks[$name] = $this->template->cloneRegion($name);
  109. }else{
  110. //return $this->fatal('missing form tag: '.$name);
  111. // hmm.. i wonder what ? :)
  112. }
  113. }
  114. /**
  115. * Should show error in field. Override this method to change form default alert
  116. * @param object $field Field instance that caused error
  117. * @param string $msg message to show
  118. */
  119. function showAjaxError($field,$msg){
  120. // Avoid deprecated function use in reference field, line 246
  121. return $this->displayError($field,$msg);
  122. }
  123. function displayError($field=null,$msg=null){
  124. if(!$field){
  125. // Field is not defined
  126. // TODO: add support for error in template
  127. $this->js()->univ()->alert($msg?:'Error in form')->execute();
  128. }
  129. if(!is_object($field))$field=$this->getElement($field);
  130. $this->js()->atk4_form('fieldError',$field->short_name,$msg)->execute();
  131. }
  132. function addField($type,$name,$caption=null,$attr=null){
  133. if($caption===null)$caption=ucwords(str_replace('_',' ',$name));
  134. switch(strtolower($type)){
  135. case'dropdown':$class='DropDown';break;
  136. case'checkboxlist':$class='CheckboxList';break;
  137. default:$class=$type;
  138. }
  139. $class[0]=strtoupper($class[0]);
  140. $class=$this->api->normalizeClassName($class,'Form_Field');
  141. $last_field=$this->add($class,$name,null,'form_line')
  142. ->setCaption($caption);
  143. $last_field->setForm($this);
  144. $last_field->template->trySet('field_type',strtolower($type));
  145. $last_field->setAttr($attr);
  146. return $last_field;
  147. }
  148. function importFields($model,$fields=undefined){
  149. $this->add($this->default_controller)->importFields($model,$fields);
  150. }
  151. function addComment($comment){
  152. if(!isset($this->template_chunks['form_comment']))
  153. throw new BaseException('Form\'s template ('.$this->template->loaded_template.') does not support comments');
  154. return $this->add('Html')->set(
  155. $this->template_chunks['form_comment']->set('comment',$comment)->render()
  156. );
  157. }
  158. function addSeparator($fieldset_class=''){
  159. if(!isset($this->template_chunks['form_separator']))return $this;
  160. $c=$this->template_chunks['form_separator'];
  161. $c->trySet('fieldset_class',$fieldset_class);
  162. return $this->add('Html')->set($c->render());
  163. }
  164. // Operating with field values
  165. function get($field=null){
  166. if(!$field)return $this->data;
  167. return $this->data[$field];
  168. }
  169. function setSource($table,$db_fields=null){
  170. if(is_null($db_fields)){
  171. $db_fields=array();
  172. foreach($this->elements as $key=>$el){
  173. if(!($el instanceof Form_Field))continue;
  174. if($el->no_save)continue;
  175. $db_fields[]=$key;
  176. }
  177. }
  178. $this->dq = $this->api->db->dsql()
  179. ->table($table)
  180. ->field('*',$table)
  181. ->limit(1);
  182. return $this;
  183. }
  184. function set($field_or_array,$value=undefined){
  185. // We use undefined, because 2nd argument of "null" is meaningfull
  186. if($value===undefined){
  187. if(is_array($field_or_array)){
  188. foreach($field_or_array as $key=>$val){
  189. if(isset($this->elements[$key])&&($this->elements[$key] instanceof Form_Field))
  190. $this->set($key,$val);
  191. }
  192. return $this;
  193. }else{
  194. throw new ObsoleteException('Please specify 2 arguments to $form->set()');
  195. }
  196. }
  197. if(!isset($this->elements[$field_or_array])){
  198. foreach ($this->elements as $key => $val){
  199. echo "$key<br />";
  200. }
  201. throw new BaseException("Trying to set value for non-existant field $field_or_array");
  202. }
  203. if($this->elements[$field_or_array] instanceof Form_Field)
  204. $this->elements[$field_or_array]->set($value);
  205. else{
  206. //throw new BaseException("Form fields must inherit from Form_Field ($field_or_array)");
  207. }
  208. return $this;
  209. }
  210. function getAllFields(){
  211. return $this->get();
  212. }
  213. function addSubmit($label='Save',$name=null){
  214. $submit = $this->add('Form_Submit',$name,'form_buttons')
  215. ->setLabel($label)
  216. ->setNoSave();
  217. return $submit;
  218. }
  219. function addButton($label='Button',$name=null){
  220. $button = $this->add('Button',$name,'form_buttons')
  221. ->setLabel($label);
  222. return $button;
  223. }
  224. function loadData(){
  225. /**
  226. * This call will be sent to fields, and they will initialize their values from $this->data
  227. */
  228. if(!is_null($this->bail_out))return;
  229. $this->hook('post-loadData');
  230. }
  231. function isLoadedFromDB(){
  232. return $this->loaded_from_db;
  233. }
  234. function update(){
  235. // TODO: start transaction here
  236. if($this->hook('update'))return $this;
  237. if(!($m=$this->getModel()))throw new BaseException("Can't save, model not specified");
  238. if(!is_null($this->get_field))$this->api->stickyForget($this->get_field);
  239. foreach($this->elements as $short_name => $element){
  240. if($element instanceof Form_Field)if(!$element->no_save){
  241. //if(is_null($element->get()))
  242. $m->set($short_name, $element->get());
  243. }
  244. }
  245. $m->save();
  246. }
  247. function submitted(){
  248. /**
  249. * Default down-call submitted will automatically call this method if form was submitted
  250. */
  251. // We want to give flexibility to our controls and grant them a chance
  252. // to hook to those spots here.
  253. // On Windows platform mod_rewrite is lowercasing all the urls.
  254. if($_GET['submit']!=$this->name)return;
  255. if(!is_null($this->bail_out))return $this->bail_out;
  256. $this->hook('loadPOST');
  257. try{
  258. $this->hook('validate');
  259. if(!empty($this->errors))return false;
  260. if(($output=$this->hook('submit',array($this)))){
  261. /* checking if anything usefull in output */
  262. if(is_array($output)){
  263. $has_output = false;
  264. foreach ($output as $row){
  265. if ($row){
  266. $has_output = true;
  267. break;
  268. }
  269. }
  270. if (!$has_output){
  271. return true;
  272. }
  273. }
  274. /* TODO: need logic re-check here + test scripts */
  275. //if(!is_array($output))$output=array($output);
  276. // already array
  277. if($has_output)$this->js(null,$output)->execute();
  278. }
  279. }catch (BaseException $e){
  280. if($e instanceof Exception_ValidityCheck){
  281. $f=$e->getField();
  282. if($f && is_string($f) && $fld=$this->hasElement($f)){
  283. $fld->displayFieldError($e->getMessage());
  284. } else $this->js()->univ()->alert($e->getMessage().' in undefined field')->execute();
  285. }
  286. if($e instanceof Exception_ForUser){
  287. $this->js()->univ()->alert($e->getMessage())->execute();
  288. }
  289. throw $e;
  290. }
  291. return true;
  292. }
  293. function lateSubmit(){
  294. if(@$_GET['submit']!=$this->name)return;
  295. if($this->bail_out===null || $this->isSubmitted()){
  296. $this->js()->univ()
  297. ->consoleError('Form '.$this->name.' submission is not handled.'.
  298. ' See: http://agiletoolkit.org/doc/form/submit')
  299. ->execute();
  300. }
  301. }
  302. function isSubmitted(){
  303. // This is alternative way for form submission. After form is initialized
  304. // you can call this method. It will hurry up all the steps, but you will
  305. // have ready-to-use form right away and can make submission handlers
  306. // easier
  307. if($this->bail_out!==null)return $this->bail_out;
  308. $this->loadData();
  309. $result = $_POST && $this->submitted();
  310. $this->bail_out=$result;
  311. return $result;
  312. }
  313. function onSubmit($callback){
  314. $this->addHook('submit',$callback);
  315. $this->isSubmitted();
  316. }
  317. function setLayout($template){
  318. // Instead of building our own Content we will take it from
  319. // pre-defined template and insert fields into there
  320. $this->template_chunks['custom_layout']=($template instanceof SMLite)?$template:$this->add('SMLite')->loadTemplate($template);
  321. $this->template_chunks['custom_layout']->trySet('_name',$this->name);
  322. $this->template->trySet('form_class_layout',$c='form_'.basename($template));
  323. return $this;
  324. }
  325. function setFormClass($class){
  326. return $this->setClass($class);
  327. }
  328. function render(){
  329. // Assuming, that child fields already inserted their HTML code into 'form'/Content using 'form_line'
  330. // Assuming, that child buttons already inserted their HTML code into 'form'/form_buttons
  331. if($this->js_widget){
  332. $fn=str_replace('ui.','',$this->js_widget);
  333. $this->js(true)->_load($this->js_widget)->$fn($this->js_widget_arguments);
  334. }
  335. if(isset($this->template_chunks['custom_layout'])){
  336. foreach($this->elements as $key=>$val){
  337. if($val instanceof Form_Field){
  338. $attr=$this->template_chunks['custom_layout']->get($key);
  339. if(is_array($attr))$attr=join(' ',$attr);
  340. if($attr)$val->setAttr('style',$attr);
  341. if(!$this->template_chunks['custom_layout']->is_set($key)){
  342. $this->js(true)->univ()->log('No field in layout: '.$key);
  343. }
  344. $this->template_chunks['custom_layout']->trySetHTML($key,$val->getInput());
  345. if($this->errors[$key]){
  346. $this->template_chunks['custom_layout']
  347. ->trySet($key.'_error',$val->error_template
  348. ->set('field_error_str',$this->errors[$key])->render());
  349. }
  350. }
  351. }
  352. $this->template->setHTML('Content',$this->template_chunks['custom_layout']->render());
  353. }
  354. $this->owner->template->appendHTML($this->spot,$r=$this->template_chunks['form']->render());
  355. }
  356. function hasField($name){
  357. return isset($this->elements[$name])?$this->elements[$name]:false;
  358. }
  359. function isClicked($name){
  360. if(is_object($name))$name=$name->short_name;
  361. return $_POST['ajax_submit']==$name || isset($_POST[$this->name . "_" . $name]);
  362. }
  363. function error($field,$text){
  364. $this->getElement($field)->displayFieldError($text);
  365. }
  366. /* external error management */
  367. function setFieldError($field, $name){
  368. $this->errors[$field] = (isset($this->errors[$field])?$this->errors[$field]:'') . $name;
  369. }
  370. }