dojo 1.1.1 to 1.6.1 Declarative Tag Update Script

This script performs the bulk of the upgrade translation to translate declarative tags from older versions of dojo up to the 1.6 format.

The script was written to work with tags in Smarty templates, so it includes a little extra logic to avoid confusion with Smarty syntax within the tags. If you’re not using Smarty, the script should still run fine, but you will need to change {ldelim} to { and {rdelim} to }.

Example of a simple translation performed by the tool.

Original

<label for="iDisplayMaxOffset">{#max_offset#}</label>
<input type="text" class="medium"
  name="iDisplayMaxOffset" id="iDisplayMaxOffset"
  dojoType="dijit.form.ValidationTextBox"
  readonly="true"
  value="{$iMaxOffset+1}" />

<label for="iDisplayMaxOffset">{#max_offset#}</label>
<input data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="'type':'text','class':'medium','name':'iDisplayMaxOffset','id':'iDisp
layMaxOffset','readonly':'true','value':'{$iMaxOffset+1}'">

Note that the attribute names are enclosed in single quotes. If there is a class attribute, for a custom CSS override, Internet Explorer (version 7, possibly others) requires the class attribute name to be quoted, or it may cause errors.

If a tag is too complex for this tool, it will output text recommending manual review, as well as the original tag, so you can fix any errors. In the translation below, the tool removed some of the message text.


{**** MANUAL REVIEW RECOMMENDED ****}
{**** 
<input type="text"
  name="sText" id="sText"
  regExp="{$sExtLngRegExp}"
  dojoType="dijit.form.ValidationTextBox"
  promptMessage="{#C_enter#} {#text#}"
  invalidMessage="{#C_invalid#} {#text#}"
  errorMessage="{#C_invalid#} {#text#} {#C_refer_to_documentation#}"
  value="{$sText}"
  onChange="setDataChanged()" />
****}
<input data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="'type':'text','name':'sText','id':'sText','regExp':'{$sExtLngRegExp}','promptMe
ssage':'{#C_enter#}','invalidMessage':'{#C_invalid#}','errorMessage':'{#C_invalid#}','value':'{$sText}','onChange':function(e){ldelim}setDataChanged(){rdelim}">

This is a command line script, run it with php dojo-tag.php old.file.html

<?php

$aEvents=array
('onblur'=>'onBlur',
'onchange'=>'onChange',
'onclick'=>'onClick',
'onclose'=>'onClose',
'ondblclick'=>'onDblClick',
'onfocus'=>'onFocus',
'onkeydown'=>'onKeyDown',
'onkeypress'=>'onKeyPress',
'onmousedown'=>'onMouseDown',
'onmouseenter'=>'onMouseEnter',
'onmouseleave'=>'onMouseLeave',
'onmousemove'=>'onMouseMove',
'onmouseout'=>'onMouseOut',
'onmouseover'=>'onMouseOver',
'onmouseup'=>'onMouseUp');

$aDeprecated=array('dijit.layout.AccordionPane'=>'dijit.layout.ContentPane');

$aDojoRequires=array();

$sFileText=file_get_contents($argv[1]);

$sNewText=preg_replace_callback('/(^\s*dojo\.require\s*\([\'"]([^\'^"]*)[\'"]\);[\r\n\s]*$)/m','requires',$sFileText);

$sUpdatedText=preg_replace_callback('/(<)(\w+) +([^>]*)(\/?>)/im','tags',$sNewText);

echo $sUpdatedText.PHP_EOL;

file_put_contents('/tmp/NEW',$sUpdatedText);

function requires($aMatches)
{
        global $aDeprecated,$aDojoRequires;
        $sDijit=trim($aMatches[2]);
        if (array_key_exists($sDijit,$aDeprecated))
                $sDijit=$aDeprecated[$sDijit];
        if (!in_array($sDijit,$aDojoRequires))
        {
                $aDojoRequires[]=$sDijit;
                return 'dojo.require("'.$sDijit.'");';
        }
        return '';
}

function tags($aMatches)
{
        global $aDeprecated;
        $sText=$aMatches[0];
        if (strpos($sText,'dojoType')!==false)
        {
                $sDataDojoType='';$aDojoText=array();$sDojoText='data-dojo-props=';$bFlagForReview=false;
                $sDataDojoId='';
                $aText=array();

                $sAttributeText=$aMatches[3];
                if (($iLeft=strpos($sAttributeText,'{'))!==false) 
                {
                        $iRight=strpos($sAttributeText,'}');
                        $iCountLeft=substr_count($sAttributeText,'{');
                        $iCountRight=substr_count($sAttributeText,'}');
                        if (($iLeft>$iRight) || ($iCountLeft>1) || ($iCountRight>1))
                                $bFlagForReview=true;
                }

                preg_match_all('/([a-zA-Z]+=[^>\s]+)/m',$sAttributeText,$aText);
                foreach ($aText[0] as $k => $v)
                {
                        if (stripos($v,'dojoType=')!==false)
                        {
                                $sDataDojoType=substr(str_replace(array('\'','"'),'',trim($v)),9);
                                $bDeprecated=array_key_exists($sDataDojoType,$aDeprecated);
                                if ($bDeprecated!==false)
                                {
                                        $bFlagForReview=true;
                                        $sDataDojoType=$aDeprecated[$sDataDojoType];
                                }
                                $sDataDojoType='"'.$sDataDojoType.'"';
                        }
                        else
                                if (stripos($v,'jsId=')!==false)
                                        $sDataDojoId='data-dojo-id='.substr($v,5);
                                else
                                        $aDojoText[]=pair($v,$bFlagForReview);
                }
                if (count($aDojoText)>0)
                        $sDojoText.='"'.implode(',',$aDojoText).'"';
                $sFlag=($bFlagForReview)?'{**** MANUAL REVIEW RECOMMENDED ****}'.PHP_EOL.
                        '{**** '.PHP_EOL.$sText.PHP_EOL.'****}'.PHP_EOL:'';
                $sClose=(strpos($sText,'/>')===false)?'>':' />';
                return $sFlag.$aMatches[1].$aMatches[2].' data-dojo-type='.$sDataDojoType.' '.$sDojoText.$sClose;
        }
        else
                return $sText;
}


function pair($value,&$bFlagForReview)
{
        global $aEvents;
        $iEqual=strpos($value,'=');
        $sProperty=substr($value,0,$iEqual);
        $sValue=substr($value,$iEqual+1);
        $sProperty=str_replace(array('\'','"'),'',trim($sProperty));
        $sValue=str_replace(array('\'','"'),'',trim($sValue));
        $bEvent=array_key_exists($sPropertyIndex=strtolower($sProperty),$aEvents);
        if ($bEvent===false)
                        $sRetVal='\''.$sProperty.'\':\''.$sValue.'\'';
        else
        {
                $bFlagForReview=true;
                $sRetVal='\''.$aEvents[$sPropertyIndex].'\':function(e){ldelim}'.$sValue.'{rdelim}';
        }

        return $sRetVal;
}

This is a simple tool, it is intended to be run on one file at a time, with the output reviewed carefully.

This script will upgrade most of the javascript, be sure to check the code carefully after it runs.

#!/bin/bash

# dojo 1.1.1 to dojo 1.6.1 javascript upgrade script

if [ $# -lt 1 ]
then
        echo 'Usage: dojo-js.sh <filename>'
        exit;
fi

NEW='/tmp/NEW'
cp $1 $NEW

sed --in-place "/attr([^\,^)]*)/s/attr/get/g" $NEW

# Must run AFTER the line above
sed --in-place "s/attr/set/g" $NEW
sed --in-place "s/\(\.set('value',[^\)]*\))/\1,false)/g" $NEW

sed --in-place "s/\.setValue\ *( *\([^\)]* *\))/.set('value',\1,false)/g" $NEW

sed --in-place "s/\.getValue\ *( *\([^\)]* *)\)/.get('value')/g" $NEW

sed --in-place "s/json-comment-filtered/json/" $NEW

sed --in-place "s/getDescendants\(.*\)$/getChildren\1 \/\/**** MANUAL REVIEW RECOMMENDED/" $NEW

sed --in-place "s/setHref(/set('href',/g" $NEW

sed --in-place "s/setAttribute/set/g" $NEW

sed --in-place "s/\.checked\s*\=\s*\(true\|false\)/.set('checked',\1)/g" $NEW
sed --in-place "s/\.checked/.get('checked')/g" $NEW

sed --in-place "s/getDisplayedValue()/get('displayedValue')/g" $NEW

sed --in-place "s/setLabel(/set('label',/g" $NEW

*** Make a backup before using these tools ***

dojo - Distinguishing between onChange events

During an upgrade from dojo 1.1.1 to 1.6.1, I found that dijit.byId('someWidget').set('value','someNewValue'), was causing the onChange event to fire for the input.

To prevent onChange from firing when the .set method is used, a third parameter priorityChange is used. Setting priorityChange to false prevents the onChange event from firing.

The updated code is: dijit.byId('someWidget').set('value','someNewValue',false)

A sed command to update the code. In this case the setValue or attr(’value’) calls had already been translated to set:

sed --in-place "s/\.set\ *([\"|']value[\"|']\,\ *\([^\)]*\))/.set('value',\1,false)/g" js/code.js

Linux - Using file -i instead of the input accept attribute

The file input allows an accept attribute to indicate what type of file may be submitted. The type is the client’s MIME type, which may vary by operating system, installed applications, and end user configuration.

A sample set of MIME types used for an accept attribute is:

accept="application/csv,text/csv,text/text,application/vnd.ms-excel,text/plain,application/octet-stream”

The browser usually doesn’t enforce the accept attribute.

The MIME type sent from the client is unreliable, since many clients use the file extension to indicate the MIME type for the browser, and that MIME type is sent to the server.

An alternative is to ignore the MIME type, but use the Linux file command to test the file, and use it for validation.

In the example below, there are three identical files of raw audio, with the extension of pdf, raw, and txt. Linux uses the file content to determine the type, rather than the extensions.

[tmp]$ file -i audio.*
audio.pdf: application/octet-stream
audio.raw: application/octet-stream
audio.txt: application/octet-stream

dojo 1.6.1 dijit.form.Button example

I’ve been upgrading an RIA from dojo 1.1.1 to dojo 1.6.1, and spent a lot of time trying to find the cause of this error message:

t.apply is not a function

After many hours, I found it was related to the way the data-dojo-props event handlers were coded.

In the example below, onClick is specified as a function which calls another function.

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.6/dijit/themes/claro/claro.css" />
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.6.1/dojo/dojo.xd.js" type="text/javascript"
data-dojo-config="isDebug:true, parseOnLoad: true"></script>
<script type="text/javascript">
function go(s)
{
alert('go('+s+')');
}
</script>
<script type="text/javascript">
dojo.require('dojo.parser');
dojo.require("dijit.form.Form");
dojo.require("dijit.form.Button");
</script>
</head>
<body class="claro">
<h1>dojo dijit Button test</h1>
<button data-dojo-type="dijit.form.Button" 
        data-dojo-props="label:'Button',onClick:function(){go('4')}"></button>
</body>
</html>

PHP <-> Perl Bridge using cURL

Using cURL to submit requests from PHP into Perl allows graceful reuse of code with a system with a robust core of Perl code and a PHP web interface.

The Perl code accepts a module name, sub/function name, and a set of parameters.

It includes the module, then tests for the sub. Then assembles the parameters into a request and calls the Perl code. The code returned may be name value pairs, or PHP, for more complex responses.

This code has been edited for brevity, it is an example, not a ready to use solution.

#!/usr/bin/perl

use strict;
use CGI qw(-compile :cgi);

my $q=CGI->new;

print $q->header('text/plain');

my $m=$q->param('module');
my $s=$q->param('sub');
$m=~s/\W//g;

my $path="$ENV{'APPHOME'}/bin/".$m.".pm";

require $path;

my $ms=$m.'::'.$s;

if (Test($ms))
{
        my $x='bridge_'.$m.'("'.$s.'",\%{$q->Vars})';
        eval $x;
        exit 0;
}
else
{
        exit 101;
}
sub Test
{
        my $sub_name=$_[0];
        return ( defined &{ $sub_name } );
}

sub bridge_PERL_CODE
{
        my $r=0;
        my $sub=$_[0];
        my (%parms)=%{$_[1]};

        delete $parms{'module'};
        delete $parms{'sub'};

        SWITCH: for ($sub)
        {
                /^getSystemServices$/ && do
                {
                        my %services;

                        my $status=PERL_CODE::getSystemServices(\%services);

                        # Sample code to generate PHP
                        my ($id,$k,$kk,$s,$t);
                        print '<?php',"\n";
                        $s='$services=array('."\n";
                        for my $k ( keys %services ) 
                        {
                                $id=$services{$k}{'id'};
                                delete $services{$k}{'id'};
                                $s.='\''.$id.'\'=>array('."\n";
                                for $kk ( keys %{$services{$k}} )
                                {
                                        $t=$services{$k}{$kk};
                                        $t =~ s/(['"])/\\$1/g;
                                        $s.="\t".'\''.$kk.'\'=>\''.$t.'\',';
                                }
                                $s=substr $s,0,-1;
                                $s.='),'."\n";
                        }
                        $s=substr $s,0,-2;
                        $s.=');';

                        print $s,"\n";

                        print '?>';

                        $r=$status;

                        last SWITCH;
                };
                /^getServiceParms$/ && do
                {
                        my $serviceId=$parms{'serviceId'};
                        my %parmValues;

                        my ($status,$msg)=PERL_CODE::getServiceParms($serviceId, \%paramValues);

                        # Sample code to generate name/value pairs
                        my ($k,$v);
                        while (($k, $v) = each(%parmValues))
                        {
                             print $k.'='.$v,"\n";
                        }
                        print 'status='.$status,"\n";
                        print 'msg='.$msg,"\n";

                        $r=$status;

                        last SWITCH;
                };
                #DEFAULT
                {
                        # Error - Should never get here
                        $r=-1;
                        last SWITCH;
                };
        }
        return $r;
}

<?php
function curl_request($sPost=null,$bHandleAsPHP=false)
{
        $cURL=curl_init();
        curl_setopt($cURL,CURLOPT_URL,"http://localhost/cgi-bin/bridge.pl");
        curl_setopt($cURL,CURLOPT_POST, TRUE);
        curl_setopt($cURL,CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($cURL,CURLOPT_POSTFIELDS,$sPost);
        $cResponse=trim(curl_exec($cURL));
        curl_close($cURL);
        return handle_response($cResponse,$bHandleAsPHP);
}

function handle_response($sResponse=null,$bHandleAsPHP=false)
{
        if (!$bHandleAsPHP)
        {
                $aResponseLines=$aResult=array();
                $aResponseLines=explode("\n",$sResponse);
                if (count($aResponseLines)>0)
                        foreach ($aResponseLines as $k => $v)
                        {
                                $e=strpos($v,'=');
                                if ($e!==false)
                                        $aResult[substr($v,0,$e)]=substr($v,$e+1);
                        }
        }
        else
        {
                $sResponse=str_replace(array('<?php','?>'),'',$sResponse);
                $sResponse.="\n".'$vars=get_defined_vars();';
                eval($sResponse);
                unset($vars['sResponse'],$vars['bHandleAsPHP']);
                $aResult=$vars;
        }
        return $aResult;
}

/* Call across the bridge, expecting PHP in response */
function getSystemServices()
{
        return curl_request('module=PERL_CODE&sub=getSystemServices',true);
}