Category: "Dojo"

dojo 1.6.1 Form.reset()

Code to clear a form without firing the onChange events on the inputs.

 /* frmDijit.reset() */
                        dojo.query('#frmForm input[class^=dijit]').forEach(
                        function(item)
                        {
                                if ((d=dijit.byId(item.id))!=null)
                                        switch(d.type)
                                        {
                                        case 'checkbox':
                                        case 'radio':
                                                d.set('checked',false);
                                                break;
                                        case 'hidden':
                                        case 'text': 
                                                d.set('value','',false);
                                                break;
                                        }
                        });

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

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>

Zend Framework Admin Form extension of Zend_Dojo_Form

The objective of this class is to provide a foundation class for admin forms which provides the following:

  • Security - a hash input is used. It must return to the server with the same value sent to the client.
  • Multi-form pages - this class prefixes all inputs so it can be used to generate forms which coexist on the same page. validate, getValues and populate have an additional parameter to support this, indicating whether or not to use the prefix.
  • Greater transaction safety - the version number of the data is sent to the client, and tested later to ensure the data was not changed prior to saving. This test is performed in the Doctrine code, using a Record_Listener which tests the version submitted against the current record version and then increments it prior to save if valid, or throws an exception in the case of a mismatch.
  • ACL support - the form reviews the array of buttons set in the extension class to ensure the administrator has adequate privileges.

<?php
	// application/forms/Account.php
	class Admin_Form extends Zend_Dojo_Form
	{
		protected $_security = null;
		protected $_buttons = null;
		protected $_hash = null;
		protected $_prefix = '';

		public function init()
		{	
			$front = Zend_Controller_Front::getInstance();
			$this->setAction('/'.$front->getRequest()->getParam('module').'/'.$front->getRequest()->getParam('controller'));
			$security = array();
			$security['hash'] = new Zend_Form_Element_Hash('hash');
			$security['hash']->setOptions(array('salt'=>'unique'));
			$security['id'] = new Zend_Dojo_Form_Element_TextBox('id');
			$security['version'] = new Zend_Dojo_Form_Element_TextBox('version');
			$security_names = array();
			foreach ($security as $k => $v)
				$v->setRequired(true)
					->addValidator('Identical')
					->addFilter('StringTrim');
			$this->addElements($security);
			$this->_security = $security;
			$this->addDisplayGroup(array_keys($security),'hsh',array('order'=>0,'style'=>"display:none"));
			if ($this->_buttons != null) 
			{
				$acl_buttons=array();
				$buttons_ok=false;
				if (Zend_Auth::getInstance()->hasIdentity())
        		{
		            $identity = Zend_Auth::getInstance()->getIdentity();
        		    $role = $identity['role'];
				}
				else
					$role = 'none';
				$acl = Zend_Registry::get('Zend_Acl');
				$resource = 'mvc:'.$front->getRequest()->getParam('module').'.'.$front->getRequest()->getParam('controller');
				$buttons = $this->_buttons;
				foreach ($buttons as $k => $v)
					if ($acl->has($resource) && $acl->isAllowed($role,$resource,$k))
					{
						$this->addElement($v);
						$buttons_ok=true;
					}
				if ($buttons_ok) 
					$this->addDisplayGroup(array_keys($buttons),'buttons',array('order'=>100,'class'=>'buttons'));
			}
			parent::init();
		}

		protected function setPrefix($prefix)
		{
			$prefix .= '_';
			$this->_prefix = $prefix;
			$elements = $this->getElements();
			foreach ($elements as $k => $v)
			{
				$v->setName($prefix.$k);
				$this->prefixElement($k);
			}
            $display_groups = $this->getDisplayGroups();
            foreach ($display_groups as $k => $v)
				$v->setName($prefix.$k);		
			$this->_hash = $prefix.'hash';
			foreach ($this->_security as $k => $v)
			{
				$this->_security[$prefix.$k] = $v;
				unset($this->_security[$k]);
			}
			/* Avoids duplicate id in XHTML for hash */
			$fix = $this->getElement($prefix.'hash');
			$tag = $fix->getDecorator('HtmlTag');
			$tag->setOption('id','dd_'.$prefix.'hash');
			foreach ($this->_buttons as $k => $v)
			{
				$this->_buttons[$prefix.$k] = $prefix.$k;
				unset($this->_buttons[$k]);
			}
		}

		public function getElement($name)
		{
			$element = parent::getElement($this->_prefix.$name);
			if ($element != null)
				return $element;
			/* Fallback */
			$element = parent::getElement($name);
			if ($element != null)
				return $element;
			return null;
		}

		public function populate($values,$use_prefix = false)
		{
			self::_prefixProcessor($values,$use_prefix);
			parent::populate($values);
		}

		private function _prefixProcessor(&$data,$use_prefix = false)
		{
            $prefixed = false;
            if (is_array($data) && (count($data)>=1))
            {
                reset($data); $key = key($data);
                $prefixed = (strpos($key,$this->_prefix) === 0);
            	if ($use_prefix)
				{
					if (!$prefixed)
		            {
						$new_data = array();
        	    	    foreach ($data as $k => $v)
            	    	    $new_data[$this->_prefix.$k] = $v;
						$data = array();
						$data = $new_data;
	    	        }
				}
				else
					if ($prefixed)
					{
						$prefixLength = strlen($this->_prefix);
                        $new_data = array();
                        foreach ($data as $k => $v)
                            $new_data[substr($k,$prefixLength)] = $v;
                        $data = array();
                        $data = $new_data;
					}
			}
		}

		public function setTokens()
		{
            if (($this->_hash == null) || ($this->_security == null)) return;

            $session_security=$this->getElement($this->_hash)->getSession();
            $security = $this->_security;
            foreach ($security as $k => $v)
				$session_security->$k = $this->getElement($k)->getValue();
		}

		public function getTokens()
		{
			if (($this->_hash == null) || ($this->_security == null)) return;

            $session_security=$this->getElement($this->_hash)->getSession();
			$security = $this->_security;
			foreach ($security as $k => $v)
			{
				$validator = $this->getElement($k)->getValidator('Identical');
				if (isset($session_security->$k))
					$validator->setToken($session_security->$k);
				else
					$validator->setToken('');
			}
		}

		public function getValues($data = null,$prefixed = false)
		{
			$return = parent::getValues();

			if ($this->_buttons != null)
				$return = array_diff ($return,$this->_buttons);

			self::_prefixProcessor($return,$prefixed);

			return $return;
		}

		private function prefixElement($name)
	    {
    	    $name = (string) $name;
        	if (isset($this->_elements[$name])) {
				$this->_elements[$name]->setName($this->_prefix.$name);
				$this->_elements[$this->_prefix.$name]=$this->_elements[$name];
	            unset($this->_elements[$name]);
    	        if (array_key_exists($name, $this->_order)) {
					$this->_order[$this->_prefix.$name]=$this->_order[$name];
        	        unset($this->_order[$name]);
            	    $this->_orderUpdated = true;
	            } else {
    	            foreach ($this->_displayGroups as $group) {
        	            if (null !== $group->getElement($name)) {
							$group->addElement($this->getElement($this->_prefix.$name));
            	            $group->removeElement($name);
	                    }
    	            }
        	    }
	            return true;
    	    }

        	return false;
	    }

	}

Doctrine code to test and update the version number. This is added in to the model for the data with addListener.

<?php
class Record_Listener extends Doctrine_Record_Listener
{
    public function preUpdate(Doctrine_Event $event) 
	{
		$invoker = $event->getInvoker();
		if (array_key_exists('version',$invoker->getModified())) 
		{
			$invoker->getErrorStack()->add('version','match');
			throw new Doctrine_EventListener_Exception('Version not identical');
		}
		/* Prevents modification of uuid */
		unset($invoker->uuid);
		$invoker->version++;
	}

	public function preDelete(Doctrine_Event $event)
	{
		$invoker = $event->getInvoker();
		if (array_key_exists('version',$invoker->getModified()))
		{
			$invoker->getErrorStack()->add('version','match');
			throw new Doctrine_EventListener_Exception('Version not identical');
		}
	}
}

This an the extension of the Admin_Form. It sets the name, configuratino from the user.ini file, buttons array, and the prefix.

<?php
	// application/forms/Account.php
	class Admin_User_View_Form extends Admin_Form
	{
		public function init()
		{
			$this->setName('frmUserView');
			$user = new Zend_Config_Ini(APPLICATION_PATH."/configs/admin/user.ini",'production',true);
			$this->setConfig($user);
			$this->_buttons = array();
			$this->_buttons['save'] = new Zend_Dojo_Form_Element_Button('save');
			$this->_buttons['save']->setOptions(array(
				'name' => 'save',  
				'label' => 'Save',
				'value' => 'save',
				'onclick' => 'user_save()'));
			parent::init();
			$this->setPrefix('user');
		}
	}

Sample view implementation. $user is a Doctrine object with the data. This is an AJAX form, so the json helper is used. Note the call to setTokens, which sets the hash value and saves the security elements in a session variable for later validation.


			$data = $user->getData();
			$form = new Admin_User_View_Form();
			$form->populate($data);
			$hash = $form->getElement('hash')->getHash();
			$form->setTokens();
			$values = $form->getValues(null,true);
			unset($values['user_password']);
            foreach ($values as $k => $v)
                $values[$k] = $this->view->escape($v);
			$this->_helper->json($values);

Sample save implementation, from the same controller. Note the getTokens call which retrieves the saved session data for the form.


			$form = new Admin_User_View_Form();
			$form->getTokens();
			$data = $this->getRequest()->getPost();
                        if ($form->isValidPartial($data))
                        {
                            $form->populate($data,true);
                            $user->fromArray($form->getValues($data,false));
                        }
			else
				$this->_helper->json(array('form'=>$form->processAjax($data)));

Supporting javascript is omitted, because it’s really application specific. One note is that the hash input isn’t a dojo/dijit input, so it must be handled manually with dojo.byId, instead of auto loaded with the form values.

This has been updated to work with Zend Framework 1.11.