PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/atk4/lib/AbstractView.php

https://github.com/intuititve/lostandfound
PHP | 359 lines | 164 code | 29 blank | 166 comment | 55 complexity | cd78954c81eb9a63bc60f0959f69afc4 MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php // vim:ts=4:sw=4:et:fdm=marker
  2. /**
  3. * A base class for all Visual objects in Agile Toolkit. The
  4. * important distinctive property of all Views is abiltiy
  5. * to render themselves (produce HTML) automatically and
  6. * recursively.
  7. *
  8. * @link http://agiletoolkit.org/learn/understand/view
  9. *//*
  10. ==ATK4===================================================
  11. This file is part of Agile Toolkit 4
  12. http://agiletoolkit.org/
  13. (c) 2008-2012 Romans Malinovskis <romans@agiletoolkit.org>
  14. Distributed under Affero General Public License v3
  15. See http://agiletoolkit.org/about/license
  16. =====================================================ATK4=*/
  17. abstract class AbstractView extends AbstractObject {
  18. /**
  19. * $template is an SMLite object containing indexed HTML
  20. * template.
  21. *
  22. * Example:
  23. *
  24. * $view->template->set('title', $my_title);
  25. *
  26. * Assuming you have tag <?$template?> in template file associated
  27. * with this view - will insert text into this tag.
  28. *
  29. * @see AbstractObject::add();
  30. * @see AbstractView::defaultTemplate();
  31. */
  32. public $template=false;
  33. /**
  34. * @internal
  35. *
  36. * $template_flush is set to a spot on the template, which
  37. * should be flushed out. When using AJAX we want to show
  38. * only certain region from our template. However several
  39. * childs may want to put their data. This property will
  40. * be set to region's name my call_ajax_render and if it's
  41. * set, call_ajax_render will echo it and return false.
  42. */
  43. public $template_flush=false;
  44. /**
  45. * $spot defines a place on a parent's template where render() will
  46. * output() resulting HTML
  47. *
  48. * @see output()
  49. * @see render()
  50. * @see AbstractObject::add();
  51. * @see defaultSpot();
  52. */
  53. public $spot;
  54. /**
  55. * When using setModel() with Views some views will want to populate
  56. * fields, columns etc corresponding to models meta-data. That is the
  57. * job of Controller. When you create a custom controller for your view
  58. * set this property to point at your controller and it will be used
  59. * automatically */
  60. public $default_controller=null;
  61. public $auto_track_element=true;
  62. // {{{ Basic Operations
  63. /** Duplicate view and it's template. Will not duplicate children */
  64. function __clone(){
  65. throw $this->exception('Can\'t clone Views');
  66. //parent::__clone();
  67. //if($this->template)$this->template=clone $this->template;
  68. }
  69. /** Get associated model. It's safe to access $object->model directly. */
  70. function getModel(){
  71. return $this->model;
  72. }
  73. /** Associate view with a model. Different models may behave differently. */
  74. function setModel($model,$actual_fields=undefined){
  75. parent::setModel($model);
  76. // Some models will want default controller to be associated
  77. if($this->model->default_controller){
  78. $this->controller = $this->model->setController($this->model->default_controller);
  79. }
  80. // Use our default controller if present
  81. if($this->default_controller){
  82. $this->controller = $this->setController($this->default_controller);
  83. if($this->controller->hasMethod('setActualFields'))$this->controller->setActualFields($actual_fields);
  84. if($this->controller->hasMethod('_bindView'))$this->controller->_bindView();
  85. }
  86. if($this->model instanceof Model_Table)$this->dq=$this->model->_dsql(); // compatibility
  87. return $this->model;
  88. }
  89. /** @internal used by getHTML */
  90. public $_tsBuffer='';
  91. function _tsBuffer($t,$data){
  92. $this->_tsBuffer.=$data;
  93. }
  94. /** Converting View into string will render recursively and produce HTML. If argument is passed, JavaScript will be added
  95. * into on_ready section of your document like when rendered normally. Note that you might require to destroy object
  96. * if you don't want it's HTML to appear normally */
  97. function getHTML($destroy=true,$execute_js=true){
  98. $this->addHook('output',array($this,'_tsBuffer'));
  99. $this->recursiveRender();
  100. $this->removeHook('output',array($this,'_tsBuffer'));
  101. $ret=$this->_tsBuffer;
  102. $this->_tsBuffer='';
  103. if($execute_js && $this->api->jquery)$this->api->jquery->getJS($this);
  104. if($destroy)$this->destroy();
  105. return $ret;
  106. }
  107. // }}}
  108. // {{{ Template Setup
  109. /** @internal Called automatically during init for template initalization */
  110. function initializeTemplate($template_spot=null,$template_branch=null){
  111. if(!$template_spot)$template_spot=$this->defaultSpot();
  112. $this->spot=$template_spot;
  113. if($this->owner->template &&
  114. !$this->owner->template->is_set($this->spot))throw
  115. $this->exception('Spot is not found in owner\'s template')
  116. ->addMoreInfo('spot',$this->spot);
  117. if(!isset($template_branch))$template_branch=$this->defaultTemplate();
  118. if(isset($template_branch)){
  119. // template branch would tell us what kind of template we have to use. Let's
  120. // look at several cases
  121. if(is_object($template_branch)){ // it might be already SMlite instance (object)
  122. $this->template=$template_branch; // so we just use that
  123. }else if(is_array($template_branch)){ // it might be array with [0]=template, [1]=tag
  124. if(is_object($template_branch[0])){ // if [0] is object, we'll use that
  125. $this->template=$template_branch[0];
  126. }else{
  127. $this->template=$this->api->add('SMlite');
  128. $this->template->loadTemplate($template_branch[0]); // we'll use it as a file
  129. }
  130. // Now that we loaded it, let's see which tag we need to cut out
  131. $this->template=$this->template->cloneRegion(isset($template_branch[1])?$template_branch[1]:'_top');
  132. }else{ // brach could be just a string - a region to clone off parent
  133. if(isset($this->owner->template)){
  134. $this->template=$this->owner->template->cloneRegion($template_branch);
  135. }else{
  136. $this->template=$this->add('SMlite');
  137. }
  138. }
  139. $this->template->owner=$this;
  140. }
  141. // Now that the template is loaded, let's take care of parent's template
  142. if($this->owner && (isset($this->owner->template)) && (!empty($this->owner->template))){
  143. $this->owner->template->del($this->spot);
  144. }
  145. // Cool, now let's set _name of this template
  146. if($this->template)$this->template->trySet('_name',str_replace('/','_',$this->name));
  147. }
  148. /** @internal Lets API auto-fill some tags in all views (such as tempalte tag) */
  149. function initTemplateTags(){
  150. if($this->template && $this->api && method_exists($this->api, 'setTags')){
  151. $this->api->setTags($this->template);
  152. }
  153. }
  154. /** Redefine to return default template, when 4th argument of add() is omitted */
  155. function defaultTemplate(){
  156. return $this->spot;
  157. }
  158. /** Default tag in parent's template where output is inserted, when 3rd argument of add() is omitted */
  159. function defaultSpot(){
  160. return 'Content';
  161. }
  162. // }}}
  163. // {{{ Rendering, see http://agiletoolkit.org/learn/understand/api/exec
  164. /** Recursively renders all views. Calls render() for all or for the one being cut. In some cases
  165. * you may want to redefine this function instead of render(). The difference is that this function
  166. * is called before sub-views are rendered, but render() is called after.
  167. *
  168. * function recursiveRender(){
  169. * $this->add('Text')->set('test');
  170. * return parent::recursiveRender();
  171. * }
  172. **/
  173. function recursiveRender(){
  174. $cutting_here=false;
  175. $this->initTemplateTags();
  176. if(isset($_GET['cut_object']) && ($_GET['cut_object']==$this->name || $_GET['cut_object']==$this->short_name)){
  177. // If we are cutting here, render childs and then we are done
  178. unset($_GET['cut_object']);
  179. $cutting_here=true;
  180. }
  181. foreach($this->elements as $key=>$obj){
  182. if($obj instanceof AbstractView){
  183. $obj->recursiveRender();
  184. $obj->moveJStoParent();
  185. }
  186. }
  187. if(!isset($_GET['cut_object'])){
  188. if(isset($_GET['cut_region'])){
  189. $this->region_render();
  190. }else{
  191. $this->render();
  192. }
  193. }
  194. if($cutting_here){
  195. $result=$this->owner->template->cloneRegion($this->spot)->render();
  196. if($this->api->jquery)$this->api->jquery->getJS($this);
  197. throw new Exception_StopRender($result);
  198. }
  199. // if template wasn't cut, we move all JS chains to parent
  200. }
  201. /** @internal Append our chains to owner's chains. JS chains bubble up to API or object being cut */
  202. function moveJStoParent(){
  203. $this->owner->js=array_merge_recursive($this->owner->js,$this->js);
  204. }
  205. /** Default render. Generates HTML presentation of the view based on $this->template and passes
  206. * it to output() function which then inserts output into parent's template */
  207. function render(){
  208. /**
  209. * For visual objects, their default action while rendering is rely on SMlite engine.
  210. * For sake of simplicity and speed you can redefine this method with a simple call
  211. */
  212. if(!($this->template)){
  213. throw $this->exception("You should specify template for this object")
  214. ->addMoreInfo('object',$this->name);
  215. }
  216. if($this->model && is_object($this->model) && $this->model->loaded())$this->template->set($this->model->get());
  217. $this->output($this->template->render());
  218. }
  219. /** Low level output function which append's to the parent object's template. Normally you wouldn't want
  220. * to use this function but should modify $this->template instead. */
  221. function output($txt){
  222. if(!$this->hook('output',array($txt))){
  223. if((isset($this->owner->template)) && (!empty($this->owner->template)))
  224. $this->owner->template->append($this->spot,$txt,false);
  225. }
  226. }
  227. /** When cutting, perform selective render for a region */
  228. function region_render(){
  229. /**
  230. * if GET['ajax'] is set, we need only one chunk of a page
  231. */
  232. if($this->template_flush){
  233. if($this->api->jquery)$this->api->jquery->getJS($this);
  234. throw new Exception_StopRender($this->template->cloneRegion($this->template_flush)->render());
  235. }
  236. $this->render();
  237. if($this->spot==$_GET['cut_region']){
  238. $this->owner->template_flush=$_GET['cut_region'];
  239. }
  240. }
  241. /** When cutting, perform selective render for an object */
  242. function object_render(){
  243. /**
  244. * if GET['cut'] is set, then only particular object will be rendered
  245. */
  246. if($this->name==$_GET['cut_object'] || $this->short_name==$_GET['cut_object']){
  247. $this->recursiveRender();
  248. if($this->template)echo $this->template->render();
  249. else $this->render();
  250. return false;
  251. }
  252. }
  253. // }}}
  254. // {{{ Object JavaScript Interface
  255. public $js=array();
  256. /**
  257. * Function js() will return jQuery chain and, if first argument was specified, bind
  258. * the chain to a certain enent. Use js() to bind views with JavaScript plugins and
  259. * calls defined in univ() chain or in 3rd party plugins.
  260. *
  261. * js([action], [other_chain]);
  262. *
  263. * Action can represent javascript event, such as "click" or "mouseenter". If you
  264. * specify action = true, then the event will ALWAYS be executed on pageload. It
  265. * will also be executed if respective view is being reloaded by js()->reload()
  266. * (Do not make mistake by specifying "true" instead of true)
  267. *
  268. * action = false will still return jQuery chain but will not bidn it. You can bind
  269. * it by passing to a different object's js() call as 2nd argument or by executing
  270. * chain.
  271. *
  272. * 1. Calling with arguments:
  273. *
  274. * $view->js(); // does nothing
  275. * $a = $view->js()->hide(); // creates chain for hiding $view but does not
  276. * // bind to event yet.
  277. *
  278. * 2. Binding existing chains
  279. * $img->js('mouseenter', $a); // binds previously defined chain to event on
  280. * // event of $img.
  281. *
  282. * Produced code: $('#img_id').click(function(ev){ ev.preventDefault(); $('view1').hide(); });
  283. *
  284. * 3. $button->js('click',$form->js()->submit());
  285. * // clicking button will result in form submit
  286. *
  287. * 4. $view->js(true)->find('.current')->text($text);
  288. *
  289. * Will convert calls to jQuery chain into JavaScript string:
  290. * $('#view').find('.current').text('abc'); // The $text will be json-encoded
  291. * // to avoid JS injection.
  292. *
  293. * 5. ON YOUR OWN RISK
  294. *
  295. * $view->js(true,'alert(123)');
  296. *
  297. * Will inject javascript un-escaped portion of javascript into chain. If you need to have
  298. * a custom script then put it into file instead, save into templates/js/myfile.js and then
  299. * include:
  300. *
  301. * $view->js()->_load('myfile');
  302. *
  303. * It's highly suggested to bind your libraries with jQuery namespace by registered them
  304. * as plugins, this way you can call your function easily:
  305. *
  306. * $view->js(true)->_load('myfile')->myplugin('myfunc',array($arg,$arg));
  307. */
  308. function js($when=null,$code=null,$instance=null){
  309. // Create new jQuery_Chain object
  310. if(!isset($this->api->jquery))throw new BaseException("requires jQuery or jUI support");
  311. // Substitute $when to make it better work as a array key
  312. if($when===true)$when='always';
  313. if($when===false || $when===null)$when='never';
  314. if($instance && isset($this->js[$when][$instance])){
  315. $js=$this->js[$when][$instance];
  316. }else{
  317. $js=$this->api->jquery->chain($this);
  318. }
  319. if($code)$js->_prepend($code);
  320. if($instance){
  321. $this->js[$when][$instance]=$js;
  322. }else{
  323. $this->js[$when][]=$js;
  324. }
  325. return $js;
  326. }
  327. // }}}
  328. }