Thursday, 7 January 2010

CakePHP: The dependent listboxes problem

When creating dynamic web-sites, sooner or later you are going to face the problem of providing dependent list boxes. Imagine the case where one wishes to select a city from a list grouped by region, or a user from a group, an invoice from an order etc. Grouping things into categories is very common in real life and as far as I am concerned, it is almost always a must for your application to be able to utilize such groupings.

In CakePHP -- as far as I know -- there are two ways you can help your users pick up a value from a list of grouped items. One is to create a select box whose items are organized in selection groups sorted in some logical way. the other way in to use dependent AJAX triggered combo or list boxes where selection on the first will filter the items displayed on the second.

In this posting I will provide code that handles both cases.

The sample data

As an exercise for learning Cake, I developed a small application that manages the IT department books. Each Book belongs to a BookCategory and each BookCategory belongs to a BookCategoryGroup. Reversely, each BookCategoryGroup has many BookCategory and each BookCategory has many Book. The corresponding tables have foreign keys adhering to the CakePHP conventions, so I am not going to waste any more time explaining the data structure.

The goal here is to help our users, when adding or editing book records, to find the right category for each book given the organization of book categories in book category groups. Like I said in the introduction there are two ways we can accomplish this

One combo box organized in selection groups

The way is very easy to implement and may become particularly handy whenever the total number of list items is relatively small. Cake's Form::imput method will create option groups if the array containing the options for a select box is organized into sub arrays so if we add the following function in our BooksController ....

    private function prepareCategoriesCombo()
    {
        // gain access to the BookCategoryGroups model class
        $this->loadModel('BookCategoryGroup');
        // prepare a list of all book category groups
        $this->BookCategoryGroup->recursive = 0;
        $bookCategoryGroups = $this->BookCategoryGroup->find(
                                'all',
                                array(
                                    'conditions' => array(),
                                    'order' => array('BookCategoryGroup.name')
                                )
                            );

        // create an empty array to hold the combo box options
        $bookCategories = array();
        foreach( $bookCategoryGroups as $bookCategoryGroup) {
            $groupId = $bookCategoryGroup['BookCategoryGroup']['id'];
            $groupName = $bookCategoryGroup['BookCategoryGroup']['name'];
            // create a sub array for each group category
            $bookCategories[] = $groupName;
            // fill the array with the categories corresponding to the group
            $bookCategories[$groupName] = $this->Book->BookCategory->find(
                                'list',
                                array (
                                    'conditions' => array(
                                        'BookCategory.book_category_group_id' => $groupId
                                        ),
                                    'order' => array(
                                        'BookCategory.name'
                                        )
                                )
                                    );
        }
        return $bookCategories;
    }

Supposing that you have baked your original controller and view code with the cake script, your add() or edit() controller actions need to have the following in order to use the option grouped combo:

   ...
   $bookCategories = $this->prepareCategoriesCombo();
   ...

while the view template will require no change at all (i.e. a simple echo $form->input('book_category_id'); will suffice). As I said earlier on, this method is simple enough and unless you intent to let your users pick a US zip code organized by state, this may be the preferred solution for many cases. If however you have lots of data and an untamable desire for ajax, read on; fear not however CakePHP's approach to AJAX makes this look also like a piece of cake.

The AJAX way: Two combos with one auto filtering the other

The basic idea behind AJAX is the following: You start by defining an area in you web page identifiable via the the HTML id attribute. Then when the user clicks on a button or changes the value of some control (edit, list or combo), you make an asynchronous call to the web server -- that is without having to reload the page -- and the server returns HTML code that is ready to be placed inside that area. The actual way you implement this depends on the libraries and the AJAX framework you use. Cake does that using the prototype and the Scriptaculus frameworks.

So to get things started, download prototype and scriptaculus and place the following files in your APP/webroot/js folder:

builder.js   dragdrop.js  prototype.js      slider.js  unittest.js
controls.js  effects.js   scriptaculous.js  sound.js
Having done that, modify you application layout in order to include them. Open APP/view/layouts/default.ctp and change the HTML head part so it looks like this :
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>
      <?php echo $title_for_layout; ?>
    </title>
    <?php echo $html->css('it-library'); ?>
    <meta name="Generator" content="Quanta Plus" />
    <meta name="Author" content="Thanassis Bakalidis" />
    <?php if (isset($javascript)) : ?>
      <?php echo $javascript->link('prototype.js'); ?>
      <?php echo $javascript->link('scriptaculous.js'); ?>;
    <?php endif; ?>
    <?php echo $scripts_for_layout; ?>
  </head>

The next thing to do is to modify our controller in order to provide Javascript and AJAX support: Our books controller now looks like the following:

class BooksController extends AppController {

    var $name = 'Books';
    var $helpers = array('Html', 'Form', 'Javascript', 'Ajax');
    var $components = array('RequestHandler');

    ...
}

Next go to your view template -- may that be the add or edit.ctp -- and change the initial echo $form->input('Book.book_category_id'); line in order to look like the following:

        // here are the two list boxes displaying groups and categories
        // aim is to create an auto-filter effect with AJAX
        echo $form->label( 'BookCategory.book_category_group_id',
                            'Category Group');
        echo $form->select('BookCategory.book_category_group_id',
                            $bookCategoryGroups,
                            $bookCategoryGroupId,
                            array(
                                'id' => 'bookCategoryGroups'
                            ),
                            FALSE);
        echo $form->input('Book.book_category_id', array('id' => 'bookCategories' ));

        // each time the bookCategoryGroups element changes we are to
        // asynchronously call the updateSelect action of the current 
        // controller and insert whatever the action produces inside the 
        // html DOM element identified by bookCategories
        $ajaxOptions = array('url' => 'updateSelect','update' => 'bookCategories');
        echo $ajax->observeField('bookCategoryGroups',$ajaxOptions);

I believe that the code is self explanatory. Now let us add the updateSelect method of the BooksController class

    function updateSelect()
    {
        $groupId = $this->data['BookCategory']['book_category_group_id'];
        if (!empty( $groupId )) {
            $options = $this->getBookCategoriesForGroup( $groupId);
            // these are the combo box options to be used in the view file
            $this->set('options',$options);
        }
    }

There is one thing to mention here: the AJAX code produced by observeField() serializes the entire field that is supposed to observe, so this will be available in the controller action as $this->data['Model']['field'].

Next we need to create the actual view code. Create a file named update_select.ctp inside your APP/views/books directory and place the following code inside:

<?php
    // create  tags coming from a $options variable
    // This is to be used by AJAX in order to fill the contents of a combo
    // box
    if(!empty($options)) {
        foreach($options as $key => $value) {
            echo "";
        }
    }
?>

Now we have everything in place. The only thing left to do is to initialize the two combo boxes so that they contain the correct data, i.e. all the group categories for the top combo and the categories for the selected records category group on the second, during initial page load. To achieve this I have created two additional functions in the BooksController class:

    private function getBookCategoriesGroups()
    {
        $this->loadModel('BookCategoryGroup');
        $this->BookCategoryGroup->recursive = 0;
        return $this->BookCategoryGroup->find('list',
                                                array(
                                                    'conditions' => array(),
                                                    'order' => array(
                                                        'BookCategoryGroup.name'
                                                        )
                                                )
                                            );
    }

    private function getBookCategoriesForGroup( $groupId)
    {
        return $this->Book->BookCategory->find('list',
                                array(
                                    'conditions' => array (
                                        'book_category_group_id' => $groupId
                                        ),
                                    'order' => array(
                                            'BookCategory.name'
                                        )
                                )
                            );
    }

Now my controller's edit action -- which as I mentioned earlier, was baked by cake -- looks like the following.

    function edit($id = null)
    {
        if (!$id && empty($this->data)) {
            $this->Session->setFlash(__('Invalid Book', true));
            $this->redirect(array('action'=>'index'));
        }
        if (!empty($this->data)) {
            if ($this->Book->save($this->data)) {
                $this->Session->setFlash(__('The Book has been saved', true));
                $this->redirect(array('action'=>'index'));
            } else {
                $this->Session->setFlash(__('The Book could not be saved. Please, try again.', true));
            }
        }

        if (empty($this->data)) {
            $this->data = $this->Book->read(null, $id);
        }

        // set up additional book record parameters
        $sites = $this->Book->Site->find('list');
        $bookTypes = $this->Book->BookType->find('list');
        $languages = $this->Book->Language->find('list');

        // setup the two AJAX operated combo boxes
        $bookCategoryGroups = $this->getBookCategoriesGroups();                
        $bookCategoryGroupId = $this->data['BookCategory']['book_category_group_id'];
        $bookCategories = $this->getBookCategoriesForGroup( $bookCategoryGroupId);

        $ratings = $this->Book->Rating->find('list');
        $publishers = $this->Book->Publisher->find('list');

        $this->set( compact( 'sites','bookTypes','languages',
                             'bookCategories', 'bookCategoryGroups', 'bookCategoryGroupId',
                             'ratings','publishers'));
    }

Needless to say that when adding a record, the initial $bookCategoryGroupId can be set to an initial value say 1 and then let your uses change to whatever seems appropriate.

I have tested this with CakePHP 1.2.5 on both Firefox (versions 3 and 3.5) and IE (version 8).

As a last statement, I would like to point out that I am by no means an expert on AJAX or CakePHP. I got my info from an earlier posting by DEVMOZ and the CakePHP AJAXHelper class info page. I have put this down as a working reference to a real problem, that anybody can copy -- hopefully -- easy to modify code.

Wednesday, 16 December 2009

CakePHP and Oracle on CentOS. My own how to guide

During the last couple of days I formated and set up from scratch my CakePHP development server, using CentOS, php 5.3 from Remi and the Oracle 11gR2 clients. The log of my actions in PDF can be downloaded from here.

During the following days I will setup one more server -- supposed to be the one that we will use productively -- following these same instructions. If I find any mistakes I will correct them and post back the orginal file here.

Update history

  • Dec-28-2009: Added information about setting up and running the cake scripts from the command line.

Tuesday, 15 December 2009

CakePHP: A behavior for acessing non UTF Oracle databases

The origins of our company's Oracle database date back at the beginning of the decade. At that time we had Oracle version 9i running on SuSE Linux 8.2 and the expected thing to do back then was to create a database using the EL8ISO8859P7 character set. After eight years we are still using Oracle. Now the database is 11g and the database server is OEL 5.4. Basic data structures however, are still the same as they were back in 2002.

During the evaluation of CakePHP as our next development environment we very soon run into the problem of trying to insert and retrieve Unicode data from a non-unicode database. Since the encoding key of $defualt array member of the DATABASE_CONFIG class (stored in app/config/database.php file) has no effect when connecting to Oracle databases, we ended up creating an additional translation layer, that would convert data to and from Unicode when reading from and writing to Oracle.

CakePHP's way of doing this kind of staff is to create behaviors. Ours is called CharsetConverter, so by CakePHP's standards it is implemented in a class named CharsetConverterBehavior that is stored in a file named charset_converter.php which is located in the APP/models/behaviors directory.

The approach here uses the mb_convert_encoding function provided with the php-mbstring package. The code implementation is the following

<?php
/**
 * A simple behavior that allows cake PHP to use single byte Oracle
 * and possibly other vendor -- databases
 * Tested with Oracle 11gR1
 *
 * @version 0.1
 * @author Thanassis Bakalidis
 */
class CharsetConverterBehavior extends ModelBehavior {
    // we have an Oracle database that dates back to 2002 so
    const DEFAULT_DB_LOCALE = 'ISO-8859-7';
    const DEFAULT_PAGE_LOCALE = 'UTF-8';

    const READING_FROM_DB = TRUE;
    const WRITING_TO_DB = FALSE;

    var $databaseLocale;
    var $webpageLocale;
    var $Model;

    function setup(&$model, $settings=array())
    {
        $this->Model = $model;

        $this->databaseLocale = isset($settings['databaseLocale']) ?
                                    $settings['databaseLocale'] :
                                    self::DEFAULT_DB_LOCALE;
        $this->webpageLocale = isset($settings['webpageLocale']) ?
                                    $settings['webpageLocale'] :
                                    self::DEFAULT_PAGE_LOCALE;
    }

    /**
     * Change the query where clause to the datbase native character set.
     */
    function beforeFind( &$queryData, $queryParams)
    {
        if (!isset( $queryParams['conditions']))
            return $queryParams;

        $queryParams['conditions'] = $this->recodeRecordArray(
                                                        $queryParams['conditions'],
                                                        self::WRITING_TO_DB);
        return $queryParams;
    }

    /**
     * Convert fetched data from single byte to utf-8
     */
    function afterFind(&$model, $results, $primary)
    {
        return $this->recodeRecordArray($results, self::READING_FROM_DB);
    }

    /**
     * Convert data to be saved into the database speciffic locale
     */
    function beforeSave()
    {
        $this->Model->data = $this->recodeRecordArray( $this->Model->data,
                                                       self::WRITING_TO_DB);
        return true;
    }

    /**
     * Recursively traverse and convert the encoding of the array passed
     * as parameter.
     */
    function recodeRecordArray(&$recordArray, $loading = TRUE)
    {
        foreach( $recordArray as $key => $value)
            if (is_array($value))
                $recordArray[$key] = $this->recodeRecordArray($value, $loading);
            else {
                if (is_numeric($value))
                    continue;
                $recordArray[$key] = $loading ?
                                    mb_convert_encoding(
                                                $value,
                                                $this->webpageLocale,
                                                $this->databaseLocale)
                                    :
                                    mb_convert_encoding(
                                                $value,
                                                $this->databaseLocale,
                                                $this->webpageLocale);
            }
        return $recordArray;
    }
}
?>

Once we have this in place, using the new behavior in one of our models is as simple as setting the correct value of the $actAs variable. Here is a simple example of a model using the Character set convention and validation.

<?php
    class Task extends AppModel {
        var $name = 'Task';
        var $actsAs = array(
                    'CharsetConverter' => array(
                                            'databaseLocale' => 'ISO-8859-7',
                                            'webpageLocale' => 'UTF-8'
                                        )
                    );
        var $validate = array(
                    'title' => array(
                                'rule' => 'notEmpty',
                                'message' => 'Task title cannot be left blank'
                    )
                );
    }
?>

Almost all applications contain more than one model. Perhaps the best place to put the $actAs definition would be the AppModel class defined in the file app_model.php in the root of your app directory

I also understand that writing a behavior to accomplish the job of the database driver is not the best solution. Since I have nothing better for the moment, I guess I will have to start every new CakePHP project by first changing my app_model.php file.

Tuesday, 1 December 2009

How to set up the SAPRFC extension for PHP on CentOS 5

SAPRFC

SAPRFC is an extension module for PHP 4 and 5, that makes it possible to call ABAP function modules running on a SAP R/3 system from PHP scripts.

I always wanted to test how SAP's remote function calls work together with PHP and since I am already evaluating CakePHP as our next development platform, I decided that the occasion was right to give it a try.

Next thing I did was to get my hands on The SAP Developer's Guide to PHP book by Craig S. Cmehil, which is an $85 cost and 98 page long (indexes included) tutorial on how to set up and use SAPRFC for PHP. Unfortunately the second chapter that discusses setting up your development system focuses mainly on Windows, so this post will contain the steps I took to set up SAPRFC for PHP on my x86_64 CentOS 5.4 server.

Package Requirements and Downloads

To get the latest PHP packages for Enterprise Linux I have used Remi's repository.

# wget http://download.fedora.redhat.com/pub/epel/5/x86_64/epel-release-5-3.noarch.rpm
# wget http://rpms.famillecollet.com/enterprise/remi-release-5.rpm
# rpm -Uvh remi-release-5*.rpm epel-release-5*.rpm

Remi's packages depend on the EPEL repository, so I am posting the installation procedure for EPEL as well. (if you haven't installed it yet, now will be a good time to do so.)

In addition to any other PHP packages that your application requires, in order for the SAPRFC to compile correctly, you will also require the php-devel package.

Next thing is the SAPRFC package itself. The method to install it will be to build the saprfc extension as a dynamic module without rebuilding PHP. (Remi has already done that for us.) The package itself can be downloaded from here.

Next thing will be SAP's Non Unicode RFC SDK version 6.20 or 6.40. This must be downloaded directly from the SAP Service Support Portal. You will need a customer SAP support ID

Be advised however, that SAP has implemented some form of a DNS transparent cluster group for their WEB service, so when you log in there, each time you end up accessing a server with a different DNS name (something like https://websmp104.sap-ag.de/support). That means that your browser will not be able to store your credentials because every time you attempt to connect to https://service.sap.com/support, the DNS name changes so it pop's up a dialog asking for login data again and again... Perhaps this is SAP's way of implementing the ultimate security system but, as far as I can say it is very annoying.

Anyway, once you are there select "Download" from the top menu. Next click "Search on all categories" from the left menu and enter RFC SDK on the search box that appears. You will be given the chance to select SAP RFC SDK 6.40 from the results page. Be careful not to choose the UNICODE version. Select Linux on x86_64 64bit from the list of architectures and you will end up with an SAR file in your download basket. Now you can download it

There is one more problem though. The file you download is of type SAR. meaning SAP Archive. In order to unpack it you will need SAPCAR, SAP's unpacking program. You download this the same way you downloaded RFCSDK -- just type SAPCAR on the search box. Only thing is that the Linux x86_64 version does not run on CentOS. You will need to download a Windows version, unpack the archive on a Windows machine and then upload it again on you Linux system. At least that is what I had to do. (From what I was able to understand SAP's SAPCAR for Linux is compiled to run under SuSE, so if you have satch a machine, you can try unpacking the archive over there...)

Installation

So now let's assume that you have placed SAP's RFC SDK under /opt/SAP/rfcsdk and SAPRFC extention module for PHP under /usr/src/saprfc-1.4.1/. Type the following commands on your shell prompt or add them at the end of your startup file. (I put them in /etc/profile.)

# SAP RFC SDK
export SAPRFC_DIR=/opt/SAP/rfcsdk/
export PATH=$SAPRFC_DIR/bin:$PATH

If necessary, log out and back in again. Now move to the SAPRFC extension for PHP directory and issue the phpize command. This will create the configure script that needs to be run next. After configure completes, run make and make install (as root) to finish installation. When everything finishes the file saprfc.so will be placed in your /usr/lib64/php/modules folder. Open you php.ini file located in /etc and add a line line

extension=saprfc.so

in the Dynamic Extensions section, save it, restart http server and you are ready to go.

[root@barbara ~]# service httpd restart
Stopping httpd:                                            [  OK  ]
Starting httpd:                                            [  OK  ]

Verification and testing

The very first check will be as simple as looking at the saprfc section of your phpinfo() page. You should be seeing something like :

The next thing will be to write an actual program that does something more practical like connecting to an SAP R/3 system and fetching back some useful data. Since this already a rather lengthy post, I will prepare and provide a test program some time later.

One last comment: It took me a while to figure that out. All examples that come along with the SAPRFC module for PHP, as well as the examples on the "SAP Developer's Guide to PHP" book, use <? instead of <?php to introduce php code. This is going to give you a lot of trouble when attempting to use these files with php 5.3.1 so before trying anything else, go to the files in the installation directory -- especially saprfc.php that you will always include -- and perform the necessary changes.

Friday, 27 November 2009

Enterprise class hardware for Enterprise class software

We wanted to set up a portable LAMP server for a home project we are playing with and there it is :

Since the little monster uses two SD cards as hard drives, it takes around four minutes to boot, but once it does boot, then its 900MHz Celeron CPU with its 1GB of RAM can stand up against a multi-threaded Java program gathering information from the local Ethernet and a complete Apache, PHP and MySQL server accessed for reporting.

Only thing is that the book is almost three times thicker than the machine.

Thursday, 26 November 2009

SAP: All the little text tables

Even after 5 years of tampering with ABAP, I still have problems getting used to the way SAP name their production tables. All those four letter German abbreviated names are explanatory enough to scare away any newcomer. Even the English descriptive texts that SAP provides along with each object are so Spartan, that I very seldom can make any good use out of them.

Luckily enough, I have just the right person available who seems to know all the four -- or five -- letter permutations that yield SAP table and field identifiers. (Thanks again Marilena), so today I am going to steal a few of her knowledge and give us code fragments that show how to get descriptive texts for various material and production related key's like material names, groups, blocking statuses, MRP controllers, etc.

We will start with the names of storage locations. The field and the data type for those are named lgode. The table that stores related info is t001l.

   DATA :
     storage_location TYPE lgort_d, 
     storage_location_name TYPE lgobe.

    " storage_location = '...'.
    SELECT SINGLE lgobe
      INTO storage_location_name
      FROM t001l
      WHERE lgort = storage_location.

The next one is easy. Even I know by heart how to get the material description given the material number, but I will put it down anyway. The thing to mention however, is that most SAP text tables are language dependent, so to get the text for the key, you also need to specify the language

    DATA :
      material TYPE matnr,
      material_descr TYPE maktx.

    " material = '...'.
    SELECT SINGLE maktx
      INTO material_descr
      FROM makt
      WHERE matnr = material
        AND spras = sy-langu.

Material types are stored in table t134t which is also language dependent.

    DATA :
      material_type TYPE mtart,
      material_type_descr TYPE mtbez.

    " material_type = '...'.
    SELECT SINGLE mtbez
      INTO material_type_descr
      FROM t134t
      WHERE mtart = material_type
        AND spras = sy-langu.

Our next table is t141t, that stores the various texts for the material blocking statuses.

    DATA :
      mat_blocking_status TYPE mstae,
      mat_blocking_status_descr TYPE mstb.

 " Material blocking status descriptions
    SELECT SINGLE mtstb
      INTO mat_blocking_status_descr
      FROM t141t
      WHERE mmsta = mat_blocking_status
        AND spras = sy-langu.

We wil now move to the inspiringly named t023t table, that provides access to the short and long names of material groups.

    DATA :
      material_group TYPE matkl,
      mat_group_short_descr type wgbez,
      mat_group_long_descr TYPE wgbez60.
    
    " material_group = "...".
    SELECT SINGLE wgbez wgbez60
      INTO (mat_group_short_descr, mat_group_long_descr)
      FROM t023t
      WHERE matkl = material_group
        AND spras = sy-langu.

Product hierarchy descriptions are found in in t179t.

    DATA :
      mat_hierarchy TYPE prodh_d,
      mat_hierarchy_descr TYPE vtext.

    " mat_hierarchy = '...'.
    SELECT SINGLE vtext
      INTO mat_hierarchy_descr
      FROM t179t
      WHERE prodh = mat_hierarchy
        AND spras = sy-langu.

Table t438t stores MRP type descriptions.

    DATA :
      mrp_type TYPE dismm,
      mrp_type_descr TYPE disbez.

    SELECT SINGLE dibez
      INTO mrp_type_descr
      FROM t438t
      WHERE dismm = mrp_type
        AND spras = sy-langu.

Last table for today's post will be t024d. this one contains MRP Controller descriptions and is not language but plant organized.

    DATA :
      plant TYPE werks_d,
      mrp_controller type idspo,
      mrp_controller_descr TYPE disnam.

    SELECT SINGLE dsnam
      FROM t024d
      INTO mrp_controller_descr
      WHERE dispo = mrp_controller
        AND werks = plant.

... and that is enough for one day.

Thursday, 12 November 2009

The CakePHP manual in PDF

if anybody wants a PDF version of the entire CakePHP manual as it appears in http://book.cakephp.org/, the book is available from here.

I created this document by copying and pasting text from the book website into an OpenOffice text document, because I wanted to have something to print and browse like a normal book. I have also added a few Oracle specific instructions. It is therefore possible, that some things may have slipped me. I will try and change this document every time I find an error or add a correction. Please feel free to post any mistakes you may find.

Last uploading: January 5-2010

Saturday, 31 October 2009

How to split a music file when given the corresponding .cue file.

This is the second time I needed to do this, so instead of googling again, I thought that I may post it here for future reference .....

Thanks to fl_bulgarelli from the Fedora Forum here is a small how to, when somebody gives you a complete album encoded in flac and a the corresponding .cue file, while what you want is to be able to split the album into smaller music files corresponding to the songs.

The magic command is: cuebreakpoints album.cue | shnsplit -o flac album.flac

More details about how to set this up, can be found here. (For the shake of completeness I will add that the cuetools package is available to install via yum. You will need to compile shntool though)

Tuesday, 29 September 2009

Fedora: Using and authenticating yum through a proxy

I have just finished a Fedora 11 installation here at the office. We needed a test bed for working with symfony and our EL machines did not provide php 5.2.

Next thing was to update the new machine and install additional software and that meant being able to go through a squid proxy server, that requires authentication.

A little bit of digging and the magic man yum.conf revealed the following :

Edit /etc/yum.conf and add the following lines:

proxy=http://proxy.domain.local:port
proxy_username=your_user_name
proxy_password=your_password

Needless to say that the same configuration works perfectly on CentOS 5.4 ...

When your machine is behind a proxy then, in order for many other programs -- like wget -- to function correctly, you also need to export the http_proxy variable. The correct format for it is :

export http_proxy=http://username:password@proxy.domain:port

Friday, 11 September 2009

Javascript : Yet an other email address validator.

I was building a Conduct us page the other day and run into the need for a JavaScript e-mail validator. I googled around a bit only to discover that the approaches were so many that I didn't know which one to choose.

So, eventually I did what every hard headed person would do, so I sat down amd wrote my own validateEmail function. The code is a merge of ideas coming from the fifth edition of Tom Mergino's and Dori Smith's JavaScript for the World Wide Web, and Anita Sudhakar's apprach from SmartWebby.

The resulted code looks like the following :

function validateMail(str)
{
  var at = "@";
  var dot = ".";
  var atPos = str.indexOf(at);    // position of '@' in the string
  var stringLength = str.length;  // position of '.' after the '@'
  var dotPos = str.indexOf(dot, atPos);
  var invalidChars = "~`/!#$%^&*()+={}[];:";
  var i;
  var badChar;

  // Step 1 Do not allow blank emails
  if ( str == "")
    return false;

  // Step 2 Make sure that the address does not contain invalid characters
  for (i = 0; i < invalidChars.length; i++) {
    badChar = invalidChars.charAt(i);
    if (str.indexOf( badChar) > -1)
      return false;
  }

  // Step 3: Make sure that the @ character is present and
  // that is not the first or the last character of the
  // email address string.
  if (atPos == -1 || atPos == 0 || atPos == stringLength)
     return false;

  // Step 4: Likewise make sure that a dot character exists and that
  // the distance between the @ and . is at least two characters apart
  if (dotPos == -1 || dotPos + 3 > stringLength)
      return false;

  // we have passed all tests let's hope that the email is valid
  return true;
}

The function should be called from an other function that will retrieve that value of an email filed and test it during form submit. A typical usage would be :


function validateEmailField( fieldID)
{
  var emailField = document.getElementById( eMailFieldID);
  var status = false;

  if (validateMail(emailField.value))
    status = true;
  else
    alert('Invalid email!');
  
  return status;
}