Obfuscate Form Fields Name And Id.

In general, I am against letting model decide the form input names. It’s not about MVC worshipness.

A practical reason is that a model can be used in different form configurations. For example, the user model may be used in a search form (using GET method) which doesn’t like the model class in the input names. It may also be used in a user registration form (using POST method) in which the name format isn’t that critical. Or it may be used in a tabular form.

[/size]

I agree. In fact, I didn’t like the model-part where the field names are mapped to text labels as this mapping is a views subject.

For Yii2, isn’t it possible to have an extra/intermediate class that maps field names to labels? This could improve consistency (both a user table and email-list table could have a field named ‘email’).

I think one root of all these problems is this vexatious CHtml class. It’s too heavy and the fact that it’s rather a container for helper methods than a real OO component makes it very hard to customize.

I wonder if maybe a new widget CHtmlForm would be an alternative. It would implement all the active* methods which are now in CHtml (similar to CActiveForm):




<?php $form = $this->beginWidget('CHtmlForm'); ?>

<?php echo $form->activeTextField(...) ?>



In my experience you hardly ever render an input tag without a form, thus a widget could make sense here. I[size="2"]nput names and ids would be generetated inside this CHtmlForm class and thus are open to modification. [/size][size="2"]CHtml could still be the expert on all things related to low-level HTML rendering: tags, attributes, etc. (but free of any [/size]limitations[size="2"]). [/size]

The form relatated static methods could als move into this new widget class:




<?php echo CHtmlForm::textField(...) ?>



Then CActiveForm could use a composition pattern to make this customizable:




<?php $form = $this->beginWidget('CActiveForm', array(

    'formClass' => 'CHtmlForm',  // default, but overridable

    ...



I’m currently working on the form related classes in Yii 2.

I have finished the static class Html, which as suggested here, only contains low-level HTML code generation without touching any model.

Next I plan to create an ActiveForm class. Its purpose is similar to CActiveForm in 1.1, but certainly with a lot of changes. Below are some changes in my mind:

  1. implement CHtml::active methods directly in ActiveForm. I do not plan to introduce another static class HtmlForm.

  2. support custom input name prefixes via class name mapping. So by configuring the ActiveForm property, you can choose to use "User[email]", "myuser[email]", or "name" as the input name for the user email input.

  3. better support of validation error handling. In 1.1, there are some discrepancy between js-based and server-based validation error displays, mainly because of the error class handling. I hope this can be fixed.

Anything else you would like to see in the new ActiveForm? Any suggestions?

In Yii 2, we will still have attributes() declaration in Model. I understand this in theory doesn’t belong to model because it’s about presentation. But I can’t think of a better place to hold it, and attribute labels are certainly useful in many places. Putting them in a separate class seems too much (and if you really want to do this, you can always customize the attributes() method in the base class to implement it).

This sounds good. I also agree to your arguments for the attributes() method: I see them as meta properties of a model, same as validation errors etc.

For ActiveForm i’d love to see an improved client side implementation. We’ve already discussed this for the GridView here. [size=“2”]Some features from the top of my head, which would be awesome:[/size]

  1. Make the clientside form a standalone plugin. So you can create new forms on the clientside - independent of any ActiveForm on the serverside.

  2. Utilize jQuery’s event system for all kind of interesting moments, so that users can attach their own event handlers on the clientside

  3. Provide methods to get/set form input values and validation errors/validation status




$('#my-form').yiiActiveForm({...});


// Get/set validation errors

errors = $('#my-form').yiiActiveForm('validation');

$('#my-form').yiiActiveForm('validation', { name: 'You have to enter a name', ...});




// Get/set values

values = $('#my-form').yiiActiveForm('values');

$('#my-form').yiiActiveForm('values', { name: 'Some name', ...});




// Trigger validation or submission

$('#my-form').yiiActiveForm('validate');

$('#my-form').submit();




// Bind event handler to interesting moments

//  (maybe use some custom Event objects for different types)

$('#my-form').on('beforevalidate', function(e) { ... } );



Qiang, I don’t know if this would be something for Yii2. (I’ll leave that to you!) But in one of my projects, I used a custom CHtml using classmap.

I changed the resolveValue so I could more easily use related data in things like activeListBox. I also changed activeId. If I recall I did that for some ajax validation.


	/**

	* Generates input field ID for a model attribute.

	* @param CModel $model the data model

	* @param string $attribute the attribute

	* @return string the generated input field ID

	*/

	public static function activeId($model,$attribute)

	{

		$activeId = self::getIdByName(self::activeName($model,$attribute));

		$activeId = str_replace(array('.'), array('_'), $activeId);

		return $activeId;

	}

		

	/**

	* Evaluates the attribute value of the model.

	* This method can recognize the attribute name written in array format.

	* For example, if the attribute name is 'name[a][b]', the value "$model->name['a']['b']" will be returned.

	* The attribute name can be a relation set in the Model->relations() array. In that case, pass the Foreign Key attribute.

	* For example as attribute: 'relationName[ForeignKeyattribute]'

	* @param CModel $model the data model

	* @param string $attribute the attribute name or relationName[ForeignKey]

	* @return mixed the attribute value

	* @since 1.1.3

	*/

	public static function resolveValue($model,$attribute)

	{

		if(($pos=strpos($attribute,'['))!==false)

		{

			if($pos===0)  // [a]name[b][c], should ignore [a]

			{

				if(preg_match('/\](\w+)/',$attribute,$matches))

				$attribute=$matches[1];

				if(($pos=strpos($attribute,'['))===false)

				return $model->$attribute;

			}

			$name=substr($attribute,0,$pos);

			$value=$model->$name;

			if( is_array($value) && isset($value[0]) )

			{

				if( $value[0] instanceof CActiveRecord )

				{

					$relatedModels = $value;

					$value = array();

				}

			}

			elseif(isset($value) && $value instanceof CActiveRecord)

			{

				$relatedModels[] = $value;

				$value = array();

			}

			foreach(explode('][',rtrim(substr($attribute,$pos+1),']')) as $id)

			{

				if(isset($_POST[get_class($model)][$name][$id]))

					$value=$_POST[get_class($model)][$name][$id];

				else if(is_array($value) && isset($value[$id]))

					$value=$value[$id];

				else if(isset($relatedModels))

				{

					foreach($relatedModels as $relatedModel)

					{

						$value[] = $relatedModel->__get($id);

					}

				}

				else

					return null;

			}

			return $value;

		}

		else

			return $model->$attribute;

	}