Category: "Web Applications"

Generic Class Wrapper

The objective of this wrapper is to provide a streamlined interface to data stored in a database.

Notes

  • Object properties are stored in an array. This makes managing them must easier. Upon instantiation, the array is loaded with empty strings.
  • Magic methods are used to set and return properties.
  • The properties have a one-to-one, exact mapping to the underlying database.
  • A generic object exists beneath the specific object. It accepts a table name, identifier column name, and an array of values.

item.class.php

<?php
/*mysql> show full columns from items;
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| Field   | Type             | Collation       | Null | Key | Default | Extra          | Privileges                      | Comment |
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
| item_id | int(10) unsigned | NULL            |      | PRI | NULL    | auto_increment | select,insert,update,references |         |
| name    | varchar(64)      | utf8_unicode_ci |      |     |         |                | select,insert,update,references |         |
| url     | varchar(128)     | utf8_unicode_ci |      | MUL |         |                | select,insert,update,references |         |
| text    | text             | utf8_unicode_ci |      |     |         |                | select,insert,update,references |         |
+---------+------------------+-----------------+------+-----+---------+----------------+---------------------------------+---------+
4 rows in set (0.02 sec)
*/

require_once 'obj.class.php';
Class Item Extends Obj
{
        private $aData;

        public function __construct()
        {
                $this->aData=array_fill_keys(array('item_id','name','url','text'),'');
                parent::__construct();
        }

        public function __set($name, $value)
        {
                $this->aData[$name] = $value;
        }

        public function __get($name)
        {
                if (array_key_exists($name, $this->aData))
                        return $this->aData[$name];
        }

        public function load($id)
        {
                $this->aData=parent::load('items','item_id',$id);
        }

        public function save()
        {
                return parent::save('items','item_id',$this->aData);
        }
}

?>

obj.class.php

<?php
require_once 'db.class.php';

Class Obj
{
        private $db;

        public function __construct()
        {
                $this->db=Database::singleton();
        }

        protected function load($sTable,$sIdName,$sId)
        {
                $aResult=false;
                $sQuery='SELECT * FROM `'.$sTable.'` WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($sId).'\'';
                $this->db->query($sQuery);
                $aResult=$this->db->fetch_assoc();
                $this->db->free_result();
                return $aResult;
        }

        protected function save($sTable,$sIdName,$aArgs)
        {
                $sWhere=' WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($aArgs[$sIdName]).'\'';
                unset($aArgs[$sIdName]);
                $sSet=$this->db->sPair($aArgs);
                $sQuery='SELECT * FROM `'.$sTable.'`'.$sWhere;
                $this->db->query($sQuery);
                if ($this->db->num_rows()>0)
                        $sQuery='UPDATE `'.$sTable.'` SET '.$sSet.$sWhere;
                else
                        $sQuery='INSERT INTO `'.$sTable.'` SET '.$sSet;
                $this->db->free_result();
                return $this->db->query($sQuery);
        }

        protected function remove($sTable,$sIdName,$sId)
        {
                $sQuery='DELETE FROM `'.$sTable.'` WHERE `'.$sIdName.'`=\''.$this->db->SQLescape($sId).'\'';
                return $this->db->query($sQuery);
        }

        public function __destruct()
        {
        }
}
?>

A search function will be added.

db.class.php

<?php
Class Database
{
        private static $instance;

        private $rLink;
        private $db;
        private $ini;
        private $rResult;

        public function __construct()
        {
                $this->ini = parse_ini_file('config.ini.php','true');
                $this->rLink = mysql_connect
                                ($this->ini['db']['host'],
                                 $this->ini['db']['user'],
                                 $this->ini['db']['password']);
                if ($this->rLink === false)
                        trigger_error(mysql_error());
                $this->db = mysql_select_db ($this->ini['db']['database'],$this->rLink);
                if ($this->db === false)
                        trigger_error(mysql_error());
                return true;
        }

        public static function singleton()
        {
                if (!isset(self::$instance)) {
                        $c = __CLASS__;
                        self::$instance = new $c;
                }
                return self::$instance;
        }

        public function SQLescape($s)
        {
                return mysql_real_escape_string($s,$this->rLink);
        }

        public function host_information()
        {
                return mysql_get_host_info($this->rLink);
        }

        public function query($sQuery)
        {
                $this->rResult=mysql_query($sQuery,$this->rLink);
                if ($this->rResult === false)
                        trigger_error(mysql_error());
                return $this->rResult;
        }

        public function fetch_assoc()
        {
                return mysql_fetch_assoc($this->rResult);
        }

        public function sPair($aPair)
        {
                $sReturn='';
                foreach ($aPair as $k => $v)
                        $sReturn .= "`$k`='".$this->SQLescape($v).'\', ';
                $sReturn=substr($sReturn,0,-2);
                return $sReturn;
        }

        public function sWhere($aWhere)
        {
                $sReturn='';
                foreach ($aWhere as $k => $v)
                        $sReturn .= " `$k`='".$this->SQLescape($v).'\' AND ';
                return substr($sReturn,0,-4);
        }

        public function sRegExp($aRegExp)
        {
                $sReturn='';
                foreach ($aRegExp as $k => $v)
                        $sReturn .= " `$k` REGEXP '".$this->SQLescape($v).'\' AND ';
                return substr($sReturn,0,-4);
        }

        public function num_rows()
        {
                return mysql_num_rows($this->rResult);
        }

        public function free_result()
        {
                mysql_free_result($this->rResult);
        }

        public function __destruct()
        {
                if (isset($this->rLink))
                        if ($this->rLink !== false)
                                mysql_close($this->rLink);
        }

        public function __clone()
        {
                trigger_error('Clone is not allowed.', E_USER_ERROR);
        }

}
?>

Web Application Help

Considerations for help systems for web applications.

  • User count If there aren’t many users, a simple text file should suffice. The next step up could be a word processing document or PDF. HTML is probably too cumbersome and costly for a small user base, unless it is expected to grow.
  • System complexity Simple systems, or those with excellent user interfaces need less documentation. Complex systems require more. Use the appropriate references to assist the user not only in using the application but working well in the system. For example, if they will be classifying information, provide links to materials that will help them enter the right data, not just valid data. This data should be separate, so it can be maintained by the appropriate people.
  • Target user type Internal users may be fine with a printed piece of paper. Other users may need a PDF, that they can search and print, and use familiar navigation. Potential clients should see the very finest help resources, because the content, navigation, presentation, and access to information may determine whether they purchase the product or not.
  • Features Should people be able to navigate between pages? Should they be able to search? Is a table of contents or index necessary? Type size should be considered, as well as whether images are important.
  • Images Screenshots should probably be avoided, since they may change and that requires documentation updates.
  • Author/Editor/Producer/Maintainer The person that produces the document should have the requisite skills. If it is an HTML document, the editor must know how to write decent HTML, including any necessary features.
  • Production The amount of effort and cost should be considered.

I think an excellent solution is a PDF, for the following reasons:

  • It is a popular format and the reader is a trusted piece of software
  • It has search and zoom
  • You can have tables of contents and indexes that link into the document
  • They print well
  • You can protect them from copying
  • You can link into them with anchors - into pages and sections
  • They are cost-effective to maintain and simple to track

Finishing Projects as Fast as Possible

All tasks have a certain minimum amount of time to complete, and no amount of persuasion, management, pressure, technology, or people can change that.

Reducing development tasks to the barest minimum is to define completion as delivery of a reasonably stable component. Unit testing is simply not done. The QA department, or another person or organization becomes responsible for the vast majority of testing.

Automated testing tools can greatly speed the cycle of delivery, defect identification, and verification. This is one way to speed a project along.

Although it is likely there will be a lot of bugs, if the software has a good architecture, most will resolve quickly.

This approach does require experienced engineers, because in order to build good code fast, people need a wide skill set, good tools, and plenty of self-discipline.

Using Crystal Icons with dojo's dijit tundra theme

The link above is a demonstration of the use of Crystal Project icons with dojo’s dijit tundra theme.

Crystal Project icons allow you to extend and customize tundra. Adding icons to inputs has the following advantages:

  • Many people can quickly look at the icon and enter the correct type of data, without reading the label or prompt. Obviously, this works best with familiar data types, such as times, dates, email addresses, and search strings.
  • If the inputs are in a table, the distance between the labels (column or row headings) and the inputs may be great enough that a quick visual reminder is valuable assistance for the user.
  • Multi-lingual applications almost always benefit from the addition of images.
  • The image are beautiful, they make the page look better.

The images chosen may have been intended for different uses. It is up to the designer to select the ones that work best.

There is also some overhead involved, each additional image is another HTTP request. The easiest way to improve perfomance is using browser caching, the next step would be to use ImageMagick to create sprites.

convert +append cp/16x16/apps/xclock.png cp/16x16/apps/yahoo_protocol.png new.png

http://wirehopper.com/design/new.png

Notes on Building a MultiLingual RIA

  • RIAs must validate on both the client and server-side. Thus, the server must have access to validation data. In that regard, it is easier to have the server handle the language-sensitive validation and then send it to the client, rather than have all language-sensitive elements (prompts, messages, validation) at the client.
  • Application common validation should be allowed. For example, allowing a dash in names - which may not be considered part of an alphabet should be consistent for all supported languages.
  • Localization is a separate issue. It can be addressed in many different ways. The amount of time spent on localization should be carefully considered. For a limited audience RIA, localization may be addressed with screen prompts and documentation which allows users unfamiliar with the formats to work with them. This approach is suitable if the localized elements are limited or the user is unlikely to use them often. For pages that will be used frequently, localization should be considered. Localization should be consistent throughout the application, meaning if one page is localized, or one type of data is, all should be.
  • Language strings can be stored in the database or in files of name/value pairs. They should be constructed in such a way that word order can be adjusted for language, this may mean more strings or multi-word strings.
  • Language resources should be dispersed such that pages only retrieve the data necessary to support the active page.
  • Some parts of the application may be based on the native language of the server. This should be supported with documentation.
  • If it is likely an application will be multi-lingual, it should be built in from the beginning, it will greatly increase the cost of development to change a single language site to multilingual if it wasn’t built with language independence.
  • Frameworks and toolkits like Zend Framework (http://framework.zend.com) and dojo (http://dojotoolkit.org) have execellent resources. It is much better to use these as starting points than to try to reinvent the wheel.
  • Report files and any emails should also be multilingual. Ideally, these should draw language data from the same source as the application.
  • Log files need not be multilingual.
  • Templates should be language independent.
  • Images should be language independent, or language sensitive versions should be developed.
  • Flags are an excellent way to allow selection of language.