Showing posts with label Code. Show all posts
Showing posts with label Code. Show all posts

Tuesday, 13 December 2011

Java enums: A complete example

I started programming with Pascal, so I have deep feelings for enumerated types. To be honest I feel that they are a much more easy going approach to modeling that the long lists of constants we used in the C programming language or in the pre JDK5 versions of Java.

Java enums are so very flexible and the guide on the Oracle website says it all. What I wanted to do in this post is to provide a full working example, as a reference to myself and hopefully any ... lost soul, that demonstrates how to bind data with each enum type value and also how to implement the previous and next methods,

package gr.abakalidis.sample.model;

/**
 * Possible image states and relative sizes regarding the actual bitmaps
 * loaded from a Web server.
 * 
 * @author Thanassis Bakalidis
 * @version 1.0
 */
public enum ImageState {
    NOT_LOADED(0, 0), SMALL(320, 240), MEDIUM(800, 600), LARGE(1024, 768);

    private final int width;
    private final int height;

    ImageState(int width, int height)
    {
        this.width = width;
        this.height = height;
    }

    public int getHeight()
    {
        return this.height;
    }

    public int getWidth()
    {
        return this.width;
    }

    /**
     * Get the next value ion the series of enumeration 
     * 
     * @return the next value in the series or null if already at end of values
     */
    public ImageState getNext()
    {
        return this.ordinal() < ImageState.values().length - 1 
            ? ImageState.values()[this.ordinal() + 1] 
            : null;
    }
 
    /**
     * Get the previous value in the series of enumeration
     * 
     * @return the next value in the series on null if called for the first va;ue 
     */
    public ImageState getPrevious()
    {
        return this.ordinal() == 0 
            ? null 
            : ImageState.values()[this.ordinal() - 1];
    }
}

Tuesday, 1 November 2011

CakePHP: An edit form with a cancel button (1.3 and 2.x)

When developing database CRUD applications with CakePHP, sooner or later you end up writing view code looking more or less like this
<div class="my model form">
<?php echo $this->Form->create('MyModel');?>
 <fieldset>
  <legend></legend>
 <?php
  echo $this->Form->input('id');
  echo $this->Form->input('name');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
This creates a nice form with a submit button at the end that every self respecting user can press to create a new record or modify the data of an existing one. It is also logical that you place a link to the index page somewhere near the form, so your users know where to go in case they change their mind about altering the database data.
With my users this time is was different. The form had to contain a cancel button. So how does one do it? If you are using the cake bake script and wish to have two nice round green buttons right at the bottom of your form then replace the last two lines of the previous code fragment with the following:
     ...
     <div class="submit">
         <?php echo $this->Form->submit(__('Submit', true), array('name' => 'ok', 'div' => false)); ?>
         <?php echo $this->Form->submit(__('Cancel', true), array('name' => 'cancel','div' => false)); ?>
     </div>
     </fieldset>                   
 <?php echo $this->Form->end();?>
The next thing to know from inside the controller code, is which button was pressed before the data were posted and that is available inside the 'form' array of the Controller::params property.
So a simple modification like the one on the following code :
        public function edit($id = null)
        {
            if (!$id && empty($this->data)) {
               $this->Session->setFlash(__('Invalid property', true));
               $this->redirect(array('action' => 'index'));
            }
            if (!empty($this->data)) {
                // abort if cancel button was pressed  
                if (isset( $this->params['form']['cancel'])) {
                    $this->Session->setFlash(__('Changes were not saved. User cancelled.', true));
                    $this->redirect( array( 'action' => 'index' ));
                }

                // proceed to save changes as usual
        }
... and everyone is happy.

Edit: Alternatively, if working with CakePHP version 2.4 then the sane info is inside the data array of the controller's request property.
So the previous code gets rewritten like this
        public function edit($id = null)
        {
            if (!$this->MyModel->exists($id)) {
                throw new NotFoundException(__('Invalid record'));
            }
            if ($this->request->is(array( 'post','put'))) {
                if (isset($this->request->data['cancel'])) {
                    $this->Session->setFlash(__('Changes were not saved. User cancelled.'));
                    return $this->redirect( array( 'action' => 'index' ));
                }

                // proceed to save changes as usual
        }
There is one last thing though... the cancel button in your form should also indicate that no form validation should be performed at the browser level. This can be accomplished by setting the 'formnovalidate' key of the Form::input options parameter to TRUE. So the whole cancel button creation tag should now look like this:
        <div class="submit">
            <?php echo $this->Form->submit(__('Create Account'), array('name' => 'ok', 'div' => FALSE)); ?>
            <?php echo $this->Form->submit(__('Cancel'), array('name' => 'cancel', 'formnovalidate' => TRUE, 'div' => FALSE)); ?>
        </div>
    Form->end(); ?>

Wednesday, 19 October 2011

A new version of the CakePHP QBE component

I have just developed and started testing of a new version of my QBE component.

Major changes are that the component now accepts the model name as an initialization parameter, it provides a new ~ X Y operator to implement the between clause and that support for a different search and results page is now more clear.

The code for the component, along with usage details can be found in my GitHub repository available from this link:.

Friday, 5 August 2011

CakePHP The Query by Example Component

After the previous post, regarding how to create and use a input QBE form, here is my simple QBE component.

To use it, paste the following code in a file named qbe.php into your APP/controllers/components directory.

<?php
/**
 * @class QbeComponent
 * Convert posted data entered in a pseudo Query by Example fashion
 * from a CakePHP Form into Model::find() acceptable conditions.
 *
 * @author: Thanassis Bakalidis
 * @version: 1.0
 */
class QbeComponent extends Object {
    // sesion keys for saving and retrieving controller data
    const CONDITIONS_SESSION_KEY = 'SRCH_COND';
    const FORM_DATA_SESSION_KEY = 'SRCH_DATA';

    // supported SQL operators
    private $SQL_OPERATORS = array(
        'IN', '<>', '>=', '<=',
        '>', '<'
    );

    var $owner;     // the controller using the component

    /**
     * @name initialize
     * The initialize method is called before the controller's
     * beforeFilter method.
     */
    function initialize(&$controller, $settings=array())
    {
        $this->owner =& $controller;
    }

    /**
     * @name: getSearchConditions()
     * Return an array to be used as search conditions in a find
     * based on the controller's current data
     * @param : string $modelName name of the model to search controller data
     * @version: 1.3
     */
    function getSearchConditions($modelName = null)
    {
        if ($modelName == null)
            return null;

        // create speciffic keys for the model andcontroller
        $sessionConditionsKey = sprintf("%s-%s-%s",
                                self::CONDITIONS_SESSION_KEY,
                                $this->owner->name,
                                $modelName
                            );
        $sessionDataKey = sprintf("%s-%s-%s",
                                self::FORM_DATA_SESSION_KEY,
                                $this->owner->name,
                                $modelName
                            );

        if (empty($this->owner->data)) {
            // attempt to read conditions from sesion
            $conditions = $this->owner->Session->check($sessionConditionsKey)
                ? $this->owner->Session->read($sessionConditionsKey)
                : array();
            $this->owner->data = $this->owner->Session->check($sessionDataKey)
                ? $this->owner->Session->read($sessionDataKey)
                : array();
        } else {
            // we have posted data. Atempt to rebuild conditons
            // array
            $conditions = array();
            foreach( $this->owner->data[$modelName] as $key => $value) {
                if (empty($value))
                    continue;

                $operator = $this->extractOperator($value);

                if (is_array($value)) {
                    // this can only be a date field

                    $month = $value['month'];
                    $day = $value['day'];
                    $year = $value['year'];

                    // We want all three variables to be numeric so we 'll check their
                    // concatenation. After all PHP numbers as just strings with digits
                    if (is_numeric($month.$day.$year) && checkdate( $month, $day, $year)) {
                        $conditionsKey ="$modelName.$key";
                        $conditionsValue = "$year-$month-$day";
                    } else
                        continue;
                } else {
                    // we have normal input, remove any leading and trailing blanks
                    $value = trim($value);                          
                    // and check the operator given
                    if ($operator === '' && !is_numeric($value)) {
                        // turn '='' to 'LIKE' for non numeric data
                        // numeric data will be treated as if they
                        // have an wquals operator
                        $operator = 'LIKE';
                        $value = str_replace('*', '%',  $value);
                        $value = str_replace('?', '.',  $value);
                    } else if ($operator === 'IN') {
                        // we need to convert the input string to an aray
                        // of the designated values
                        $operator = '';
                        $value = array_filter(explode( ' ', $value));
                    }

                    $conditionsValue = $value;
                    $conditionsKey = "$modelName.$key $operator";
                }

                // add the new condition entry
                $conditions[trim($conditionsKey)] = $conditionsValue;
            }

            // if we have some criteria, add them in the sesion
            $this->owner->Session->write($sessionConditionsKey, $conditions);
            $this->owner->Session->write($sessionDataKey, $this->owner->data);
        }

        return $conditions;
    }

    private function extractOperator(&$input)
    {
        if (is_array($input))
            return '';

        $operator = strtoupper(strtok($input, ' '));

        if (in_array($operator, $this->SQL_OPERATORS)) {
            $opLength = strlen($operator);
            $inputLength = strlen($input);
            $input = trim(substr( $input, $opLength, $inputLength - $opLength));
        } else {
            $operator = '';
        }

        return $operator;
    }
}

Next modify your AppController to use the component by adding 'Qbe' to the $components array. An example would be:

    var $components = array(
        'RequestHandler',
        'Auth',
        'Session',
        'Qbe'
    );

Now crate a query form before the results table into your index view. Copying the form from the corresponding model's create or edit pages is usually enough to get you started.

Finally, modify the corresponding controller method to use the posted data in order to create search conditions. A typical example for a model named Product would be something like that:

    function index()
    {        
        $this->Product->recursive = 0;
        $conditions = $this->Qbe->getSearchConditions($this->Product->name);
        $products = $this->paginate('Product', $conditions);

        $this->set( 'products', $products);
        $this->prepareCombos();
    }

The prepareCombos() method is a simple private function I created by copying all the find)'list', ...) commands that the cake bake script created after the add() and edit() controller methods, so that all foreign key fields have correct value ranges, when the input form gets displayed. For more information and example code, see the previous post which presents the same functionality, by adding code to the AppController.

Tuesday, 28 June 2011

CakePHP: Turn an input form into a query form (Version 2)

CakePHP makes constructing input forms easy. The Form helper object's input() methods provides us with the necessary intelligence, to display and get input data from our users, then store it into the controller's $this->data property using the format required by the framework's Model->save() method, so they can eventually be written to the database, with no need for any elaborate code. Furthermore, given the fact that cake's cake script will bake all that standard code in a flash. it becomes a matter of minutes to create a full featured CRUD application, starting from just a good database design.

One thing I find missing from the baked code, is the ability to create input forms whose data will be used as search criteria -- in a QBE fashion -- in an index or display page. My solution requires us to convert the submitted data into a form that is compatible with the Model->find()'s $conditions parameter and then feed this to the Model->find() or the Controller->paginate() methods in order to limit the number of returned items.

As Richard pointed out during the first version of this post, cake offers the postConditions() method, which does this out of the box and the truth of the matter is that I had completely missed that when I started coding. After looking at the CakePHP code and comparing it with my approach, I can only say that with the code below, you don't have to worry about operators and providing complex parameters. The latest version will allow users to type their desired operator before the value in a QBE like fashion. The standard Cake approach gives more accurate control over the entire process, but I am not sure how to create a UI that will allow users to change operators dynamically .

So, to get started with my alternative version, the typical index() method, for a model named Product, using this approach will have to look somehow like this:

    function index()
    {        
        $this->Product->recursive = 0;
        $conditions = $this->getSearchConditions($this->Product->name);
        $products = $this->paginate('Product', $conditions);

        $this->set( 'products', $products);
        $this->prepareCombos();
    }

The important part here s obviously the $this->getSearchConditions() function that will get the controllers $data property and transform it into the $conditions array. The perfect place to put the code for that would be our application's controller.

class AppController extends Controller {
    const CONDITIONS_SESSION_KEY = 'SRCH_COND';
    const FORM_DATA_SESSION_KEY = 'SRCH_DATA';

    private $SQL_OPERATORS = array(
        'IN', '<>', '>=', '<=',
        '>', '<'        
    );

    /**
     * @name: getSearchConditions()
     * @access: protected
     * @author: Thanassis Bakalidis
     * @param : string $modelName name of the model to search controller data
     * Return an array to be used as search conditions in a Model's find 
     * method based on the controller's current data
     * @version: 1.4
     */
    protected function getSearchConditions($modelName = null)
    {
        if ($modelName == null)
            return null;

        // create speciffic keys for the model andcontroller
        $sessionConditionsKey = sprintf("%s-%s-%s",
                                self::CONDITIONS_SESSION_KEY,
                                $this->name,
                                $modelName
                            );
        $sessionDataKey = sprintf("%s-%s-%s",
                                self::FORM_DATA_SESSION_KEY,
                                $this->name,
                                $modelName
                            );

        if (empty($this->data)) {
            // attempt to read conditions from sesion
            $conditions = $this->Session->check($sessionConditionsKey)
                ? $this->Session->read($sessionConditionsKey)
                : array();
            $this->data = $this->Session->check($sessionDataKey)
                ? $this->Session->read($sessionDataKey)
                : array();
        } else {
            // we have posted data. Atempt to rebuild conditons
            // array
            $conditions = array();
            foreach( $this->data[$modelName] as $key => $value) {
                if (empty($value))
                    continue;

                $operator = $this->extractOperator($value);
                
                if (is_array($value)) {
                    // this can only be a date field

                    $month = $value['month'];
                    $day = $value['day'];
                    $year = $value['year'];

                    // We want all three variables to be numeric so we 'll check their
                    // concatenation. After all PHP numbers as just strings with digits
                    if (is_numeric($month.$day.$year) && checkdate( $month, $day, $year)) {
                        $conditionsKey ="$modelName.$key";
                        $conditionsValue = "$year-$month-$day";
                    } else
                        continue;                        
                } else {
                    // we have normal input check the operator given                    
                    if ($operator == '' && !is_numeric($value)) {
                        // turn '=' to 'LIKE' for non numeric data
                        // numeric data will be treated as if they
                        // have an wquals operator
                        $operator = 'LIKE';
                        $value = str_replace('*', '%',  $value);
                        $value = str_replace('?', '.',  $value);                        
                    } else if ($operator === 'IN') {
                        // we need to convert the input string to an aray
                        // of the designated values
                        $operator = '';
                        $value = array_filter(explode( ' ', $value));
                    } 
                    
                    $conditionsValue = $value;
                    $conditionsKey = "$modelName.$key $operator";                    
                }

                // add the new condition entry
                $conditions[trim($conditionsKey)] = $conditionsValue;
            }

            // if we have some criteria, add them in the sesion
            $this->Session->write($sessionConditionsKey, $conditions);
            $this->Session->write($sessionDataKey, $this->data);
        }

        return $conditions;
    }

    private function extractOperator(&$input)
    {     
        $operator = strtoupper((strtok($input, ' '));
        
        if (in_array($operator, $this->SQL_OPERATORS)) {
            $opLength = strlen($operator);
            $inputLength = strlen($input);
            $input = trim(substr( $input, $opLength, $inputLength - $opLength));                        
        } else {            
            $operator = '';
        }
        
        return $operator;
    }

This was the hard part. The next ones have to do with only copy and paste. So get the form that cake created from your add or edit views and copy it before the table in the index view page. You may possibly wish to remove some unwanted input fields and change the value of the $this->Form->end() function's parameter from "Submit" to "Search".

So we are almost there. One last stroke and we are ready. Remember the $this->prepareCombos(); last comand on the index action? Well, that is a simple private method I created by copying all the find)'list', ...) commands that the cake bake script created after the add() and edit() methods, so that all foreign key fields have correct value ranges, when the input form gets displayed. For my product's controller for example the function looks like this:

    private function prepareCombos()
    {
        $productTypes = $this->Product->ProductType->find('list');
        $productCategories = $this->Product->ProductCategory->find('list');
        $suppliers = $this->Product->Supplier->find(
                                                'list',
                                                array(
                                                    'order' => array(
                                                        'id' => 'asc'
                                                    )
                                                )
                                            );
        $qualities = $this->Product->Quality->find('list');        
        $this->set(compact('productTypes', 'productCategories', 'suppliers', 'qualities'));
    }

Notes

  • In case your model contains validation rules like range then the default input element that the form helper creates , will contain an appropriate maxlength attribute, that will not allow your users to type more than the maximum allowed characters for the field. Say, for example that you have a rule for a field named width, that looks like this
           'width' => array (
                'rule' => array('range', 1, 999),
                'message' => 'Width must be between 1 and 999 mm',
                'last' => true
           )
          
    ... then the actual HTML input element will have a maxlength attribute of 3. This is not going to make your query UI very usable, so make sure that the search form's echo $this->Form->input( ... statement provides an appropriate hardcoded value for maxlength.
  • The next thing to consider, is what will happen when our users provide wrong inputs, say they type =< 13 instead of >= 13. In that case the code -- as it is now -- will create an SQL statement of the form select ... where Model.Field LIKE '=< 13' , that will provide an empty result set, but is still valid SQL. Any workaround for this type of situation would make our code even more complicated than it is now, so I am not going to spend any more thought on this.
  • After going this far, I believe that the next reasonable thing would be to move the functionality into a new component, that will take away all the complexity from the AppController class, but this is probably going to be the subject of a forthcoming post.

Thursday, 2 June 2011

C and UTF-8 data in Linux

During the last few days, I found myself struggling to create a PHP extension what would allow PHP to convert common characters between Greek and Latin, so that typed codes would for instance, always use the same version of 'A' no matter the language that the user keyboard is switched to. You see whether you type a Latin 'A' (U+0041) or a Greek 'Α' (U+0391) the two letters appear to be the same despite the difference in their internal representation. The same applies to other common letters like 'E', 'P' and 'Y'

I reckoned that this conversion and translation of characters based on their position on some given string would be an excellent chance to remember string pointers from my student C days, so I set out to create a library with functions called latinToGreek() and greekToLatin() that would do the conversion and return the new string.

Like most people in similar positions, I tried googling for C and Unicode howtos and run into some very good articles but nothing was in the form of a recipe that I could follow. I did read many of them and managed to get the work done. The basic idea here is that since UTF-8 uses a variable number of bytes per character, we need to convert UTF-8 strings into wchat_t strings, then process them like they were ordinary constant length null terminated character arrays and finally convert them back to UTF-8 single byte character strings in order for them to display correctly.

Following is the check list of things to do before actually playing with UTF-8 encoded Unicode.

  1. Use setlocale to set the current program locale to something that supports UTF-8. For example 'en_US.UTF-8' is just fine.
    Bear in mind, that the default locale for C programs in the "C" locale and unless you set the locale correctly nothing will work as expected.
  2. Read your input using normal char[] arrays. Just make sure that hey are big enough. Remember that UTF-8, uses one, two or even more bytes per individual character, so a logical guess would be to allocate an array at least twice the size of your maximum anticipated input.
    Do not forget that good old strlen() will still give you the size of your input in bytes, but not in characters.
  3. Convert multibyte input to wchar_t[] input in order to perform any processing. Remember that wchar_t constants need to be prefixed by an L, so '\0' is now L'\0'.
    Functions mbstowcs and wcstombs can be used to perform the conversion to and from.
  4. Perform your processing as you would ordinarily do using wchat_t data and wchar_t speciffic functions. For example toupper() for wchar_t is now towupper().
  5. Convert your data back to multibyte format using wcstombs and return them to the user.

... and that's about it.

Tuesday, 19 April 2011

CakePHP: Storing paging info in the Session

Warning: This post applies to CakePHP 1.3. If you are using CakePHP 2, then visit the updated version available through here.

There is no way you can miss it. Sooner or later you 'll end up having baked your controllers and views only to discover that when you edit a record from the 4th page and press submit on the edit page, much to your annoyance you end up on page one. Needless to say that whatever sort order was there is now gone with the wind.

I have googled a bit and found out the following article in Stack Overflow. The solutions provided over there seemed rather complicated to me, so I decided to craft my own.

Now here is the deal: From what I have seen in cake 1.3, paging is controlled by the value of three parameters: page, sort and direction. All we need to do is provide a way to store them in the session and then have the controller's redirect method inject these back into the URL parameters when they are not present.

<?php
     
class AppController extends Controller {
    const SESSION_KEY = 'PagingInfo.%s.%s';
        
    public $components = array('Session');            

    protected function savePagingInfo()
    {
        // create an array to hold the paging info
        $argsToSave = array();

        // remove paging info from passed args
        foreach( $this->passedArgs as $key => $value)
            if ($key == 'page' || $key == 'sort' || $key == 'direction')
                $argsToSave[$key] = $value;

        // abort if any paging info was found
        $sessionKey = $this->buildSessionKey();
        if (!empty($argsToSave)) {
            $this->Session->write( $sessionKey, $argsToSave);
            return;
        }
        
        // no paging info let's see if we have something in the sesion
        if ($this->Session->check($sessionKey)) {
                $pagingInfo = $this->Session->read($sessionKey);
                $this->passedArgs = array_merge( $this->passedArgs, $pagingInfo);
        }
    }
                        
    private function buildSessionKey($action = NULL)
    {
        if (empty($action))
            $action = $this->action;
            
        return sprintf( self::SESSION_KEY, $this->name, $action);
    }
}
     
?>    

This approach may be simplistic but in my case it worked. All I had to was call $this->savePagingInfo(); from my index() action and then let the rest of the baked code do its work.

Note

And one last comment, there are many cases that you need paging during display of master detail record sets. In cases like these savePagingInfo() will save paging status for the detail records matching the current master and when the master changes, the paging in the session becomes invalid. My simple solution to this is to add a 'page' => 1 entry in the $url params pointing to a master record change. With the design of the application I am currently working on this proves just good enough.

Wednesday, 1 December 2010

A simple GUID generator function for Delphi

Here is a little code sample, that I run into the other day. The actual URL is here, but since the original site is now closed, I thought that I could save this posting on my blog as well.

If anybody running CodeGear RAD Studio 2007 ever wishes to play with GUID's then this function is a real must have, All credit goes to the original poster.

interface

function GUIDString: String;

implementation

uses ActiveX;
  
function GUIDString: String;
var
  Guid: TGUID;
  wsGuid: WideString;
begin
  Result := '';
  { create the 128 bit integer }
  if CoCreateGuid(Guid) = S_OK then begin
    { create a buffer of suitable size }
    SetLength(wsGuid, 80);
    { convert the 128 bit integer into a Unicode string }
    if StringFromGUID2(Guid, POleStr(wsGuid), 80) > 0 then
      { convert the Unicode to a String for output }
      Result := OleStrToString(PWideChar(wsGuid));
    end;  { if CoCreateGuid ... }
end;

Tuesday, 7 April 2009

Linux: Getting passed rsync's most notorious error messages and determining whether a shell is interactive or not.

While trying to back up my personal data using rsync -aruvzh --delete /home/thanassis/jdevhome/ thanassis@192.168.1.68:/home/thanassis/jdevhome I run into the following error message.

protocol version mismatch - is your shell clean?
(see the rsync man page for an explanation)
rsync error: protocol incompatibility (code 2) at compat.c(69)

A little googling and a final look at the rsync manual revealed that :

DIAGNOSTICS
     rsync occasionally produces error messages that may seem a little cryp-
     tic.  The  one that seems to cause the most confusion is “protocol ver-
     sion mismatch — is your shell clean?”.

     This message is usually caused by your startup scripts or remote  shell
     facility  producing  unwanted garbage on the stream that rsync is using
     for its transport. The way to diagnose this  problem  is  to  run  your
     remote shell like this:

            ssh remotehost /bin/true > out.dat

     then  look  at out.dat. If everything is working correctly then out.dat
     should be a zero length file. If you are getting the above  error  from
     rsync  then  you  will probably find that out.dat contains some text or
     data. Look at the contents and try to work out what  is  producing  it.
     The  most  common cause is incorrectly configured shell startup scripts
     (such as .cshrc or .profile) that contain output  statements  for  non-
     interactive logins.

Needless to say that fortune was the one to blame. My .bashrc file had the following last statements since the days of SuSE 8.2 and although I am now using a completely different distro, I still refuse to get them out of my startup files.

# Something to remember the old SuSE days
if [ -x /usr/bin/fortune ] ; then
    echo
    /usr/bin/fortune
    echo
fi

So, the solution would be to display a fortune cookie only when the current shell is interactive and then it was time for the Bash Reference Manual to come to our rescue. Their answers are straightforward :

To determine within a startup script whether Bash is running interactively or not, examine the variable $PS1; it is unset in non-interactive shells, and set in interactive shells. Thus:
     if [ -z "$PS1" ]; then
        echo This shell is not interactive
     else
        echo This shell is interactive
     fi
Alternatively, startup scripts may test the value of the `-' special parameter. It contains i when the shell is interactive. For example:
     case "$-" in
     *i*)  echo This shell is interactive ;;
     *)    echo This shell is not interactive ;;
     esac

In my case I wanted to run fortune on interactive shells only. So again I modified my bashrc file to look like :

# Display a fortune cookie on interactive logins only
if [ -n "$PS1" ]; then
        # Some people don't like fortune. If you uncomment the following lines,
        # you will have a fortune each time you log in ;-)
        if [ -x /usr/bin/fortune ] ; then
                echo
                /usr/bin/fortune
                echo
        fi
fi

... and that did it.

Tuesday, 11 November 2008

JavaScript: Dynamically changing CSS properties of any DOM object

I was trying to create a web album the other day and played around with the gThumb image program. The program offers various templates that allow you to create web galleries with various different styles and it turned out that for me, the BestFit template was the best looking of them all.

The interesting part of the story is that when you create a web gallery based on the BestFit template, you also get a number of JavaScript files containing various programming goodies like the get and set property functions that I am displaying on this post. The copyright of these functions is Copyright (C) 2005-2006 Free Software Foundation, Inc., thus my thanks here go to Rennie deGraaf who provided both the template and the opportunity to get access to these functions.

So changing the width, height, or display mode of any DOM object found in your web page has never been so simple.

// set a property to a given value on an object
function setProperty ( obj, prop, val )
{
    if (obj.style.setProperty) // W3C
        obj.style.setProperty(prop, val, null);
    else // MSIE
        eval("obj.style." + prop + " = \"" + val + "\"");
}
 
// gets a style property for an object
function getProperty ( obj, prop )
{
    if (document.defaultView && 
        document.defaultView.getComputedStyle) {
        var val = document.defaultView.getComputedStyle( obj, 
                    null).getPropertyValue(prop)
        if (val)
            return val;
        else
            return eval("document.defaultView.getComputedStyle(obj,null)." + prop);
    }
    else if (window.getComputedStyle) // Konqueror
        return window.getComputedStyle(obj,null).getPropertyValue(prop);
    else if (obj.currentStyle)        // MSIE
        return eval('obj.currentStyle.' + prop);
}

Wednesday, 2 July 2008

PHP: Number of rows from query

The idea comes from the developers of Oracle ADF, I have seen it work on JDeveloper and I thought that I can use it in PHP. The trick here is to place the entire query inside a SELECT count(*) from ($query) statement.

Once you know what to do implementation is as easy as witting the code below. The only comment is that the function references an already initialized external mysqli class variable that connects to the database, which if you feel like it may also be passed as a function parameter.

    /**
      * returns the number of records  of the query passed as parameter 
      */
    function numRecordsOfQuery( $a_query) 
    {
        global $mysqli;

        $count_query = " 
                   SELECT count(*) as TotalLines 
                     FROM ($a_query) MyQuery";
        $result = $mysqli->query($count_query);
        // echo $count_query;
        $row = $result->fetch_array( MYSQLI_ASSOC);
        $numRecords = $row['TotalLines'];
        $result->close();
        
        return $numRecords;
    }

Note The MyQuery references is mandatory. MySQL will return an error if the enclosed query is not referenced.

Friday, 30 May 2008

Oracle ADF: Enriching the actions of a ViewObject

Based on the technique described two posts back regarding creation of a custom ViewObject that offers ready to drag actions, I now give you another simple yet very handy function. The showAllRows() function when added to the AB_BaseViewObject methods, allows you to create a button in a search page that removes all view criteria and executes the current query, very useful for cases when uses enter lots of view criteria and then need to display the entire contents of the entire row with a single click.

    public void showAllRows()
    {
        clearViewCriterias();
        executeQuery();
    }

Remember that in order for the action to appear in the JDeveloper Data Control Palette like in the following picture, you must include the showAllRows() function to the view objects client interface.

Wednesday, 21 May 2008

Oracle ADF: Duplicating a ViewObject's current row

I wanted to add a Create Like functionality that would allow a new row to be inserted in a ViewObject then drive the user to an edit page where he or she would make ant changes required for valid insertion of the new row to the database and avoid having to re-enter everything from scratch.

In order to accomplish this I created the following method in the ViewObject's implementation class.

import oracle.jbo.AttributeDef;
import oracle.jbo.Row;
import oracle.jbo.ReadOnlyAttrException;
import oracle.jbo.TooManyObjectsException;

. . .

    /**
     * create and insert a row similar to the current
     */
    public void createLike()
    {
        // get the current row to dublicate
        Row currentRow = getCurrentRow();
        if (currentRow == null)
            return;
            
        // create new one
        Row newRow = createRow();
        
        // get the attribute values for the current attribute
        Object[] myValues = currentRow.getAttributeValues();
        
        for (int i = 0; i < currentRow.getAttributeCount(); i++)
            try {
                AttributeDef d = getAttributeDef(i);
                    
                if (d.getUpdateableFlag() != AttributeDef.READONLY)                
                    newRow.setAttribute( i, myValues[i]);                
            }    
            catch (ReadOnlyAttrException roe) {
                continue;
            }
            catch (Exception e) {
                // it is almost certain that the primary key will be
                // violated so the user must do something in the 
                // page following execution of the method.
                if (!(e instanceof TooManyObjectsException))
                    System.out.println(e.toString());
            }
            
        insertRow(newRow);        
    }

The code works fine and copies the values all columns from the current row into a new one except that of the last column that constitutes the primary key. That way the user will have to make the appropriate changes to the new record, so it can be inserted correctly.

There are a few points worth mentioning in this apprach

  • if your view object contains attributes from multiple entity objects then the the key attributes of the referenced entities that gets inserted by the framework appear to be updatable -- hence the catch (ReadOnlyAttrException roe) line. I have brought this to the JDeveloper forum but got no suggestions.
  • Using the above technique requires that you expose the createLike() method to the ViewObject's client interface. See the previous post for details.
  • The most probable use of this method will be to place a command button into an <af:tableSelectOne> facet in order to drive your users to an edit page with values already set. On most occasions the edit page will contain a commit button that will perform the final post. My suggestion is to use a backing bean to invoke the commit action like I discuss in a previous article named ADF/JSF Avoiding uncaught exceptions after commit that will prevent the controller layer to change back to the browse page in case of any errors.

Thursday, 15 May 2008

Oracle ADF: A view object with cancelInsert and cancelEdit operations

I have already discussed the problem of providing a cancel button in an ADF/JSF edit page. Here is an alternative that allows the usage of a base view object that provides both a cancel edit and a cancel insert method.

I have come to believe that this method is the most appealing so here is a brief outline of how to use it :

First create the following class in the queries package of you model application.

package myapp.model.queries;

import oracle.jbo.Row;
import oracle.jbo.server.ViewObjectImpl;

public class AB_BaseViewObject
    extends ViewObjectImpl {

    public AB_BaseViewObject()
    {
    }
    
    /**
     * Refresh the view obejct's current row from the dataset practically 
     * undoing any changes.
     */
    public void cancelEdits() 
    {
        Row r = getCurrentRow();

        if (r != null) 
            r.refresh( Row.REFRESH_WITH_DB_FORGET_CHANGES);
    }
    
    /**
     * Undo changes and get rid of new rows
     */
    public void cancelInsert()
    {
        clearCache();      
    }
}

Next create Java Classes that extend the AB_ViewObject for all the view objects that require editing in your project. Publish the cancelXXX() methods on the view interface like in following picture.

Finally create buttons on the edit or create pages in the ViewController project that call the methods.

Wednesday, 23 April 2008

Java: Formatting a Number Using a Custom Format

The example comes form the The Java Developers Almanac 1.4 and I am adding it here to serve only as a quick reference note.

    // The 0 symbol shows a digit or 0 if no digit present
    NumberFormat formatter = new DecimalFormat("000000");
    String s = formatter.format(-1234.567);  // -001235
    // notice that the number was rounded up
    
    // The # symbol shows a digit or nothing if no digit present
    formatter = new DecimalFormat("##");
    s = formatter.format(-1234.567);         // -1235
    s = formatter.format(0);                 // 0
    formatter = new DecimalFormat("##00");
    s = formatter.format(0);                 // 00
    
    
    // The . symbol indicates the decimal point
    formatter = new DecimalFormat(".00");
    s = formatter.format(-.567);             // -.57
    formatter = new DecimalFormat("0.00");
    s = formatter.format(-.567);             // -0.57
    formatter = new DecimalFormat("#.#");
    s = formatter.format(-1234.567);         // -1234.6
    formatter = new DecimalFormat("#.######");
    s = formatter.format(-1234.567);         // -1234.567
    formatter = new DecimalFormat(".######");
    s = formatter.format(-1234.567);         // -1234.567
    formatter = new DecimalFormat("#.000000");
    s = formatter.format(-1234.567);         // -1234.567000
    
    
    // The , symbol is used to group numbers
    formatter = new DecimalFormat("#,###,###");
    s = formatter.format(-1234.567);         // -1,235
    s = formatter.format(-1234567.890);      // -1,234,568
    
    // The ; symbol is used to specify an alternate pattern for negative values
    formatter = new DecimalFormat("#;(#)");
    s = formatter.format(-1234.567);         // (1235)
    
    // The ' symbol is used to quote literal symbols
    formatter = new DecimalFormat("'#'#");
    s = formatter.format(-1234.567);         // -#1235
    formatter = new DecimalFormat("'abc'#");
    s = formatter.format(-1234.567);         // -abc1235

Thursday, 20 March 2008

Oracle ADF Two action listeners in one action

Some time ago, I run into a situation when I wanted to add two action listeners inside a single command element but I realized that this was just not possible. When I asked about that in the JDeveloper forums somebody told me that this could only be accomplished in code, but at that time my knowledge was just not enough

A couple of days ago I run into the same problem. I had a view page that when accessed wanted to know two things:

  • First was the ${row.rowKeyStr} of the row to display and ...
  • Next a session scope boolean parameter called #{sessionScope.returnHome} that indicated whether to return the user to the application home page or to another (the search in that case) page.

To summarize, I ended up with the a button called View record in two different pages both having a navigation case (also called view) linking to the view record page. in the application JSF diagram.

On both cases the expression #{row.rowKeyStr} had to be recorded in the JSF session scope along with a value for the returnHome flag that had to be true on one occasion and false on the other..

Following is the code I used to perform this. First the base class :

package myapp.view.backing;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

class backing_SRListBase {

    public backing_SRListBase()
    {
    }
    
    public String viewButtonBase_action( boolean returnHome)
    {
        // access the faces context
        FacesContext fc = FacesContext.getCurrentInstance();
        Application app = fc.getApplication();
        
        // on both case row.rowKeyStr points to the current row key
        ValueBinding vbActRowKey = app.createValueBinding("#{row.rowKeyStr}");
        String rowKey = (String )vbActRowKey.getValue(fc);
                
        // in that case we must save that in sessionScope
        ValueBinding vbSessionScopeRowKey = app.createValueBinding("#{sessionScope.rowKey}");
        vbSessionScopeRowKey.setValue( fc, rowKey);
                
        // also save the return home boolean value
        ValueBinding vbReturnHome = app.createValueBinding("#{sessionScope.returnHome}");
        vbReturnHome.setValue( fc, new Boolean( returnHome));
        
        return "view";
    }
}

Finally, the backing bean for each of the two pages looks like this

package myapp.view.backing;

public class backing_OpenSRList 
    extends backing_SRListBase {

    public backing_OpenSRList()
    {
    }

    public String viewButton_action()
    {
        return viewButtonBase_action( false);
    }
}

Friday, 11 January 2008

ABAP Get the value of a date type characteristic

When an object is classified with characteristics of type date then, using BAPI BAPI_OBJCL_GETCLASSES these characteristics are returned as numeric. That means that entries for date characteristics are found in the valuesnum array which --as you remeber from a couple of postings back -- is defined to be a standard table of bapi1003_alloc_values_num.

When I first saw this I thought that the actual date value could be acquired by assigning the value_from part of the returned bapi1003_alloc_values_num structure to a date variable, but it was not at all like this. The actual value is stored like an integer representing the ABAP internal date fromat which is YYYYMMDD. So an integer value like 20080112 represents January 12th 2008.

In ABAP code this translates as follows

  DATA :
    temp_int TYPE i,
    temp_date_str(8) TYPE c, 
    my_date TYPE d.

  FIELD-SYMBOLS :
     TYPE bapi1003_alloc_values_num.

      READ TABLE valuesnum
        WITH KEY charact = 'MY_DATE_CHARACT'
        ASSIGNING .

      IF sy-subrc = 0.
        temp_int = -value_from.
        temp_date_str = temp_int.
        my_date = temp_date_str.
      ENDIF.

Java: Dates/Times and Calendars

The problem that many new Java programmers -- me included -- face when they start dealing with dates and times is : Given a java.util.Date object get month, date and year fields in separate variables.

The usual misunderstanding here is that the class java.util.Date is almost deprecated and all things you expect to be able to perform with dates are done using the java.util.Calendar; class.

Calendar is an abstract class whose javadoc can be found here. The class provides get and set methods that allow you to alter any of the fields that constitute a point in time. The following example demonstrates one way to calculate the date corresponding to the start of the current term, using the Calendar get and set functions. It also shows how to convert a Date to a Calendar and vice versa.

    private void calStartOfCurrentTerm()
    {
        // create a new Gregorian Calendar
        Calendar c = new GregorianCalendar();
        // you can synchronize a Calendar with a date by using the
        // setTime() method
        c.setTime( new Date());
        
        // once you get a Calendar extracting field information is 
        // as easy as ....
        int month = c.get( Calendar.MONTH);
        int day = c.get(Calendar.DAY_OF_MONTH);
        int year = c.get(Calendar.YEAR);
        
        // let's calculate the date that corresponds to the 
        // start of the current term
        int newMonth;
        
        switch (month) {
        case Calendar.JANUARY:
        case Calendar.FEBRUARY:
        case Calendar.MARCH:
            newMonth = Calendar.JANUARY;
            break;
        case Calendar.APRIL:
        case Calendar.MAY:
        case Calendar.JUNE:
            newMonth = Calendar.JANUARY;
            break;
        case Calendar.JULY:
        case Calendar.AUGUST:
        case Calendar.SEPTEMBER:
            newMonth = Calendar.JULY;
            break;
        default:
            newMonth = Calendar.OCTOBER;
        }
        
        // adjust the calendar to point to the new date
        c.set(Calendar.MONTH, newMonth);
        c.set(Calendar.DAY_OF_MONTH, 1);
        
        // get the information to a new date variable
        Date startOfTerm = c.getTime();
        
        // display results
        System.out.println("Day is " + day +" month is " + month + " year is " + year);
        System.out.println( startOfTerm.toString());
    }

Here is a slightly different version that returns an object of type oracle.jbo.domain.Date

import java.sql.Timestamp;

import java.util.Calendar;
import java.util.GregorianCalendar;

import oracle.jbo.domain.Date;

  private Date getStartOfTerm()
  {
    // create a new Gregorian Calendar
    Calendar c = new GregorianCalendar();

    // once you get a Calendar extracting field information is 
    // as easy as ....
    int month = c.get(Calendar.MONTH);

    // let's calculate the date that corresponds to the 
    // start of the current term
    int newMonth;

    switch (month) {
      case Calendar.JANUARY:
      case Calendar.FEBRUARY:
      case Calendar.MARCH:
        newMonth = Calendar.JANUARY;
        break;
      case Calendar.APRIL:
      case Calendar.MAY:
      case Calendar.JUNE:
        newMonth = Calendar.JANUARY;
        break;
      case Calendar.JULY:
      case Calendar.AUGUST:
      case Calendar.SEPTEMBER:
        newMonth = Calendar.JULY;
        break;
      default:
        newMonth = Calendar.OCTOBER;
    }

    // adjust the calendar to point to the new date
    c.set(Calendar.MONTH, newMonth);
    c.set(Calendar.DAY_OF_MONTH, 1);
    c.set(Calendar.HOUR_OF_DAY, 0);
    c.set(Calendar.MINUTE, 0);
    c.set(Calendar.SECOND, 0);
    c.set(Calendar.MILLISECOND, 0);
    
    // get the information to a new date variable
    java.util.Date javaDate = c.getTime();
    Timestamp st = new Timestamp(javaDate.getTime());
    return new Date(st);
  }

PS:. Finally There is one last thing I have to get used to. "Copy as HTML" on JDeveloper 10g, does not work on openSUSE 10.2.

Thursday, 10 January 2008

ABAP Obtaining characteristics of a document from the classification system

In addition to a previous post regarding getting classification information for objects of type Material or Batch and thanks to additional info and feedback that I received from our MM specialist Ms Marilena Svolaki, I will be able to provide an example of how to do the same thing with documents.

Master information about documents is found in the DRAW table. (This is short for German Dokumentinformationssatz). The key fields for each document are displayed in the following image :

Creating the object key for BAPI_OBJCL_GETCLASSES is performed by concatenating the document type (field DRAW-DOKAR), the document number (field DRAW-DOKNR), the document version (field DRAW-DOKVR) and the document part (field DRAW-DOKTL). In code this can be expressed as follows

 DATA
    wa_draw TYPE draw, 
    cur_bapi_objectkey LIKE inob-objek,

    class_list TYPE STANDARD TABLE OF bapi1003_alloc_list,
    valueschar TYPE STANDARD TABLE OF bapi1003_alloc_values_char,
    valuescurr TYPE STANDARD TABLE OF bapi1003_alloc_values_curr,
    valuesnum  TYPE STANDARD TABLE OF bapi1003_alloc_values_num,
    return     TYPE STANDARD TABLE OF bapiret2.

*   Make wa_draw somehow correspond to a valid document entry ....     
    CONCATENATE wa_draw-dokar
                wa_draw-doknr
                wa_draw-dokvr
                wa_draw-doktl
                INTO cur_bapi_objectkey.

*   Call BAPI to get the list of characteristics
    CALL FUNCTION 'BAPI_OBJCL_GETCLASSES'
    EXPORTING
      objectkey_imp         = cur_bapi_objectkey
      objecttable_imp       = 'DRAW'
      classtype_imp         = '017'
      read_valuations       = 'X'
      keydate               = sy-datum
*     language              = 'E'
    TABLES
      alloclist             = class_list
      allocvalueschar       = valueschar
      allocvaluescurr       = valuescurr
      allocvaluesnum        = valuesnum
      return                = return.

    READ TABLE return INDEX 1 ASSIGNING <ret>.

*   Check that object allocations exist
    IF  sy-subrc = 0 AND
        <ret>-id = 'CL' AND
        <ret>-number = 741.

*      Read characteristic values
       ...
    ENDIF

Friday, 9 November 2007

ADF/JSF Getting the current row with code executed from a table bucking bean

The following code comes from the JDeveloper Forums courtesy of Frank Nimphus. If added to any event listener fired from "inside" an ADF Table, it prints the row's 1'st attribute and the rowKey.

        FacesContext fctx = FacesContext.getCurrentInstance();
        ValueBinding vb = (ValueBinding) fctx.getApplication().createValueBinding("#{row}");
        JUCtrlValueBindingRef rwbinding = (JUCtrlValueBindingRef) vb.getValue(fctx);
        System.out.println("--- Key as String --"+rwbinding.getRow().getKey().toStringFormat(true));
        System.out.println("--- First Attribute ---"+rwbinding.getRow().getAttribute(1));

Thanks again Frank