activeTextField and table relations

Hello all. I’m fairly new to Yii and am looking for some guidance in implementing a feature.

I have two models called Article and Company which have a many to many relationship. I’ve setup the model relationships no problem. What I’d like to do is enhance the Article create/update form to allow the linking of companies to that article. I’d like the form to be dynamic, so the user can add additional elements to the form as needed and submit more than one company at a time, as well as active, so as the user types, the matches show.

I’ve done some poking around and figure that using the tabular input approach described here in conjunction with some jQuery DOM manipulation is probably the way to go. But I’m running into some problems…

I’m trying to figure out how to wire the activeTextField with the list of all companies so that the active filtering mojo works. Any guidance or a nudge toward some example out there would be appreciated.

Take a look at this wiki and this extension.

Thanks, zaccaria. That helped!

I ended up simplifying the TabularInputManager for my own use, because I don’t need to have the user create Company assets on the fly, but just associate existing ones with Articles. I also took out the need to subclass it.

Here’s what it looks like now; I hope I didn’t trample on too many best practices :P




class TabularInputManager extends CComponent {

	

	public $_items;

	

	protected $class;

	

	protected $searchField;

	

	

	public function __construct($class, $searchField) {

		$this->class = $class;

		$this->searchField = $searchField;

	}

	

	public function manage($data) {

		

		$this->_items = array();

		

		$id = isset($data['id']) ? $data['id'] : null;

		$command = isset($data['command']) ? $data['command'] : null;

		$isDelete = ($command === 'delete');	

		

		foreach($data as $i=>$item_post) {	

			if($i === 'command' || $i === 'id') {

				continue;

			}

			

			if($isDelete && $id == $i) {

				continue;

			}

		

			$searchFieldValue = $data[$i][$this->searchField];

			if(!$searchFieldValue) {

				continue;

			}

			

			$item = $this->findItem($searchFieldValue);

			if(!$item) {

				$item = $this->newEmptyItem();

				$searchField = $this->searchField;

				$item->$searchField = $searchFieldValue;

				$item->addError($this->searchField, $this->class . ' does not exist.');

			}

			$this->_items[$i]=$item;

			

		}

	}

	

	protected function findItem($searchFieldValue) {

		$condition = $this->searchField . '=:' . $this->searchField;

		$paramKey =  ':' . $this->searchField;

		$item = CActiveRecord::model($this->class)->find($condition, array($paramKey => $searchFieldValue));

		return $item;

	}

	

	public function getItems() {

		if(!is_array($this->_items)) {

			return array();

		}

		return $this->_items;

	}	

	

	public function getTableItems() {

		$empty_item_array = array( $this->newEmptyItem() );

		if(!is_array($this->_items)) {

			return $empty_item_array;

		}

		return array_merge($this->_items, $empty_item_array);

	}


	protected function newEmptyItem() {

		return new $this->class();

	}

	

	public function getSize() {

		return count($this->_items);

	}

	

}




…and in my Article controller:




	public function actionCreate() {

		

		$model=new Article;

		$companyManager = new TabularInputManager('Company', 'name');


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Article'])) {


			$model->attributes=$_POST['Article'];

			$companyManager->manage($_POST['Company']);

		

			if(!isset($_POST['noValidate'])) {

				$model->companies = $companyManager->getItems();

				$valid=$model->validate();

				

				if($valid) {

					$model->save();

					$this->redirect(array('view','id'=>$model->id));

				}

			}

			

		}


		$this->render('create',array(

			'model'=>$model,

			'companyManager '=>$companyManager ,

		));

	}



The view code stayed mostly as in the examples given.

I had to make use of the cadvancedarbehavior extension to get the save to work on my MANY_MANY relationship.

It took longer than I expected, but damn, a great Yii learning experience. Now onto sexying it up with some Ajax lookups for when the user is typing in the company name…

So, as suggested by zaccaria, I’ve implemented the method described here, which allows the user to add/remove rows to the tabular input form (the view code is below). It works, but there’s another thing I’d like to do…

The user is assigning Company assets to an Article when using the tabular input. I’d like to have the matching Company names show as suggestions as the user is typing, sort of a live search. Is there a standard approach to doing that?

view code:

view/article/_form




<div class="form">


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

	'id'=>'article-form',

	'enableAjaxValidation'=>true,

)); ?>


    [... Article fields ...] 

	

	<h2>Company</h2>

	

	<table id="company">

		<thead>

			<tr>

				<td><?php echo Company::model()->getAttributeLabel('name'); ?></td>

				<td>

					<?php echo CHtml::link('add', '', array('onClick' => 'addCompany($(this))', 'class'=>'add')); ?>

				</td>

			</tr>

		</thead>

		<tbody>

			<?php foreach($companyManager->tableItems as $id=>$company): ?> 

				<?php $this->renderPartial('_formCompany', array('id' => $id, 'model' => $company, 'form' => $form)); ?>

			<?php endforeach;?>

		</tbody>

	</table>

	

	<script type="text/javascript">

		var lastCompany = <?php echo $companyManager->getSize(); ?>;

		var trCompany = new String(

			<?php

				echo CJSON::encode($this->renderPartial('_formCompany', array(

					'id' => 'idRep',

					'model' => new Company,

					'form' => $form),

				true));

			?>

		);

	 

		function addCompany(button) {

			button.parents('table').children('tbody').append(trCompany.replace(/idRep/g, ++lastCompany));

		}

	 

		function deleteCompany(button) {

			if(button.parents('tbody').children('tr').length == 1) {

				addCompany(button);

			}

			button.parents('tr').detach();

		}

	 

	</script>	


	<div class="row buttons">

		<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>

	</div>


<?php $this->endWidget(); ?>


</div><!-- form -->




view/article/_formCompany




<tr>

    <td>

        <?php echo $form->textField($model, "[$id]name", array('size' => 50, 'maxlength' => 255)); ?>

        <?php echo $form->error($model, "name"); ?>

     </td>

 

    <td> <?php echo CHtml::link('delete', '', array('class' => 'delete', 'onClick' => 'deleteCompany($(this))')); ?></td>

</tr>



Hopefully nobody minds the friendly bump. :)

I’m just wondering if anyone could point me towards an example or give me a slight push toward how the following is generally done in Yii: I want to have a field which, as the user types, shows possible matches pulled from the DB or wherever.

Thanks in advance!