Joomla开发实录

采用版本Joomla1.5、

第一步,在administrator/components/下建立com_reviews文件夹,然后创建toolbar.reviews.html.php

代码如下:

<?php
    defined( '_JEXEC' ) or die( 'Restricted access' );
    
    class TOOLBAR_reviews{
       function _NEW(){
            JToolBarHelper::save();
            JToolBarHelper::apply();
            JToolBarHelper::cancel();
       }
       function _DEFAULT(){
            JToolBarHelper::title(JText::_('Restaurant Reviews'),'generic.png');
            JToolBarHelper::publishList();
            JToolBarHelper::unpublishList();
            JToolBarHelper::editList();
            JToolBarHelper::deleteList();
            JToolBarHelper::addNew();
       }
       
    }
    
?> 

 

第二步,在administrator/components/下建立com_reviews文件夹,然后创建toolbar.reviews.php,代码如下

<?php 
    defined( '_JEXEC' ) or die( 'Restricted access' );
    require_once(JApplicationHelper::getPath('toolbar_html'));
    switch($task){
        case 'edit':
        case 'add':
            TOOLBAR_reviews::_NEW();
            break;
        default:
            TOOLBAR_reviews::_DEFAULT();
            break;
    }
?>

 

以上代码是希望得到以下效果:

可是输入以下url无法显示,报出404页面,http://localhost/joomla/administrator/index.php?option=com_reviews

 先不管,继续做下去,

第三步,创建数据库表

CREATE TABLE `jos_reviews`(
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`address` varchar(255) NOT NULL,
`reservations` varchar(255) NOT NULL,
`quicktake` text NOT NULL,
`review` text NOT NULL,
`notes` text NOT NULL,
`smoking` tinyint(1) unsigned NOT NULL default '0',
`credit_cards` varchar(255) NOT NULL,
`cuisine` varchar(31) NOT NULL,
`avg_dinner_price` tinyint(3) unsigned NOT NULL default '0',
`review_date` datetime NOT NULL,
`published` tinyint(1) unsigned NOT NULL default '0',
PRIMARY KEY(`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

 第四步,既然创建了数据库表,那么接下来自然而然就创建数据库表类,在 administrator/components/com_reviews 文件夹下,创建一个 tables 文件夹,然
后在里面创建 review.php 文件并输入一下代码:

<?php
    defined('_JEXEC') or die('Restricted access');
    //修改前class TableRevies extends JTable
class TableReview extends JTable{ var $id = null; var $name = null; var $address = null; var $reservations = null; var $quicktake = null; var $review = null; var $notes = null; var $smoking = null; var $credit_cards = null; var $cuisine = null; var $avg_dinner_price = null; var $review_date = null; var $published = null; function __construct(&$db){ parent::__construct('#__reviews','id',$db); } } ?>

  我们继承了 JTable 类,并加入数据表的所有字段作为类的成员变量,成员变量都初始化为 null。然后覆盖类的构造函数 __construct() ,__construct() 会带有一个数据库对象为参数,并调用父类的构造函数,以数据表名(以#__为前缀)、主键和数据库对象为参数值。

      说明:为什么要使用 #__ 为数据表的前缀?
      在 Joomla!编写查询和定义 JTable 扩展时,使用 #__ 代替 jos_。Joomla! 执行查询时会自动将 #__ 替换为 管理员选择的数据库前缀。 这样的好处是可以在同一个数据库中运行多套Joomla!。你随便修改数据库的前缀也不用修改代码。TableReview 类继承了  bing()、store()、load() 和  delete() 等函数,这四个函数可以让你不用写一行的 SQL 就可以管理数据库的记录。

第五步,创建好了数据表,我们需要有一个友好的界面来增加点评。添加admin.reivews.php,代码如下:

<?php 
    defined('_JEXEC') or die('Restricted access');
    require_once(JApplicationHelper::getPath('admin_html'));
    JTable::addIncludePath(JPATH_COMPONENT.DS.'tables');
    switch($task){
        case 'add':
            editReview($option);
            break;
    }
    function editReview($option){
        $row = &JTable::getInstance('Review','Table');
        $lists = array();
        $reservations = array(
            '0'=>array('value'=>'None Taken','text'=>'None Taken'),
            '1'=>array('value'=>'Accepted','text'=>'Accepted'),
            '2'=>array('value'=>'Suggested','text'=>'Suggested'),
            '3'=>array('value'=>'Required','text'=>'Required')
        );
        $lists['reservations'] = JHTML::_('select.genericList',$reservations,'reservations','class="inputbox"'.'','value','text',$row->reservations);
        $lists['smoking'] = JHTML::_('select.booleanlist','somking','class="inputbox"',$row->smoking);
        $lists['published'] = JHTML::_('select.booleanlist','published','class="inputbox"',$row->published);
        HTML_reviews::editReview($row,$lists,$option);
    }
    
?>

  使用  require_once(  JApplicationHelper::getPath(  'admin_html'  )  )  来包含admin.reviews.html.php 文件。getPath() 函数带一个字符串参数并返回与组件文件一致的绝对路径。尽管我们没有指定组件名,但是它会自动包含适当的文件,即使是改变了组件名也一样。使用require_once() 确保文件只被包含一次。
      addIncludePath() 成员函数会包含我们的数据表类,  addIncludePath() 会自动包含所有我们定义在 tables 目录下的数据表类。
      Joomla!设置 JPATH_COMPONENT 为后端代码的绝对路径。  DS 常量是指定的操作系统的目录分隔符。
      editReview() 函数准备了一些 HMTL元素然后传给显示函数 HTML_reviews::editReview()。

第六步,创建 admin.reviews.html.php文件并加入以下代码:

<?php
    defined('_JEXEC') or die('Restricted access');
    class HTML_reviews{
        function editReview($row,$lists,$option){
            $editor = &JFactory::getEditor();
            JHTML::_('behavior.calendar');
?>
    <form action="index.php" method="post" name="adminForm" id="adminForm">
        <fieldset class="adminform">
            <legend>Details</legend>
            <table>
                <tr>
                    <td width="100" align="right" class="key">Name:</td>
                    <td><input class="textarea" type="text" name="name" id="name" size="50" maxlength="250" value="<?php echo $row->name;?>"/></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Address:</td>
                    <td><input class="textarea" type="text" name="address" id="address" size="50" maxlength="250" value="<?php echo $row->address;?>"/></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Reservations:</td>
                    <td><?php echo $lists['reservations'];?></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Quicktake:</td>
                    <td><?php echo $editor->display('quicktake',$row->quicktake,'100%','150','40','5');?></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Review:</td>
                    <td><?php echo $editor->display('review',$row->review,'100%','250','40','10');?></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Notes:</td>
                    <td>
                        <textarea class="textarea" cols="20" rows="4" name="notes" style="width:500px">
                            <?php echo $row->notes;?>
                        </textarea>
                    </td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Smoking:</td>
                    <td><?php echo $lists['smoking'];?></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Credit Cards:</td>
                    <td><input class="textarea" type="text" name="credit_cards" id="credit_cards" size="50" maxlength="250" value="<?php echo $row->credit_cards;?>"/></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Cuisine:</td>
                    <td><input class="textarea" type="text" name="cuisine" id="cuisine" size="31" maxlength="31" value="<?php echo $row->cuisine;?>"/></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Average Dinner Price:</td>
                    <td><input class="textarea" type="text" name="avg_dinner_price" id="avg_dinner_price" size="5" maxlength="3" value="<?php echo $row->avg_dinner_price;?>"/></td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Review Date:</td>
                    <td>
                        <input class="textarea" type="text" name="review_date" id="review_date" size="25" maxlength="19" value="<?php echo $row->review_date;?>"/>
                        <input type="reset" class="button" value="..." onclick="return showCalendar('review_date','y-mm-dd');"/>
                    </td>
                </tr>
                <tr>
                    <td width="100" align="right" class="key">Published:</td>
                    <td><?php echo $lists['published'];?></td>
                </tr>
            </table>
        </fieldset>
        <input type="hidden" name="id" value="<?php echo $row->id;?>"/>
        <input type="hidden" name="option" value="<?php echo $option;?>"/>
        <input type="hidden" name="task" value=""/>
    </form>
<?php
        }
    }
?>

  在浏览器地址栏输入 http://localhost/joomla/administrator/index.php?option=com_reviews&task=add,然后希望我会看到以下界面效果:

悲剧呀,我看到的页面时这样的,到底啥原因呢?原来是我上面的数据表类写错了名字,把TableRevies改成TableReview,就一切正常了。先稍微解析一下,数据表类的命名与在第七步当中的$row = &JTable::getInstance('Review','Table');有关的。JTable::getInstance将TableReview 类的一个实例赋值给 $row。类的名字由第一个参数和第二个参数组合而成,第二个参数是第一个参数的前缀。就说到这,继续把代码敲下去

在上述代码中,JHTML::_('behavior.calendar');的JHTML::_() 做了什么?
Joomla!提供了很多自动生成HTML元素的函数,如下拉列表、复选框等。为了提供执行的效率,这些函数只有在需要的时候才会读到内存里。这个工作有 _() 函数来完成。

首先,JFactory::getEditor() 函数返回 HTML 编辑器,JHTML::_(‘behavior.calendar’) 函数会在 header 中加入JavaScript 和 CSS ,这是用在点评日期字段中弹出日历的代码: 
class HTML_reviews 

      function editReview( $row, $lists, $option ) 
     { 
            $editor =& JFactory::getEditor(); 
            JHTML::_('behavior.calendar'); 
编辑器对象的成员函数 display() 返回选择的富文本编辑器的 HTML , 如果富文本编辑器不存在就返回<textarea> 元素。
display() 函数带有以下的参数:表单变量名、值、宽、高、列数和行数。当没有使用HTML 编辑器,最后两个参数是 <textarea> 的大小。代码片段如下:
<td> 
    <?php 
        echo $editor­->display( 'quicktake',   $row­>quicktake , '100%', '150', '40', '5' ) ; 
    ?> 
</td> 

第七步,管理员填完表单并且当即保存按钮后,我们需要保存信息到数据库里。开始,在admin.reviews.php中创建 saveReview() 函数,代码如下:

<?php 
    defined('_JEXEC') or die('Restricted access');
    require_once(JApplicationHelper::getPath('admin_html'));
    JTable::addIncludePath(JPATH_COMPONENT.DS.'tables');
    switch($task){
        case 'add':
            editReview($option);
            break;
    }
    function editReview($option){
        $row = &JTable::getInstance('Review','Table');
        $lists = array();
        $reservations = array(
            '0'=>array('value'=>'None Taken','text'=>'None Taken'),
            '1'=>array('value'=>'Accepted','text'=>'Accepted'),
            '2'=>array('value'=>'Suggested','text'=>'Suggested'),
            '3'=>array('value'=>'Required','text'=>'Required')
        );
        $lists['reservations'] = JHTML::_('select.genericList',$reservations,'reservations','class="inputbox"'.'','value','text',$row->reservations);
        $lists['smoking'] = JHTML::_('select.booleanlist','somking','class="inputbox"',$row->smoking);
        $lists['published'] = JHTML::_('select.booleanlist','published','class="inputbox"',$row->published);
        HTML_reviews::editReview($row,$lists,$option);
    }
    
    function saveReview($option){
        global $mainframe;
        $row=&JTable::getInstance('review','Table');
        if(!$row->bind(JRequest::get('post'))){
            //使用 bind() 成员函数来加载表单中所有变量到 $row 中
echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n"; exit(); } $row->quicktake = JRequest::getVar('quicktake','','post','string',JREQUEST_ALLOWRAW); $row->review = JRequest::getVar('review','','post','string',JREQUEST_ALLOWRAW); if(!$row->review_date){ $row->review_date = date('Y-m-d H:i:s'); } if(!$row->store()){ echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n"; exit(); } $mainframe->redirect('index.php?option='.$option,'Review Saved'); } ?>

  首先,将全局变量  $mainframe 传进来,  $mainframe 对象提供很多成员函数来控制session 变量和  headers。然后将  TableReview 类的一个实例赋值给 $row ,类的名字由第一个参数和第二个参数组合而成,第二个参数是第一个参数的前缀。

      然后,使用 bind() 成员函数来加载表单中所有变量到 $row 中。

      bind()  函数传一个关联数组参数并且要数组的所有元素都要和对象的成员变量完全匹配。为了减少 SQL注入的风险,我们使用 Jrequest::get() 来清除 $_POST 的值,这个过程会过滤掉所有能够控制 SQL的字符。

     如果 bind() 失败了会弹出一个 JavaScript 的警告对话框并返回到前一个页面。绑定后就可以直接操作 $row 的成员变量。

      既然 quicktake 和 review 字段都接受 HTML内容,那么它们需要对 bind() 函数进行清除 HTML 的特殊处理。
      要做这样处理,可以使用 Jrequest的成员函数getVar()  并传递表单的变量名、默认值、请求的数组、期望的格式和各自JREQUEST_ALLOWRAW 标识。以防点评没有选择日期,我们赋了当前日期给点评日期。

      最后,调用 store() 函数,把所有的成员变量都转化成 UPDATE 和 INSERT 语句(由id 的值决定是 UPDATE 还是 INSERT)。因为是第一次创建记录,id 没有值,所以会构建INSERT 查询语句。
      如果有 SQL错误就返回上一页, 通常这一类的 SQL错误都是由于 $row额外的成员变量而没有在数据表类中引起的。那么如果发现SQL 错误,第一时间就是要检查确保你的成员变量的拼写要与数据表的列一致。否则,如果  SQL 执行成功,将使用$mainframe 的 redirect() 函数返回组件的页面。

     此时,我们还要在admin.review.php中的switch添加以下代码:

     case 'save':
            saveReview($option);
            break;

     保存文件后访问这个地址:http://localhost/joomla/administrator/index.php?option=com_reviews&task=add你填好表单后点击保存,希望我能看到类似以下的页面:

   

     如果没有异常,运行成功的话,刚才我添加的数据就应该在数据库表jos_reviews里面了。

      现在尝试点击上述页面的“创建”按钮,你会有疑问,为什么我们不能点击“新建”按钮?
      工具栏的按钮需要有名字为 adminForm 的表单才能有效,既然现在没有表单,那么点击任何的按钮都产生JavaScript 错误的。当你加上 adminForm 表单后,按钮马上就生效了。

第八步:既然我们的管理员不会有访问 phpMyAdmin 的权限,我们需要创建显示点评的列表。开始 我们在 admin.reviews.php中添加以下函数:

function showReviews($option){
        $db =&JFactory::getDBO();
        $query = "SELECT * FROM #__reviews";
        $db->setQuery($query);
        $rows=$db->loadObjectList();
        if($db->getErrorNum()){
            echo $db->stderr();
            return false;
        }
        HTML_reviews::showReviews($option,$rows);
    }

  

这个函数加载了将被显示的数据,我们得到了一个当前数据库连接的引用, 然后调用它的成员函数 setQuery() ,setQuery() 函数带一个 SQL 语句的字符串为参数,但只做存储之后使用而不是立即执行。当调用 loadObjectList() 函数,之前设置的 SQL 语句就会执行并返回记录到一个数组中。如果运行过程出现错误,那么将显示错误和停止组件运行。

第九步,如果第八步一切正常,那么把记录结果的数组传给  admin.reviews.htlm.php 中的成员函数showReviews(),修改后代码如下:

     function showReviews($option,&$rows){
?>
    <form action="index.php" method="post" name="adminForm">
        <table class="adminlist">
            <thead>
                <tr>
                    <th width="20">
                        <input type="checkbox" name="toggle" value="" onclick="checkAll(<?php echo count($rows);?>)"/>
                    </th>
                    <th class="title">Name</th>
                    <th width="15%">Address</th>
                    <th width="10%">Reservations</th>
                    <th width="10%">Cuisine</th>
                    <th width="10%">Credit Cards</th>
                    <th width="5%" nowrap="nowrap">Published</th>
                </tr>
            </thead>
            <?php
                $k = 0;
                for($i=0,$n=count($rows);$i<$n;$i++){
                    $row=&$rows[$i];
                    $checked = JHTML::_('grid.id',$i,$row->id);
                    $published = JHTML::_('grid.published',$row,$i);
            ?>
            <tr class="<?php echo "row$k";?>">
                <td><?php echo $checked;?></td>
                <td><?php echo $row->name;?></td>
                <td><?php echo $row->address;?></td>
                <td><?php echo $row->reservations;?></td>
                <td><?php echo $row->cuisine;?></td>
                <td><?php echo $row->credit_cards;?></td>
                <td><?php echo $published;?></td>
            </tr>
            <?php
                $k=1-$k;
                }
            ?>
        </table>
        <input type="hidden" name="option" value="<?php echo $option;?>"/>
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="boxchecked" value="0"/>
    </form>

<?php
        }

  这个函数定义了一个名为 adminForm(作为 JavaScript 应用) 并指向 index.php 的表单,接着显示一个带有  adminlist 类的表格,第一行为表格的头部,第一列是一个复选框“check all”,它会自动地选择页面上的所有记录。
      接着使用传进来的记录数组来循环显示每一行的数组。要注意的是变量 $k,它在每次循环中会在 0 和 1 之中更换值,它的作用好是用来更换每个 <tr> 的类,从而控制了每行显示的背景色。
      大部分的成员变量会直接输出,但是有两个比较特殊,JHTML::(‘grid.id‘) 函数将返回一个能被后端 JavaScript 识别的复选框,JHTML::_('grid.published')  函数返回一个基于成员变量 published 的值的图片,当  published 的值是  1 时,将返回打勾的图片,否则返回打“X”的图片。 

      在表格下面,有四个隐藏的变量,第一个处理 option 的值,以便路由到正确的组件,第二个是 task,它是在提交表单之前以便让工具栏中的 JavaScript 能给它赋值。第三个是boxchecked,当有任意一行的复选框被选择,boxchecked 被置为 1,当所有行的复选框被清除,boxchecked 被置为 0,它是用来辅助 JavaScript 来处理列表。
       当完成了 HTML 代码的输出,最后一步就是更新文件 admin.reviews.php中的 switch() 语句,加入下面的高亮代码: 

switch($task) 
{ 
case 'add': 
editReview( $option ); 
break; 
case 'save': 
saveReview( $option );
break; 
default: 
showReviews( $option ); 
break; 
} 

在浏览器中输入URL路径 http://localhost/joomla/administrator/index.php?option=com_reviews希望能够看到一个相似的页面如下:

我们将扩展原有的代码来编辑记录, 而不是写一个新的功能。 在文件 admin.reviews.php中的  editReview()  函数中用以下的高亮代码来代替:$row=&JTable:getInstance(‘Review’, ‘Table’):

function editReview($option){
        //$row = &JTable::getInstance('Review','Table');
        $row =&JTable::getInstance('review','Table');
        $cid = JRequest::getVar('cid',array(0),'','array');
        $id = $cid[0];
        $row->load($id);
        $lists = array();
        $reservations = array(
            '0'=>array('value'=>'None Taken','text'=>'None Taken'),
            '1'=>array('value'=>'Accepted','text'=>'Accepted'),
            '2'=>array('value'=>'Suggested','text'=>'Suggested'),
            '3'=>array('value'=>'Required','text'=>'Required')
        );
        $lists['reservations'] = JHTML::_('select.genericList',$reservations,'reservations','class="inputbox"'.'','value','text',$row->reservations);
        $lists['smoking'] = JHTML::_('select.booleanlist','somking','class="inputbox"',$row->smoking);
        $lists['published'] = JHTML::_('select.booleanlist','published','class="inputbox"',$row->published);
        HTML_reviews::editReview($row,$lists,$option);
    }

  当执行 editReview() 函数时,我们取得 TableReview 对象来处理数据,然后会从表单中取得记录 ID 的数组变量 cid,既然在同一个时间只编辑一条记录,那我们只选择第一个数组元素来加载相应的记录。更新文件 admin.reviews.php 中的 switch() 语句如下:

switch($task){
        case 'edit':
        case 'add':
            editReview($option);
            break;
        case 'save':
            saveReview($option);
            break;
        default:
            showReviews($option);
            break;
    }

 你应该要提供 能够让用户通过点击来编辑各自的记录的链接 。 在文件admin.reviews.html.php 的 HTML_reviews::showReviews() 函数下加入一下高亮的代码:

function showReviews($option,&$rows){
?>
    <form action="index.php" method="post" name="adminForm">
        <table class="adminlist">
            <thead>
                <tr>
                    <th width="20">
                        <input type="checkbox" name="toggle" value="" onclick="checkAll(<?php echo count($rows);?>)"/>
                    </th>
                    <th class="title">Name</th>
                    <th width="15%">Address</th>
                    <th width="10%">Reservations</th>
                    <th width="10%">Cuisine</th>
                    <th width="10%">Credit Cards</th>
                    <th width="5%" nowrap="nowrap">Published</th>
                </tr>
            </thead>
            <?php
                JLoader::import('joomla.filter.output');//貌似有点写错,感觉应该写成joomla.filter.filteroutput,不过这样运行没有错误
                $k = 0;
                for($i=0,$n=count($rows);$i<$n;$i++){
                    $row=&$rows[$i];
                    $checked = JHTML::_('grid.id',$i,$row->id);
                    $published = JHTML::_('grid.published',$row,$i);
                    $link = JFilterOutput::ampReplace('index.php?option='.$option.'&task=edit&cid[]='.$row->id);
            ?>
            <tr class="<?php echo "row$k";?>">
                <td><?php echo $checked;?></td>
                <td>
                    <a href="<?php echo $link;?>">
                    <?php echo $row->name;?>
                    </a>
                </td>
                <td><?php echo $row->address;?></td>
                <td><?php echo $row->reservations;?></td>
                <td><?php echo $row->cuisine;?></td>
                <td><?php echo $row->credit_cards;?></td>
                <td><?php echo $published;?></td>
            </tr>
            <?php
                $k=1-$k;
                }
            ?>
        </table>
        <input type="hidden" name="option" value="<?php echo $option;?>"/>
        <input type="hidden" name="task" value=""/>
        <input type="hidden" name="boxchecked" value="0"/>
    </form>

<?php
        }

    为了兼容 XHTML, 我们需要确保符号 & 使用 &amp; 来代替, 我们使用 ampReplace()来处理 , 它是  JFilterOutput  类的成员函数 ,  JFilterOutput  通过调用JLoader::import('joomla.filter.output'); 来加载。  Joomla! 提供了许多不同的库, 例如 XML处理和 RSS输出等。我们使用 JLoader::import 函数来按需要加载代码,而不是每次加载Joomla! 时都加载所用的库。

      你可能已经注意到了在编辑页面的工具栏上有个 “应用” 按钮, 它允许人们保存内容的同时,页面依然保留在编辑的状态,为了是应用按钮生效,需要在文件 admin.reviews.php 中做两个改变,在 switch() 语句中加入一下的高亮代码

switch($task){
        case 'edit':
        case 'add':
            editReview($option);
            break;
        case 'apply':
        case 'save':
            saveReview($option,$task);
            break;
        default:
            showReviews($option);
            break;
    }

 

在 saveReview() 函数中加入 $task 参数:function saveReview( $option, $task )  将 saveReview() 函数的最后一行更改为如下:

function saveReview($option,$task){
        global $mainframe;
        $row=&JTable::getInstance('review','Table');
        if(!$row->bind(JRequest::get('post'))){
            echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n";
            exit();
        }
        $row->quicktake = JRequest::getVar('quicktake','','post','string',JREQUEST_ALLOWRAW);
        $row->review = JRequest::getVar('review','','post','string',JREQUEST_ALLOWRAW);
        if(!$row->review_date){
            $row->review_date = date('Y-m-d H:i:s');
        }
        if(!$row->store()){
            echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n";
            exit();
        }
        
        switch ($task){
            
            case 'apply':
                $msg = 'Changes to Review saved';
                $link = 'index.php?option='.$option.'&task=edit&cid[]='.$row->id;
                break;
            default:
                $msg = 'asdfsdfReview Saved';
                $link = 'index.php?option='.$option;
                break;
        }
        $mainframe->redirect($link,$msg);
    }

  增加删除的功能是相当的简单,在文件  admin.reviews.php 的  switch()  语句中加入以下的case 语句:

switch($task){
        case 'edit':
        case 'add':
            editReview($option);
            break;
        case 'apply':
        case 'save':
            saveReview($option,$task);
            break;
        case 'remove':
            removeReviews($option);
            break;
        default:
            showReviews($option);
            break;
    }

  当然也要增加 removeReviews() 函数:

function removeReviews($option){
        global $mainframe;
        $cid = JRequest::getVar('cid',array(),'','array');
        $db =&JFactory::getDBO();
        if(count($cid)){
            $cids = implode(',',$cid);
            $query = "DELETE FROM #__reviews WHERE id IN ($cids)";
            $db->setQuery($query);
            if(!$db->query()){
                echo "<script> alert('".$db->getErrorMsg()."');window.history.go(-1);</script>";
            }
        }
        $mainframe->redirect('index.php?option='.$option);
    }

  好了,基本做到这里,组件的后台管理小例子已经完成了

     接下来,我要做做这个组件的前台显示

第一步,在网站根目录/component/创建com_reviews文件夹,在里面创建一个reviews.php的文件,代码如下:

<?php 
    defined('_JEXEC') or die('Restricted access');
    jimport('joomla.application.helper');
    require_once(JApplicationHelper::getPath('html'));
    JTable::addIncludePath(JPATH_ADMINISTRATOR.DS.'components'.DS.$option.DS.'tables');
    switch($task){
        default:
            showPublishedReviews($option);
            break;
    }
    function showPublishedReviews($option){
        $db = &JFactory::getDBO();
        $query = "SELECT * FROM #__reviews WHERE published = '1' ORDER BY review_date DESC";
        $db->setQuery($query);
        $rows=$db->loadObjectList();
        if($db->getErrorNum()){
            echo $db->stderr();
            return false;
        }
        HTML_reviews::showReviews($rows,$option);
    }
    
?>

  与后端的方法相似,require_once(  JApplicationHelper::getPath(  'html'  )  );  导进文件reviews.html.php,传递 JPATH_ADMINISTRATOR.DS.'components'.DS.$option.DS.'tables' 到Jtable::addIncludePath();  导进数据表类。 最后 switch()  语句设置了默认的 case 来显示所有发布的点评。这里的 SQL 语句确保只有发布的点评被选择并且以点评日期的倒序来排序。

第二步,在刷新页面之前 , 我们需要 在  /component/com_reviews/reviews.html.php  中加入HTML_reviews 类:

<?php
    class HTML_reviews{
        function showReviews($rows,$option){
?>
    <table>
        <?php
            foreach($rows as $row){
                $link = 'index.php?option='.$option.'&id='.$row->id.'&task=view';
                echo '<tr><td><a href="'.$link.'">'.$row->name.'</a></td></tr>';
            }
        ?>    
    </table>
<?php
        }
    }
?>

  如果你没有写任何的代码来处理 “view” “task”,当你单击一条链接后,你看到的是相同的页面,在 reviews.php 中加入以下代码:

function viewReview($option){
        $id = JRequest::getVar('id',0);
        $row = & JTable::getInstance('review','Table');
        $row->load($id);
        if(!$row->published){
            JError::raiseError(404,JText::_('Invalid ID provided'));
        }
        HTML_reviews::showReview($row,$option);
    }

  先,用 getVar() 获取请求的 id,它能检查变量的各种攻击。外部的数据处理要很小心,特别是处理公共访问的网站的数据,在我们的代码中统一使用 getVar() 将会提供一个合理的安全层。如果 id 的值丢失或者不合法,第二个参数 0 将会作为默认值提供给 id。然后,我们从后端得到一个数据表类,加载相应 id 的记录后,我们检查选择的记录是否被发布,如果不是发布的,我们用  JError 的成员函数 raiseError() 来输出找不到信息的404 页面

这个检查确保不让用户随便输入  id 来获取点评,而且如果记录不存在也会显示以上的页面。viewReview() 函数会做所有的事情来加载请求的点评,但是我们仍然需要加入代码来
调用这个函数,加入下面的高亮代码到 switch() 中: 

switch($task){
        case 'view':
            viewReview($option);
            break;
        default:
            showPublishedReviews($option);
            break;
    }

  我们也需要在我们的输出类创建一个显示函数,在  reviews.html.php 文件中加入showReview() 函数到 HTML_reviews:

<?php
    class HTML_reviews{
        function showReviews($rows,$option){
?>
    <table>
        <?php
            foreach($rows as $row){
                $link = 'index.php?option='.$option.'&id='.$row->id.'&task=view';
                echo '<tr><td><a href="'.$link.'">'.$row->name.'</a></td></tr>';
            }
        ?>    
    </table>
<?php
        }
        
        function showReview($row,$option){
        
?>
    <p class="contentheading"><?php echo $row->name;?></p>
    <p class="createdate"><?php echo JHTML::date($row->review_date);?></p>
    <p><?php echo $row->quicktake;?></p>
    <p><strong>Address:</strong><?php echo $row->address;?></p>
    <p><strong>Cuisine:</strong><?php echo $row->cuisine;?></p>
    <p><strong>Average dinner price:</strong><?php echo $row->avg_dinner_price;?></p>
    <p><strong>Credit cards:</strong><?php echo $row->credit_cards;?></p>
    <p><strong>Reservations:</strong><?php echo $row->reservations;?></p>
    <p>
        <strong>Smoking:</strong>
        <?php 
            if($row->smoking==0){
                echo "No";
            }
            else{
                echo "Yes";
            }
        ?>
    </p>
    <p><?php echo $row->review;?></p>
    <p><em>Notes:</em><?php echo $row->notes;?></p>
    <?php $link = 'index.php?option='.$option;?>
    <a href="<?php echo $link;?>"><return to the reviews</a>
<?php
        }
    }
?>

  showReview() 函数传进一个数据库行对象和组件的名字作为参数,这行记录的大部分字段都以 HTML 格式显示,也包含许多逻辑。Smoking 字段陪赋给合适的 Yes 或者 No 值。调用 JHTML::Date() 来格式化从数据库取出来的时间戳。最后,显示一个可以返回点评列表的链接,保存文件后,再次点击点评的链接。

     好了,现在终于前台的文章可以查看了。真是太爽了。

     过了一天,我继续开发“评论”代码块,做做以下记录。

第一步,创建一个评论表,代码如下:

CREATE TABLE `jos_reviews_comments`(
`id` int(11) NOT NULL auto_increment,
`review_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`full_name` varchar(50) NOT NULL,
`comment_date` datetime NOT NULL,
`comment_text` text NOT NULL,
PRIMARY KEY (`id`)
)

  第二步,既然有了新的数据库表格,那么我们就要添加一个新的数据库表类了。下面我来做做,在administrator/component/com_reviews文件夹下的tables文件创建comment.php文件,代码如下:

<?php
    defined('_JEXEC') or die('Restricted access');
    class TableComment extends JTable{
        var $id = null;
        var $review_id = null;
        var $user_id = null;
        var $full_name = null;
        var $comment_date = null;
        var $comment_text = null;
        function __construct(&$db){
            parent::__construct('#__reviews_comments','id',$db);
        }
    }
?>

  第三步,现在我希望得到页面效果是评论在点评详情的下面显示,好了,目标明确,现在我来开发一下,去到根目录/component/com_reviews文件夹,对reviews.html.php进行编辑,添加以下代码高亮部分:

function showCommentForm($option,$review_id,$name){
       
?>
            <br /><br />
            <form action="index.php" method="post">
                <table>
                    <tr>
                        <td><strong>Name:</strong></td>
                        <td><input class="textarea" type="text" name="full_name" id="full_name" value="<?php echo $name;?>"/></td>
                    </tr>
                    <tr>
                        <td><strong>Comment:</strong></td>
                        <td><textarea class="textarea" cols="20" rows="4" name="comment_text" id="comment_text" style="width:500px;"></textarea></td>
                    </tr>
                </table>
                <input type="hidden" name="review_id" value="<?php echo $review_id;?>"/>
                <input type="hidden" name="task" value="comment"/>
                <input type="hidden" name="option" value="<?php echo $option;?>"/>
                <input type="submit" class="button" id="button" value="Submit"/>
            </form>
<?php
        }

第四步,既然reviews.html.php有了showCommentForm方法,那么我们就应该找个文件写些代码去调用它,所以我就找到根目录/component/com_reviews/reviews.php这个文件,知道了改哪个文件之后,我就要想想改文件的哪个部分,现在我要修改viewReview方法这个部分,修改后代码如下:

function viewReview($option){
        $id = JRequest::getVar('id',0);
        $row = & JTable::getInstance('review','Table');
        $row->load($id);
        if(!$row->published){
            JError::raiseError(404,JText::_('Invalid ID provided'));
        }
        HTML_reviews::showReview($row,$option);
        
        $user =&JFactory::getUser();
        if($user->name){
            $name = $user->name;
        }
        else{
            $name = '';
        }
        HTML_reviews::showCommentForm($option,$id,$name);
    }

  现在登陆一下:http://localhost/Joomla/index.php,应该可以看到以下类似页面:

第五步,当我们填写和提交表单之前,我们需要加入处理输入数据和插入到数据库。那么,我们就改改reviews.php这个文件,先加入插入评论的方法addComment代码如下:

function addComment($option){
        global $mainframe;
        $row =&JTable::getInstance('comment','Table');
        if(!$row->bind(JRequest::get('post'))){
            echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n";
            exit();
        }
        $row->comment_date = date('Y-m-d H:i:s');
        $user  =&JFactory::getUser();
        if($user->id){
            $row->user_id = $user->id;
        }
        if(!$row->store()){
            echo "<script> alert('".$row->getError()."');window.history.go(-1);</script>\n";
            exit();
        }
        $mainframe->redirect('index.php?option'.$option.'&id='.$row->review_id.'&task=view','Comment Added.');
    }

  有了插入数据的方法,那么我要找个地方去调用它,就在reviews.php中的switch块进行控制,现在去改改代码:

switch($task){
        case 'view':
            viewReview($option);
            break;
        case 'comment':
            addComment($option);
            break;
        default:
            showPublishedReviews($option);
            break;
    }

第六步,估计这个时候,我可以添加评论数据了。尝试了一下,去数据库表看了一下数据,果然添加成功了。现在我要在前台显示评论的内容,我希望评论跟在文章内容下面,那么,我想好了前台显示的大概效果,现在就来修改一下reviews.html.php的内容了,追加代码如下:

function showComment($row){
?>
            <br /><br />
            <p>
                <strong><?php echo $row->full_name;?></strong>
                <em><?php echo JHTML::date($row->comment_date);?></em>
            </p>
            <p>
                <?php echo $row->comment_text;?>
            </p>
<?php
        }

第七步,搞定了前台的显示,那么下一步,就去reviews.php中修改一下viewReview这个方法,代码如下:

 function viewReview($option){
        $id = JRequest::getVar('id',0);
        $row = & JTable::getInstance('review','Table');
        $row->load($id);
        if(!$row->published){
            JError::raiseError(404,JText::_('Invalid ID provided'));
        }
        HTML_reviews::showReview($row,$option);
        
        $db = &JFactory::getDBO();
        $db->setQuery("SELECT * FROM #__reviews_comments WHERE review_id = '$id'");
        $rows = $db->loadObjectList();
        foreach($rows as $row){
            HTML_reviews::showComment($row);
        }
                
        $user =&JFactory::getUser();
        if($user->name){
            $name = $user->name;
        }
        else{
            $name = '';
        }
        HTML_reviews::showCommentForm($option,$id,$name);
    }

  呵呵。测试一下,可以了。好开心。大概会看到以下类似页面效果:

 

练习到这里,我来回顾总结一下,

  • 首先,结合MVC的思想来分析几个文件间的机制。就本文例子而言还没很好地按照MVC思想进行代码和文件结构的划分,先粗略地理解一下,有错以后改,知错能改,善莫大焉。(*^__^*) 嘻嘻……言归正题啦。

Model一般是与数据库打交道的,那么在administrator/component/com_reviews/tables文件夹下面的文件review.php(即TableReview类)就是有点类似充当了model的角色;

Controller一般指的是控制器,review.php和admin.review.php就是充当了控制器这个角色,这些文件的代码主要通过自定义function对数据进行处理和通过switch代码块对视图按需进行选择,从而达到控制的效果。一般控制器对处理后的数据有两种动作:一种是往数据库放,另一种是往视图放。往数据库放时通过store()方法与model的文件进行交互,往视图放的时候通过把处理后的数据存放在(admin.)xxx.html.php文件中定义的相应的类名和方法中,从而进行交互,如本文例子中的HTML_reviews::showReviews($option,$rows);。如果是往数据库放,在此之前我们应该先告诉store()方法往哪个数据库表放,我们通过通过类似$row=&JTable::getInstance('review','Table');获取希望插入的数据库表类的实例;如果是往视图放,要根据视图相应的类名和方法处理好需要的数据值及其格式。如showReviews($option,&$rows),那么就要先从数据库表里面获取数据存放到$rows。

View一般指的是视图,那么凡是含有(admin.)xxx.html.php的文件就充当了这个角色,这些文件的代码主要对HTML、JS、css的处理与对Ctroller层传递过来的变量值进行显示。

  • 然后,看看这些文件中的代码体现出来的关于这个框架的使用技巧和规则
  1.  上文控制器Controller中提到的HTML_reviews::showReviews($option,$rows);中的HTML_reviews能够识别执行,是因为通过require_once(JApplicationHelper::getPath('admin_html'));获取到了admin.reviews.html.php中的HTML_reviews类。因此,我们记得在编写控制器的方法之前,先通过require_once(JApplicationHelper::getPath()来进行对视图类的加载。
  2. JTable::getInstance('review','Table');之所以能够执行匹配到TableReview这个数据库表类,是因为通过JTable::addIncludePath(JPATH_COMPONENT.DS.'tables');这个指令告诉了getInstance()方法要扫描数据库表类所存在的路径。因此,我们记得如果
  • 再看看这些文件中涉及到的部分核心代码分析

 

  • 最后回归理论,更好地把实践与理论相结合。看看这些文件和提到的核心代码用了哪些方面的技术以及部分问题思考
  1.  现在来看看JApplicationHelper::getPath(),它会自动根据参数自动找出与这段代码所在当前文件相匹配的视图文件,就本文的例子而言,一个控制器文件(如admin.review.php)对应一个视图类(admin.reviews.html.php),由此引发我的一个疑问,如果要通过一个控制器跳转到不同视图类的页面时该怎么处理呢?也就是说reviews.php通过JApplicationHelper::getPath()来获取的视图类只有一个,那么其他的视图类又能通过Joomla框架的哪些方法进行获取呢?

posted on 2012-11-21 10:29  ellisonDon  阅读(3079)  评论(0编辑  收藏  举报

导航