A Simple View

From GeeklogWiki
Revision as of 16:58, 16 May 2005 by Tony (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Those who have taken the time to learn what views are already know they represent the user interface by which our users will manipulate model objects or show information to the users. Geeklog 2 has a series of abstract classes that can be used by developers. Here is a brief over view of the hiearchy.

MVCnPHP_BaseView

This abstract view actually resides in the MVCnPHP package. It provides the author the ability to have a view require the use of SSL. It also provides two output options. The first and preferred output option is MVCnPHP_BaseView::MVC_PRINT. MVCnPHP::MVC_PRINT is a class level constant that tells the MVCnPHP library that the generated HTML should be printed to the browser. 1.3.x developers will find this in stark contrast to the 1.3.x method which is to build the HTML in a string variable where the developer would explicilty output (or echo) the variable. Though strongly discouraged (and generally untested yet), you can still use the 1.3.x output method which is MVCnPHP_BaseView::MVC_STRING. Finally, MVCnPHP_BaseView also provide access to the requestToObjects() method which can convert a form post from Geeklog 2 and generate the corresponding model objects on the fly so that you only have to work with the objects. If all this seems terribley complicated, don't worry, it should make sense when we get to the example.

Geeklog_BaseViewFlexy

This abstract view inherts from MVCnPHP_BaseView and, as the name suggests, it provides a handle to an instantiate PEAR::HTML_Template_Flexy (aka Flexy) object. The configuration options for Flexy are sent to the constructor by way of a PHP array that is in config.php. It will be important for Geeklog 2 developers to appreciate what this class is doing but intimate knowledge isn't so critical as the code examples that soon follow cover how to use it so that you can initially take a black-box approach to using this class.

Geeklog_BaseViewFlexyUser

This abstract class inhertis from Geeklog_BaseViewFlexy but it does so in a very important way. This class provides the foundation for security. Specifically, this class allows child views to tell it whether or not a valid user is required to view it *and* it provides access to the Gl2User object in the session. Additionally it provides some security related functions. NOTE: It is strongly urged that all views inherit from this class or one of it's descendants.

A Sample View

Ok, enough talk, let's show you a view that uses this framework. The contact manager application has a view called PHPARCH_ContactEditorView.php. You should pull this page up in your browser by logging into the application (username is tbibbs with any password). This will take you to the list of contact in the system (only 2). Select either one of the contacts by clicking on the name and that will take you to the contact editor. Take note of the URL in your browse, particularly the cmd=editContact. Now go to your IDE and open that class. You should initially be impressed with how little code it takes to show that page...a good chunk of that file is commends and whitespace. let's take that code section by section and explain what is happening.

10     protected function authorizedToView()
20	{
30	    $this->contact = new Contact(); 
40
50	    if (empty($_GET['cid'])) return true;
60	     
70	        $this->contact->setContactId($_GET['cid']);
80             $dao = PHPARCH_DAO::singleton();
90             $this->contact = $dao->get($this->contact);

100 110 // Make sure the contact is tied to the user trying to edit it. 120 if ($this->contact->getAccountId() == $this->user->getAccountId()) { 130 return true; 140 } 150 160 return false; 170 } 180 }

This method is called by the constructor which, in this case, doesn't exist but is fire by one of the anscestors (Geeklog_BaseViewFlexyUser.php to be exact). This method is responsible for determining if the user can even see this page. The basic logic implemented here checks

  1. Did we get an empty contact id? If so, we are creating a new link so let them on by.
  2. If we have a contact id, go get the contact object and ensure that the user who is trying to edit the contact actually owns the object.

I know a bit of magic happens in that methid, specifically, the fact that we were able to get the contact without explicity running any SQL (see lines 70-90). This was done using Propel and the Propel DAO. This will be convered later but hopefully you can begin to appreciate how simple it was get get the contact from the database.

Now let's take a look at processView(). Process view gets fired indirectly by the MVCnPHP controller. I'll go into more depth on the controller later but for now, just note that the controller is responsible for acting as the proxy for *all* requests. In this case, the requests comes in the form of the URL I told you to note before that had 'cmd=editContact'. That exerpt from the GET string tells the controller to show the contact editor and it does this by creating this view and firing the processView() method (again, this happens indirectly but that doesn't matter).

10     if (empty($_GET['cid'])) { 
20         $this->contact->setAddressRelatedByWorkAddressId(new Address());
30         $this->contact->setAddressRelatedByHomeAddressId(new Address());      
40     }        
50       
60     $_SESSION['contact'] = serialize($this->contact);

This code will check to see if we got a contact ID. If not, authorizedToView() would have already created the contact so, for now, we simply need to tie two new address objects to the contact. If this doesn't look clear, you might want to open the create.sql and look at the contact and address tables to get an understanding of the foreign key references.

Now, by the time we hit line 60, we either have the handle to a newly created contact or we have the handle to the persisted contact that we loaded from the database. At this time, we place the contact in the session...mainly for use when we go to save the contact which we cover a bit later. Now, if you have started to drift off, be sure to take note of the following bit of code as it is key to entire framework.

            $this->bindObjectToFields(array('Contact->firstName',
                                            'Contact->lastName',
                                            'Contact->nickName',
                                            'Contact->primaryEmail',
                                            'Contact->SecondaryEmail',
                                            'Contact->homeNumber',
                                            'Contact->workNumber',
                                            'Contact->mobileNumber',
                                            'Contact->faxNumber',
                                            'Contact->pagerNumber',
                                            'Contact->birthDate',
                                            'Contact->internetHompage')
                                      , $this->contact);

This bit of code calls a method on Geeklog_BaseViewFlexy that binds the contact object that we have to the fields on the form. To get a clear understanding of this, you'll need to open up the template file /path/to/sample/app/templates/ContactEditor.thtml. At first glance you'll notice that this is pretty much raw HTML, but a close inspection on one of the fields should raise some questions:

10    <tr>
20        <th align="left">First Name:</th>
30        <td><input type="text" name="Contact->firstName" /></td>        
40    </tr>

First, look at the funny looking field name 'Contact->firstName'. This wierd name is going to be a key time saver for us later and is tied, in part to our use of Propel but for now, just know that the field names in the template map cleanly to the fields listed in bindObjecToFields() call. With that one call, the contact that we created will have it's corresponding values show up automagically on the form in the template. For now you should black-box it all and now worry about how it work, just know that this is possible through combination of using Flexy and Propel.

In this same manner, we then bind the related work address fields in the template to the contact's work address as follows:

 10        $wAddress = $this->contact->getAddressRelatedByWorkAddressId();
 20        $this->bindObjectToFields(array('Address_Work->line1',
                                             'Address_Work->line2',
                                             'Address_Work->city',
                                             'Address_Work->zipCode')
                                       , $wAddress);

On line 10 above, we are getting the contact's work address and then we are binding it to the work address form fields in the template. Take note that we don't even touch the state field, yet. That's because we will need to handle that in a special way because it's control is a drop down.

10        if (is_object($wAddress)) {
20            $this->getDropDown('getEnabledListItems','Address_Work->stateId',array('STATES'),
30                $wAddress->getStateId(),'itemId','description','-- Select State --');
40        } else {
50            $this->getDropDown('getEnabledListItems','Address_Work->stateId',array('STATES'),
60                '','itemId','description','-- Select State --');
70        }

This bit of code is a bit complicated to look at but once you understand the framework better you will appreciate that what it is doing is quite simple. As I said, to this point we haven't built the state field on the form because it is a drop down that gets its values from the database. The call to getDropDown() refers to a method on Geeklog_BaseViewFlexy that knows how to build drop downs. In the IF part of the equation, we are building the drop-down in the case we have a valid address object already so that it will default the selcted valued int he drop-down to the right value. In the ELSE portion we are simply showing the drop-down without preselecting an item.

Assuming you understand how this is working, we then repeat this process for the home address. At this point, then, we have the Flexy template bound to our objects so now all we have to do is display the page:

10        $this->setPageTitle('PHPARCH - Contact Editor');
20        $this->showHeader(true);
30        $this->flexyHandle->compile('ContactEditor.thtml');
40        $this->flexyHandle->outputObject($this, $this->flexyElements); 
50        $this->showFooter();

These lines should be easy to understand. Line 10 sets the page title (i.e. what shows up in the <title> tags). It is worth noting that there is also a method called setMetaTags where you can provide the keywords to put in the meta tags. Line 20 will get the header (similar to COM_showHeader() in Geeklog 1.3.x). Line 30 tells Flexy to compile the template if needed. It is worth noting that Flexy will check the template to see if is has changed since last compilation. If it has it will recompile it, otherwise it will simply use the compiled PHP version of the template (this is very similar to how JSP's work in the Java world). Finally, Line 50 shows the footer.

Hopefully most of this makes sense. This example, though small, actually exercises almost all of what it will take to build pages in Geeklog 2, both for kernel specific pages and for the plugin views. Now let's go and learn about commands.