Building My First Application with Zend, Part 4


As I mentioned in part 3, I went ahead and made models for the genres, studios, and series.  Then I started working on bulking up some of the interfaces.  For example, I wanted to see how many titles are in a set genre, series, etc. Let's look at the series area to see how this worked out. Note: for this post, I'm using … to indicate where I snipped out the rest of the functions/etc to keep the code briefer.  See the previous parts for the rest of the code.

In the SeriesTable.php file, I had to add a new use statement, and I rewrote my fetchAll function to instead utilize the Select function to prepare a joined statement. 

<?php
	namespace MovieShelves\Series\Model;
	
	use Zend\Db\TableGateway\TableGateway;
	use Zend\Db\Sql\Select;  // ADDED THIS USE STATEMENT

	class SeriesTable {
		protected $tableGateway;
		
		public function __construct(TableGateway $tableGateway) {
			$this->tableGateway = $tableGateway;
		}
		
		public function fetchAll() {  // COMPLETELY CHANGED 🙂 
			$select = new Select('movieshelves_series'); // name of the primarily table

			$select->join(
					'movieshelves_movies', // name of the table joining
					'movieshelves_series.seriesid = movieshelves_movies.series_seriesid',  // join condition
					array()  // which columsn from teh second table to get; if none, do the array() like I did here
				)
				->columns(array(
						'seriesid', 
						'seriesname', 
						'numberoftitles', 
						'isongoing' => new \Zend\Db\Sql\Expression("IF(isongoing=1, 'Yes', 'No')"), // for an if statement, I had to use the Expression function
						'serieswebsite', 
						'numOwned' => new \Zend\Db\Sql\Expression("COUNT(movieid)") // ditto for the count
					)
				)
				->group('seriesid') // dont' forget that group 😉
			;
			
			$resultSet = $this->tableGateway->selectWith($select);  // notice the original select has been replaced with selectWith and now we're passing in the select object we made
			
			return $resultSet;
		}	
		...
	}
?>

Then in the Series.php file, I had to add references to the new field so that it could be mapped.

<?php
	namespace MovieShelves\Series\Model;
	
	use Zend\InputFilter\InputFilter;
	use Zend\InputFilter\InputFilterAwareInterface;
	use Zend\InputFilter\InputFilterInterface;
	use Zend\Validator\NotEmpty;
	
	class Series {
		public $seriesid;
		public $seriesname;
		public $numberoftitles;
		public $isongoing;
		public $serieswebsite;
		public $numOwned; // ADDED for our new column
		protected $inputFilter;
		
		public function exchangeArray($data) {
			$this->seriesid = (isset($data['seriesid'])) ? $data['seriesid'] : null;
			$this->seriesname = (isset($data['seriesname'])) ? $data['seriesname'] : null;
			$this->numberoftitles = (isset($data['numberoftitles'])) ? $data['numberoftitles'] : null;
			$this->isongoing = (isset($data['isongoing'])) ? $data['isongoing'] : null;
			$this->serieswebsite = (isset($data['serieswebsite'])) ? $data['serieswebsite'] : null;
			$this->numOwned = (isset($data['numOwned'])) ? $data['numOwned'] : null;
		}
		...
	}
?>

And finally, I updated my index.phtml to update the view to add the new $numOwned column, resulting in:

I also discovered a flaw in my earlier model, where I was using !empty in my dataExchangeArray function.  This needed to be changed to !isset, otherwise it not only correctly cause missing data, but also wrongly changed 0 entries to NULLs!! Other than that, for now at least, the series add/edit/delete works all the same as the rest.  The only "big" difference is that it's add/edit has a radio button.  These ended up being a little trickier than I expected, due to some wonkiness with how Zend does Radio's with formRow.

In my SeriesForm.php, I added the Radio, following the general info in the docs.

$this->add(array(
	'name' => 'isongoing',
	'type' => 'Radio',
	'decorators' => $radioDecorator,
	'attributes' => array(
		'id' => 'numberoftitles',
		'required' => 'required',
	),
	'options' => array(
		'label' => 'Is Series On-going:',
		'value_options' => array(
			array(
				'value' => '0',
				'label' => ' No',
				'attributes' => array(
					'id' => 'isongoing_no',
				),
				'label_attributes' => array(
					'class' => 'noAppend pure-radio firstOption',
				),
			),
			array(
				'value' => '1',
				'label' => ' Yes',
				'attributes' => array(
					'id' => 'isongoing_yes',
				),
				'label_attributes' => array(
					'class' => 'noAppend pure-radio',
				),
			),
		),
	),
));

Now here is the fun bit with Zend.  The label that I put on the form element as a whole becomes the legend of a fieldset that wraps around the whole set of radios, while the individual option labels correctly become labels that wrap each option.  Because of this, it's important to style your fieldsets and legends appropriately. Catch #2 though – if you set label_attributes for the main label (i.e. the one that becomes the legend), they will NOT be passed to that legend, including any CSS classes!  Which gave me something rather ugly (IMHO).

At this point, my partner and I had decided to implement the Pure CSS modules to help with some basic stylings, in particular our forms, on our current project.  Why reinvent the wheel when we just use their already awesomely made form layouts? To help practice with that, I decided to implement it on my app here as well.  I added the pure-form and pure-form-aligned classes to my form, and the pure-control-group class to divs wrapped around each formRow. For each radio, I added a class of pure-radio.

Already much better, but I still don't like those radios! Since I'm also using CSS to auto add the : on our labels in general, I added the class of noAppend to each radio then updated my style so it wouldn't apply to the radios.  I added a class of firstOption to the first radio so I could indent the whole set and overrode some of Pure's handling of radios with their aligned forms.

But I still had to deal with the fieldset that Zend added  The only way to stop it was to manually dump each bit of the element, which seemed silly, so instead I gave the wrapping div a class of radiosRequired and turned to a bit of JavaScript to push down the styles I needed.

<?php
	$title = 'Add New Series';
	$this->headTitle($title);
	$this->layout()->pageTitle = $title;
	
	$form->setAttribute('action', $this->url('series', array('action' => 'add')));
	$form->prepare();
	
	echo $this->form()->openTag($form) . "\n";
	echo $this->formHidden($form->get('seriesid')) . "\n";
	echo '<div class="pure-control-group">' . $this->formRow($form->get('seriesname')) . "</div>\n\n";
	echo '<div class="pure-control-group">' . $this->formRow($form->get('numberoftitles')) . "</div>\n\n";
	echo '<div class="pure-control-group radiosRequired">' . $this->formRow($form->get('isongoing')) . "</div>\n\n";
	echo '<div class="buttonWrapper">' . $this->formButton($form->get('saveButton')) . "</div>\n";
	echo $this->form()->closeTag() . "\n";
?>
form {
	margin-top: 10px;
	margin-bottom: 10px;
}
label { font-weight: bold; }
.required {
	color: #900 !important;
	font-weight: bold !important;
}
.standardForm label:not(.noAppend)::after { content:": "; }
.radioGroup {
	width: 400px;
	text-align: left;
}
.radioGroup label:not(.firstOption) {
	margin: 0em 0.5em 0.2em !important;
	width: auto !important;
}
.firstOption { 
	margin: 0em 0.5em 0.2em 4em !important;
	width: auto !important;
}
.pseudoLabel {
	border: none !important;
	vertical-align: middle;
	width: 10em !important;
	text-align: right;
	padding-top: 0px;
	padding-right: 1em;
	padding-bottom: 0px;
	padding-left: 0px;
	display: block;
}
if($(".radiosRequired").length){
	$(".radiosRequired fieldset").addClass("radioGroup");
	$(".radiosRequired fieldset legend").addClass("required pseudoLabel");
}

The end result?

Much better! 🙂

P.S. We have three Zend books on the way 🙂