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 );

}

2 comments:

  1. Now that I have a better feel for how this works, I think it would have been better to create a basically empty controller for my custom string manipulations, then in the application configuration add that controller to the app and attach the various behaviors like this one that will be needed.

    Then I can just use: Yii::app()->Foo->checkLength($aString);

    ReplyDelete
  2. You should really only have one controller running per request though.

    In:
    Yii::app()->Foo->checkLength($aString);

    it looks like Foo could actually an application component (which would extend CApplicationComponent)

    Another option would be to create an active record behavior (which extends CActiveRecordBehavior)

    The route I probably would have gone personally would be creating a helper class (or extending CHtml)

    Your initial example is also very good

    ReplyDelete