New way of using helpers

Currently, we largely rely on the static helper class CHtml to write most HTML view code. This class, although very powerful, is not very easy to be extended due to the fact that all its methods are static.

So I am thinking about new ways to define and use helpers (mainly view helpers). One way that came to my mind after I implemented the behavior feature (available since 1.0.2) is to define a helper as a behavior and plug it into the controller. For example,



class MyController extends CController


{


     public function behaviors()


     {


           return array(


                'html'=>'CHtml',


                'html2'=>'AnotherHelper',


           );


     }


}


Now in the controller view, we can use $this->activeLabel() instead of CHtml::activeLabel. If we want to change to a different helper, we just need to modify the behaviors method without touching the view code.

My main concern about this approach is that the view code may be difficult to read by maintainers since those methods seem to be magical.

So another approach is that we explicitly declare a controller member variable, for example, named 'html':



$this->html=new CHtml;


Now in the view code, we can use $this->html->activeLabel(). Of course this needs more typing than the first approach. It is, however, easier to read and is more maintainable in future.

Do you have any suggestions or new ideas? Or are you satisfied with the current CHtml approach?

I was never confident with the way Yii did helpers, as they had to be static, so as you said they were hard to extend, and (although I don't have much experience with static methods and variables) could not have as much "dynamic" functionality.

About your new proposal:

What if two helpers have a method with the same name? That is a huge drawback for your new proposal in my opinion.  If helper A and B both have a method named foo(), Yii won't know which to call.

Also, less readable.

For instance does this make sense?

$this->encode();

Not really.  Encode what into what?  This makes much more sense:

Json::encode()

or

JavaScript::encode()

The method is like the verb while the class is the noun.  Verbs without a noun don't make as much sense.

$this->helper->method() does not seem like a bad idea to me, if Yii automatically set up the helper class variables so you don't have to.  Right now there does not seem like much you can do with controller attributes anyways.

BTW, how cake did it, is the helpers were passed to the views through normal variables (and hoped there were no clashing with other user variables).

So I could use the form helper like so (they had a different form and html helper - and the form helper had a lot of magic in it):

$form->start(); //starts a form or something

I never experienced and "variable clashing" however.  Yii could throw an error if any variables ever clash, so people won't be confused if that ever happens.

Yes, if the controller has a 'html' behavior, you can also refer to the methods of the behavior via $this->html->xyz(). No additional setup work is needed.

The way cake does is also fine. The drawback is the naming clashing as you described (the behavior way also has this problem). Another drawback is that you have to instantiate all the helpers even if you use none of them in an action.

Maybe we can use the cakephp approach, but enforce some naming conventions to reduce the possibility of name clashing?

For example, all helper variables are prefixed with 'h'. So we can use $hh to represent HTML helpers.

BTW, I’d like to hear other aspects that you are not confident about Yii. ;)

Hmm, yes it would not be able to use lazy-loading that way, I did not think of that.

$this->html->doIt()      //19 chars


CHtml::doIt()            //13 chars

Quote

So we can use $hh to represent HTML helpers.

What? So the var name would be the first letter in the name of the helper prefixed with h?  What if two helpers start with the same letter?  Or did I read you wrong?

What cake did actually is you defined an array of helpers in a controller attribute to be loaded in the view.

Because of the lazy loading feature, my vote goes to $this->helper->method()

Or, what if keeps the current configuration but the classes use the singleton method? (http://us.php.net/manual/en/language.oop5.patterns.php)  This way they would not be static.

Or what if a had a helper factory helper?

Helper::CHtml->link();

where Helper is a (static,singleton) factory class?

I think there are a lot of options out there.

I think we probably will go this way:

In CController, we add a new method called helpers(), which returns an array of helper configurations (helper name=>helper class and properties).

We change CController::__get() so that helpers can be accessed like a class member (similar to application components). A helper is created the first time it is accessed.

Create a new class named CHtmlHelper which is similar to CHtml but should be instantiated. And the default helpers() method would return array('html'=>'CHtmlHelper') so that we can use $this->html->xyz() to do things that were done using CHtml::xyz() previously.

I can foresee several benefits with this approach. The most important one is that a helper can now be configured! For example, if you want to use a different CSS class for error input fields, you can configure the HTML helper in helpers() method. You can also replace, extend or plugin helpers easily.

There are some drawbacks. For example, how to use helpers in a widget, especially this widget is released as an extension? Should we fix some helper namings so that these widgets can always use 'html' helper?

What do you think?

Quote

Create a new class named CHtmlHelper which is similar to CHtml but should be instantiated. And the default helpers() method would return array('html'=>'CHtmlHelper') so that we can use $this->html->xyz() to do things that were done using CHtml::xyz() previously.

What would the purpose of another Html helper be?  What would be different?  What makes the html helper special versus the other helpers that it gets special care like this?  And would you only be able to use $this->helper if that helper is configured in helpers()?  So by default you could only access the html helper via $this->html?

Quote

There are some drawbacks. For example, how to use helpers in a widget, especially this widget is released as an extension? Should we fix some helper namings so that these widgets can always use 'html' helper?

So this is a problem because widgets don't inherit from controllers?  What if instead of this new helper logic being in the controllers, maybe yii should have a whole new class for it that is passed to the views through $helpers

So in a view I could do:

$helpers->helperName->method(); //or just $h->helper->method();

So it's not dependent on the controller anymore

Being able to configure helpers would be great, especially if you could configure it in some config file like other components.

CHtmlHelper is nearly the same as CHtml except that CHtmlHelper needs to be instantiated first.

The html helper is special because it is needed by nearly every view, and we also want one so that it can be shared by all extensions. So we will define a getHtml() method in CController so that you can access it via $this->html.

And the object is created the first it is accessed.

The default helpers() method is empty. If a helper 'xyz' is declared inside it, you will be able to access the helper object via either $this->xyz or $this->getHelper('xyz') (this is very similar to application component).

In case you declare an 'html' helper inside helpers(), when you access $this->html, you will get this helper instead of the one returned by getHtml().

Regarding widget views, we can modify CWidget so that when you access $this->xyz ($this refers to the widget), you are actually getting the controller's xyz helper. So it won't be too big a problem.

Sounds good, but how hard would it be to use other core helpers inside of extensions?  What if you decide to add a time helper or something to the core that should be excessable to all extensions?

Maybe all the helpers should be accesable through $this->helper by default? not just the html one?

Yes, all helpers can be accessed via $this->helper. I already mentioned that. The html helper is just a special one that is predeclared so that extensions can be sure such a helper exists.

I say go for it then.  I'm surprised no one else is commenting

Quote

I say go for it then.  I'm surprised no one else is commenting
Be sure, we are reading at least  ;D

Any updates on this? I think Yii::app()->helpers->foo->bar() is a good solution.

Im SO reading this! :D

I’ve been waiting for this actually, as im not much of a JQuery lover (hope no one here throws anything at me) Ive faced this problem before (The CHtml static stuff) because Ive have made some Mootools based widgets, and still amke some, one day I decided to me Mootools someway a core thing.

So I started with making a new CHtml that would generate Mootools javascript instead of JQuery javascript, then I found the damm (sorry) registerCoreScript, so I also created a new ClientScript that used a new configuration.php file so that It can support other Javascript frameworks (I <3 mootools) but then… well the story goes on… until the point I gave up because of time and frustration.

So I would love a Factory approach to this.

And what about “ApplicationHelpers” we already have “ApplicationComponents” ;)

in main.php


'helpers' =>array(

        	'html' => array(

        		'class' => 'application.components.CHtmlHelperFactory',

        		'jsFramework' => 'mootools'

        	),

        	'image'=>array(

                        'class'=>'application.extensions.image.CImageComponent',

                        'driver'=>'GD',

            ),

        	

        ), 

And use it like :

in a view:

$this->html->activeInp…

in a controller:

$this->image->load("someimag.jpg")->resize(100, 100)->render();

Making those helpers avaliable to controllers and widgets.

I dont know just some ideas, as long as Extending CHtml and the builtin jquery code get easier ill be happy =P

Alex,

OMG just noticed the post date :S, iI guess its now too late…