Thursday, March 18, 2010

Custom Behavior Example/Test

Ok, so I originally thought I should write a widget for this, then I realized that this was a good example of a behavior that should be attachable to multiple components.

I want to give the ability for the page to trim a string if it's over a given length, and return the truncated string with an ellipsis or other marker. A quick search through the yii documentation/forums did not find anything already built that would do this (though I did find one article in polish that had a function for doing truncation ;) Alas, I do not read Polish).

I'm adding this link to the list of items to read on behaviors in Yii:
http://www.yiiframework.com/doc/guide/extension.use#behavior



First, I create my behavior class:
class CTruncator extends CBehavior{
   /** 
    * Extremely simplified truncation method  
    *    -- all these params could be made properties of the class.
    * @param $str string to truncate
    * @param $len length to truncate to
    * @param $ellipsis concatenation characters
    * @return string
    */
    public function truncate( $str, $len=100, $ellipsis="..." )
    {
        if ( strlen ( $str ) < $len )
        {
            // do not change the string, it's already short enough
            return $str;   
        }    

        // make the string the length of the ellipsis shorter than the desired length
        $tmp = substr( $str, 0 ( $len - strlen( $ellipsis ) ) );

        // return the modified string w/ ellipsis
        return $tmp.$ellipsis;
    }
} 
I save this to my /protected/components/ directory as CTruncator.php (I think it should actually go into the /protected/extensions/ directory, but I'm not going to tackle going outside the autoloading areas just yet) Now, I need to attach the behavior to one of my Controllers. I'm actually going to attach it to ALL of my Controllers, because I have a lot of controllers that are going to need this behavior, and its footprint is quite small. (Actually, what I'd to if I was going to be using this long term, would be to extend my Controller.php to MediaController.php and attach the behavior there, then for all models that I wish to have this ability in their CRUD files, I'd change their base extension class to MediaController, allowing me to keep the extra bloat out of other controllers -- but that's outside the scope of this example/test). So! To attach it to the controller, I override the controller's init() function as follows:
public function init()
   {
      $this->attachBehavior('checkLength', new CTruncator);  
   }
   ... 
Then, to utelize the behavior, in my _view.php file, I simply do:
<?php echo CHtml:encode( $data->Title )." by ".CHtml::encode( $data->Author ); ?>
   <br />
   <div class="quote">
      <?php echo $this->checkLength( $data->Entry, 200 ); ?>
   </div>

A more advanced version of the CTruncator would be: First, I create my behavior class:
class CTruncator extends CBehavior{

   protected $_len = 100;
   protected $_ellipsis = "...";

   /** 
    * Extremely simplified truncation method 
    * @param $str string to truncate
    * @param $len length to truncate to
    * @param $ellipsis concatenation characters
    * @return string
    */
    public function truncate( $str, $len = NULL, $ellipsis = NULL )
    {
        if ( $len === NULL ){
           $len = $this->_len; 
        }
        if ( $ellipsis === NULL ){
           $ellipsis = $this->_ellipsis;
        }
        if ( strlen ( $str ) < $len )
        {
            // do not change the string, it's already short enough
            return $str;   
        }    

        // make the string the length of the ellipsis shorter than the desired length
        $tmp = substr( $str, 0 ( $len - strlen( $ellipsis ) ) );

        // return the modified string w/ ellipsis
        return $tmp.$ellipsis;
    }

    public function setEllipsis( $s )
    {
         $this->_ellipsis = $s;
    }
    public funciton setLen( $x )
    {
         if (!is_numeric( $x ))
         {
             return;
         }
         $this->_len = $x;
    }
} 
Then, if I want to make it default differently for each controller, in the specific controller I can override the init as:
public function init()
{
     $trunc = new CTruncator();
     $trunc->setLen( 100 );
     $trunc->setEllipsis( '::.' );
    
     $this->attachBehavior( 'checkLength', $trunc );

}