Templated views

1 & 2 Sounds good to me, item 3 is what would normally occur in 90% of the cases, except for the example I have shown. The unassigned attribute "body" (CHtml::link) is a special case in which I allow the helper to capture the output of its body as a passed in value. So if you want to use an image for a link you could do something like

<helper:CHtml:link …><helper:CHtml:image …/></helper:CHtml:link>

Currently only CHtml::link() has a "body" attribute which is applicable, but it may be useful for future "helpers"

nz

I think the parser should not handle special cases (e.g. CHtml) inside. Make it as general as possible. If we want something like: <helper:CHtml:link …><helper:CHtml:image …/></helper:CHtml:link>, we should create image widget and use <com:> tag to accomplish the goal.

Sounds good to me, it was getting a bit hairy trying to do that  ;D

nz

Ok input code



<h2>


	Welcome, <?php echo Yii::app()->user->username; ?>!


</h2>


<p>


This is the homepage of <em><?php echo Yii::app()->name; ?></em>. You may modify the following files to customize the conent of this page:


</p>


<dl>


	<dt><?php echo Yii::app()->controllerPath . DIRECTORY_SEPARATOR . 'SiteController.php'; ?></dt>


	<dd>This file contains the <tt>SiteController</tt> class which is


	the default application controller. Its default <tt>index</tt> action


	renders the content of the following two files.


	</dd>


	<dt><?php echo __FILE__; ?></dt>


	<dd>This is the view file that contains the body content of this page.</dd>


	<dt><?php echo Yii::app()->layoutPath . DIRECTORY_SEPARATOR . 'main.php'; ?></dt>


	<dd>This is the layout file that contains common presentation (such as header, footer)	shared by all view files.</dd>


</dl>








<com:CCaptcha ButtonType="link" ShowRefreshButton="{true}">


<br />


</com:CCaptcha>


<com:CMultiFileUpload Name="mup"/>


Output code



<h2>


	Welcome, <?php /** Line:2-2*/  echo Yii::app()->user->username;   ?>!


</h2>


<p>


This is the homepage of <em><?php /** Line:5-5*/  echo Yii::app()->name;   ?></em>. You may modify the following files to customize the conent of this page:


</p>


<dl>


	<dt><?php /** Line:8-8*/  echo Yii::app()->controllerPath . DIRECTORY_SEPARATOR . 'SiteController.php';   ?></dt>


	<dd>This file contains the <tt>SiteController</tt> class which is


	the default application controller. Its default <tt>index</tt> action


	renders the content of the following two files.


	</dd>


	<dt><?php /** Line:13-13*/  echo __FILE__;   ?></dt>


	<dd>This is the view file that contains the body content of this page.</dd>


	<dt><?php /** Line:15-15*/  echo Yii::app()->layoutPath . DIRECTORY_SEPARATOR . 'main.php';   ?></dt>


	<dd>This is the layout file that contains common presentation (such as header, footer)	shared by all view files.</dd>


</dl>








<?php /** Line:20-20*/ $this->beginWidget('CCaptcha',array( "buttonType"=>'link',"showRefreshButton"=>true,));   ?>


<br />


<?php /** Line:22-22*/  $this->endWidget('CCaptcha');   ?>


<?php /** Line:23-23*/ $this->beginWidget('CMultiFileUpload',array( "name"=>'mup',));  $this->endWidget('CMultiFileUpload');   ?>


(For widgets the first letter of the attribute name is lower cased automatically)

Thoughts ?

nz

The widget code looks good to me. Maybe <com:Widget /> can be translated to <?php $this->widget('Widget'); ?> instead of (beginWidget, endWidget) pair?

Also, maybe support translation from <%= expr; %> to <?php echo expr; ?> ?

Really nice work! Can I take a look at how your code about renderFile() method? Do not include the parsing part.

The <%= %> are already there but my sample did not include them, Ill add in the widget thing…

here is the renderFile methods, is the implementation about right ?



  /**


   * Called by the context to render the file,


   * calls the getViewFileInternal to interrupt the working file


   */


  public function renderFile($context,$file,$data,$return) {


    // Call the context to render the template file


		return $context->renderInternal($this->getViewFileInternal($file),$data,$return);


  }





	/**


	 * Looks for the view file in the work folder.


	 * it is assumed the $viewFile points to a valid file.


	 *


	 * @param $viewFile the template file


	 * @param $chmod The folder attributes to set if folders need to be created


	 * @param $force True if you want to force the recompile of the view


	 *


	 * @return returns a file path to the "working" file.


	 */


	public function getViewFileInternal($viewFile, $chmod=755, $force=false)


	{


    // Convert the view file to the work dir folder


    $newFilePath = $this->_workPath . substr($viewFile,$this->_basePathLen);





    if (!is_file($newFilePath) ||


        constant('YII_DEBUG')===true ||


        $force) {





      // Check to see if directory exists


      preg_match('`^(.+)[/\\]([a-zA-Z0-9]+.[a-z]+)$`i', $newFilePath, $matches);





      $directory = $matches[1];


      if (!is_dir($directory)){


        if (!mkdir($directory, $chmod, 1)){


        return FALSE;


        }


      }





      // If in debug mode check the dates on the file


      if (constant('YII_DEBUG')===true && is_file($newFilePath)) {


        if (filemtime($viewFile)>filemtime($newFilePath)) {


          // Call the parser to generate the file


          $this->generateFile($viewFile,$newFilePath);


        }


      }


      else {


        // Call the parser to generate the file


        $this->generateFile($viewFile,$newFilePath);


      }


    }





    // Return the working file path


		return $newFilePath;


	}


Yeah, that's what I expected, but a slight difference.

I was thinking that the generated files should be put under the runtime directory since it is already configured to be writable. The directory structure under the runtime should look like the following



/protected/runtime/


	views/


		23ac234ef/


			view1.php


			view2.php


		423aef3ec/


			view1.php


			view3.php        


where '23ac234ef' and '423aef3ec' are the CRC encoding of the directory part of the corresponding view files. For example, if we are rendering 'view1' of SiteController, then the CRC path would be generated using:



sprintf('%x',crc32(dirname($sourceViewFilePath)))


I also realized that using this parsing approach, we changed the value of FILE. So it needs to be careful. Maybe a substitution of FILE, too?

Hi

Okay I modified the code to default the work path to the runtime/views folder



	public function getViewFileInternal($viewFile, $chmod=755, $force=false)


	{


    // Convert the view file to the work dir folder


    $pathName = dirname($viewFile);


    $fileName = basename($viewFile);


    


    $newFilePath = $this->_workPath . DIRECTORY_SEPARATOR .sprintf('%x', crc32($pathName)) . 


                   DIRECTORY_SEPARATOR . $fileName;





    if (!is_file($newFilePath) ||





I also replaced the FILE constant as I rendered the php code on the template, and I added a Visibility attribute to components or helpers

so this code

<com:CMultiFileUpload Name="mupHidden" Visibility="{false}"/>

renders this

<?php /** Line:25-25*/if (false===true)  $this->widget('CMultiFileUpload',array( "name"=>'mupHidden',));  ?>

That's great, although I don't think the visibility property is quite necessary.

Congratulations for the nice work!

thanks for the guidance, looking forward to the 1.0 beta release

NZ

Hi

I was thinking of adding a couple of more tags like <view: and <cache: and have them call the beginContent/endContent and beginCache/endCache but after looking at the CBaseController I see those method calls simply pass the calls to the beginWidget / endWidget methods. So (if I added those two extra tags) should the generated code invoke the widgets directly or should I still call methods in the CBaseController ?

Thanks

NZ

yeah, adding some new tags seems to be a great idea.

I would suggest using <yii:cache>, <yii:content> and <yii:clip>. You should call the corresponding CBaseController methods instead of widget methods. Also, pay attention to the <yii:cache> tag as its usage involves an "if" statement. It would be great the parser can automatically insert the if statement.

A quick question: how do you handle quote escaping? For example, if a property value contains both single and double quotes, will you parser handle this?

Every attribute is encoded like this

  protected static function encodeParamaterValue($value) {

    $value = htmlspecialchars_decode($value)

    if (substr($value,0,1)=="{" && substr($value,-1)=="}") {

      return substr($value,1,-1);

    }

   

    $escaped = preg_replace('{([&#039;\\])}', '\\$1', $value);

    $literal = '&#039;' . $escaped . '&#039;';

    return $literal;

  }

I'll add in those tags also.

NZ

Added $value = htmlspecialchars_decode($value) to function

What I mean is if I enter the following:



<com:MyWidget name='It's my name' />


Will your parser handle this situation?

The parser only allows double quoted attribute values so that would not be a valid tag

This would be valid

<com:MyWidget name="It's my name" />

and would be evaluated as array("name"=>'It&#039;s my name')

This would be illegal syntax

<com:MyWidget name="It"s my name" />

This would be valid

<com:MyWidget name="It&quot;s my name :&quot; />

and evaluated as array("name"=>'It"s my name :\')

NZ

With latest modifications :

Input



<h2>


	Welcome, <%= Yii::app()->user->name; %>!


</h2>


<p>


This is the homepage of <em><%= Yii::app()->name; %></em>. You may modify the following files to customize the conent of this page:


</p>


<yii:cache id="MainCache">


  Cached data


</yii:cache>


<dl>


	<dt><%= Yii::app()->controllerPath . DIRECTORY_SEPARATOR . 'SiteController.php'; %></dt>


	<dd>This file contains the <tt>SiteController</tt> class which is


	the default application controller. Its default <tt>index</tt> action


	renders the content of the following two files.


	</dd>


	<dt><%= __FILE__ %></dt>


	<dd>This is the view file that contains the body content of this page.</dd>


	<dt><%= Yii::app()->layoutPath . DIRECTORY_SEPARATOR . 'main.php'; %></dt>


	<dd>This is the layout file that contains common presentation (such as header, footer)	shared by all view files.</dd>


</dl>


outside of clip


	<yii:clip id="myClip" Visibility="{true}">


  	<yii:view name="/testview">


    inside


    </yii:view>


  </yii:clip>


out of clip





<com:system.web.widgets.CCaptcha


      ButtonType="link"


      ShowRefreshButton="{true}">


<br />


</com:system.web.widgets.CCaptcha>


<com:CMultiFileUpload Name="mup" Visibility="{false}"/>


<%= $this->clips['myClip'] %>


output



<?php /* Source template file C:tempxamppxampplitehtdocstestdriveprotectedviewssiteindex.php */ ?><h2>


	Welcome, <?php /** Line:2-2*/ echo  Yii::app()->user->name; ;   ?>!


</h2>


<p>


This is the homepage of <em><?php /** Line:5-5*/ echo  Yii::app()->name; ;   ?></em>. You may modify the following files to customize the conent of this page:


</p>


<?php /** Line:7-7*/ if ($this->beginCache('MainCache',array()) ) {   ?>


  Cached data


<?php /** Line:9-9*/ $this->endCache(); }    ?>


<dl>


	<dt><?php /** Line:11-11*/ echo  Yii::app()->controllerPath . DIRECTORY_SEPARATOR . 'SiteController.php'; ;   ?></dt>


	<dd>This file contains the <tt>SiteController</tt> class which is


	the default application controller. Its default <tt>index</tt> action


	renders the content of the following two files.


	</dd>


	<dt><?php /** Line:16-16*/ echo  'C:\temp\xampp\xampplite\htdocs\testdrive\protected\views\site\index.php' ;   ?></dt>


	<dd>This is the view file that contains the body content of this page.</dd>


	<dt><?php /** Line:18-18*/ echo  Yii::app()->layoutPath . DIRECTORY_SEPARATOR . 'main.php'; ;   ?></dt>


	<dd>This is the layout file that contains common presentation (such as header, footer)	shared by all view files.</dd>


</dl>


outside of clip


	<?php /** Line:22-22*/if (true===true) {  $this->beginClip('myClip',array()) ;     ?>


  	<?php /** Line:23-23*/ $this->beginContent('/testview',array()) ;    ?>


    inside


    <?php /** Line:25-25*/ $this->endContent();    ?>


  <?php /** Line:26-26*/ $this->endClip();  }   ?>


out of clip





<?php /** Line:29-31*/ $this->beginWidget('system.web.widgets.CCaptcha',array( "buttonType"=>'link',"showRefreshButton"=>true,));    ?>


<br />


<?php /** Line:33-33*/  $this->endWidget('system.web.widgets.CCaptcha');    ?>


<?php /** Line:34-34*/if (false===true) {  $this->beginWidget('CMultiFileUpload',array( "name"=>'mup',));  $this->endWidget('CMultiFileUpload');  }   ?>


<?php /** Line:35-35*/ echo  $this->clips['myClip'] ;   ?>


The only "bad" issue is (as you pointed out before) if the compiled file has a bad attribute set it the error could be misleading as to which physical view file is at fault.

NZ

This now looks nearly perfect! Is <helper:> still in? How to use that?

One minor suggestion: remove Visibility or rename it to be Visible (easier to input).

Regarding error reporting, I think your solution is fine as you already inserted line numbers and file names in the compiled files. That's very good.

Yup still there



   <helper:CHtml:link body="Welcome" 


      Url="{array('login')}" 


      HtmlOptions="{array('confirm'=>'Are You Sure')}" />, 


   <%= Yii::app()->user->name; %>!


Outputs



   <?php /** Line:2-3*/ echo CHtml::link( 'Welcome',array('login'),array('confirm'=>'Are You Sure'));   ?>, 


<?php /** Line:4-4*/ echo  Yii::app()->user->name; ;   ?>!





I have changed the Visibility attribute to be called Visible

Is there a reason why we need to obfuscate the folders we are compiling the templates into (by using the crc32 function) ?

Thanks

NZ

Without obfuscating the folder, you would end up with deeply nested directories. And if on windows, this would be very tricky if you attempt to keep the original directory structures.

One more suggestion, maybe we should use name={expr} instead of name="{expr}" ? The quotes are just extra typing that is not really necessary.