[prev in list] [next in list] [prev in thread] [next in thread] 

List:       pear-cvs
Subject:    [PEAR-CVS] =?utf-8?q?svn:_/pear/packages/HTML=5FQuickForm2/trunk/_HTML/QuickForm2/JavascriptBuilder.
From:       Alexey_Borzov <avb () php ! net>
Date:       2011-04-26 18:42:03
Message-ID: svn-avb-1303843323-310525-1885081056 () svn ! php ! net
[Download RAW message or body]

avb                                      Tue, 26 Apr 2011 18:42:03 +0000

Revision: http://svn.php.net/viewvc?view=revision&revision=310525

Log:
Added objects representing client-side rules, this makes generated validation code less verbose
Replaced old JS libraries by ones generated by new build script
Updated JS in dualselect example, got rid of conditional comments
"Live" rules can now be disabled as was intended

Changed paths:
    U   pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/JavascriptBuilder.php
    U   pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Node.php
    U   pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Rule.php
    A   pear/packages/HTML_QuickForm2/trunk/data/js/
    A   pear/packages/HTML_QuickForm2/trunk/data/js/min/
    A   pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform-hierselect.js
    A   pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform.js
    A   pear/packages/HTML_QuickForm2/trunk/data/js/quickform-hierselect.js
    A   pear/packages/HTML_QuickForm2/trunk/data/js/quickform.js
    D   pear/packages/HTML_QuickForm2/trunk/data/quickform-hierselect.js
    D   pear/packages/HTML_QuickForm2/trunk/data/quickform.js
    U   pear/packages/HTML_QuickForm2/trunk/docs/examples/js/dualselect.js
    U   pear/packages/HTML_QuickForm2/trunk/js/src/validator.js
    U   pear/packages/HTML_QuickForm2/trunk/package.xml
    U   pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/CompareTest.php
    U   pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/EachTest.php
    U   pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/NonemptyTest.php
    U   pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/RuleTest.php


["svn-diffs-310525.txt" (text/x-diff)]

Modified: pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/JavascriptBuilder.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/JavascriptBuilder.php	2011-04-26 \
                16:49:39 UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/JavascriptBuilder.php	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -111,11 +111,13 @@
         $this->defaultWebPath = $defaultWebPath;

         if (null === $defaultAbsPath) {
-            $defaultAbsPath = '@data_dir@' . DIRECTORY_SEPARATOR . 'HTML_QuickForm2' \
. DIRECTORY_SEPARATOR; +            $defaultAbsPath = '@data_dir@' . \
DIRECTORY_SEPARATOR . 'HTML_QuickForm2' +                              . \
DIRECTORY_SEPARATOR . 'js' . DIRECTORY_SEPARATOR;  // package was probably not \
installed, use relative path  if (0 === strpos($defaultAbsPath, '@' . 'data_dir@')) {
-                $defaultAbsPath = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . \
                '..'
-                                           . DIRECTORY_SEPARATOR . '..' . \
DIRECTORY_SEPARATOR . 'data') +                $defaultAbsPath = \
realpath(dirname(dirname(dirname(__FILE__))) +                                        \
. DIRECTORY_SEPARATOR . 'data' +                                           . \
                DIRECTORY_SEPARATOR . 'js')
                                   . DIRECTORY_SEPARATOR;
             }
         }

Modified: pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Node.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Node.php	2011-04-26 16:49:39 \
                UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Node.php	2011-04-26 18:42:03 \
UTC (rev 310525) @@ -607,7 +607,7 @@
         }
         foreach ($this->rules as $rule) {
             if ($rule[1] & HTML_QuickForm2_Rule::CLIENT) {
-                $builder->addRule($rule[0], $rule[1] & \
HTML_QuickForm2_Rule::ONBLUR_CLIENT); +                $builder->addRule($rule[0], \
$rule[1] & (HTML_QuickForm2_Rule::ONBLUR_CLIENT ^ HTML_QuickForm2_Rule::CLIENT));  }
         }
     }

Modified: pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Rule.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Rule.php	2011-04-26 16:49:39 \
                UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/HTML/QuickForm2/Rule.php	2011-04-26 18:42:03 \
UTC (rev 310525) @@ -372,9 +372,13 @@
     {
         HTML_QuickForm2_Loader::loadClass('HTML_QuickForm2_JavascriptBuilder');

-        $js = "{\n\tcallback: " . $this->getJavascriptCallback() . ",\n" .
-              "\towner: '" . $this->owner->getId() . "',\n" .
-              "\tmessage: " . \
HTML_QuickForm2_JavascriptBuilder::encode($this->getMessage()); +        $js = \
$this->getJavascriptCallback() . ",\n\t'" . $this->owner->getId() +              . \
"', " . HTML_QuickForm2_JavascriptBuilder::encode($this->getMessage()); +
+        $js = $outputTriggers && count($triggers = $this->getJavascriptTriggers())
+              ? 'new qf.LiveRule(' . $js . ', ' . \
HTML_QuickForm2_JavascriptBuilder::encode($triggers) +              : 'new qf.Rule(' \
. $js; +
         if (count($this->chainedRules) > 1 || count($this->chainedRules[0]) > 0) {
             $chained = array();
             foreach ($this->chainedRules as $item) {
@@ -384,12 +388,9 @@
                 }
                 $chained[] = '[' . implode(",\n", $multipliers) . ']';
             }
-            $js .= ",\n\tchained: [" . implode(",\n", $chained) . "]";
+            $js .= ",\n\t [" . implode(",\n", $chained) . "]";
         }
-        $triggersStr = $outputTriggers && count($triggers = \
                $this->getJavascriptTriggers())
-                       ? ",\n\ttriggers: " . \
                HTML_QuickForm2_JavascriptBuilder::encode($triggers)
-                       : '';
-        return $js . $triggersStr . "\n}";
+        return $js . ')';
     }
 }
 ?>

Added: pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform-hierselect.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform-hierselect.js	          \
                (rev 0)
+++ pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform-hierselect.js	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -0,0 +1,15 @@
+/*
+ HTML_QuickForm2: support functions for hierselect elements
+ Package version @package_version@
+ http://pear.php.net/package/HTML_QuickForm2
+
+ Copyright 2006-2011, Alexey Borzov, Bertrand Mansion
+ Licensed under new BSD license
+ http://opensource.org/licenses/bsd-license.php
+*/
+qf.elements.hierselect=function(){function g(b){return \
function(){setTimeout(function(){if(b.id in qf.elements.hierselect.defaults)for(var \
c=qf.elements.hierselect.defaults[b.id],d=b.hierselect.next,a=0;a<d.length;a++)qf.elem \
ents.hierselect.replaceOptions(document.getElementById(d[a]),qf.elements.hierselect.getOptions(b.id,c.slice(0,a+1))),qf.form.setValue(d[a],c[a+1])},1)}}function \
h(b){return function(){if(b.id in qf.elements.hierselect.defaults){var \
c=qf.elements.hierselect.defaults[b.id],d=b.hierselect.next; \
+qf.form.setValue(b,c[0]);for(var \
a=0;a<d.length;a++)qf.form.setValue(d[a],c[a+1])}}}function \
i(b){b=qf.events.fixEvent(b);b.target.hierselect&&0!=b.target.hierselect.next.length&&qf.elements.hierselect.cascade.call(b.target)}return{init:function(b,c){for(var \
d=[],a=document.getElementById(b[0]),e;b.length&&(e=b.shift());){d.push(e);var \
f=document.getElementById(e);f.hierselect={previous:d.concat(),next:b.concat(),callback:c};qf.events.addListener(f,"change",i)}qf.events.addListener(a.form,"reset",
 +g(a));qf.events.addListener(window,"load",h(a))},getValue:function(b){for(var \
c=[],d=0;d<b.length;d++)c.push(qf.form.getValue(b[d]));return \
c},replaceOptions:function(b,c){function d(b){var \
a=document.createElement("div");a.innerHTML=b;return \
a.childNodes[0]?a.childNodes[0].nodeValue:""}for(var \
a=b.options.length=0;a<c.values.length;a++)b.options[a]=new \
Option(-1==String(c.texts[a]).indexOf("&")?c.texts[a]:d(c.texts[a]),c.values[a],!1,!1)},getOptions:function(b,c,d){if(!(b \
in qf.elements.hierselect.options)|| +typeof \
qf.elements.hierselect.options[b][c.length-1]=="undefined")return \
qf.elements.hierselect.missingOptions;for(var \
a=qf.elements.hierselect.options[b][c.length-1],e=c.concat();e.length;){var \
f=e.shift();if(0==e.length)return f in \
a||(a[f]=d?d(c,b):qf.elements.hierselect.missingOptions),a[f];else f in \
a||(a[f]={});a=a[f]}},getAsyncCallback:function(b,c){return function(d){b in \
qf.elements.hierselect.options||(qf.elements.hierselect.options[b]=[]);typeof \
qf.elements.hierselect.options[b][c.length- \
+1]=="undefined"&&(qf.elements.hierselect.options[b][c.length-1]={});for(var \
a=qf.elements.hierselect.options[b][c.length-1],e=c.concat();e.length;){var \
f=e.shift();0==e.length?a[f]=d:a=f in \
a?a[f]:a[f]={}}a=document.getElementById(b).hierselect;e=document.getElementById(a.nex \
t[c.length-1]);qf.elements.hierselect.replaceOptions(e,d);c.length<a.next.length&&qf.elements.hierselect.cascade.call(e)}},cascade:function(){var \
b=qf.elements.hierselect.getValue(this.hierselect.previous);qf.elements.hierselect.replaceOptions(document.getElementById(this.hierselect.next[0]),
 +qf.elements.hierselect.getOptions(this.hierselect.previous[0],b,this.hierselect.call \
back));1<this.hierselect.next.length&&qf.elements.hierselect.cascade.call(document.getElementById(this.hierselect.next[0]))},missingOptions:{values:[""],texts:[" \
"]},options:{},defaults:{}}}();


Property changes on: \
pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform-hierselect.js \
                ___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Added: pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform.js	                     \
                (rev 0)
+++ pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform.js	2011-04-26 18:42:03 \
UTC (rev 310525) @@ -0,0 +1,30 @@
+/*
+ HTML_QuickForm2 client-side validation library
+ Package version @package_version@
+ http://pear.php.net/package/HTML_QuickForm2
+
+ Copyright 2006-2011, Alexey Borzov, Bertrand Mansion
+ Licensed under new BSD license
+ http://opensource.org/licenses/bsd-license.php
+*/
+var qf=qf||{};qf.elements=qf.elements||{};
+qf.typeOf=function(a){var b=typeof a;if("function"==b&&"undefined"==typeof \
a.call)return"object";else if("object"==b)if(a){if(a instanceof Array||!(a instanceof \
Object)&&"[object Array]"==Object.prototype.toString.call(a)&&"number"==typeof \
a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof \
a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if(!(a \
instanceof Object)&&("[object \
Function]"==Object.prototype.toString.call(a)||"undefined"!=typeof \
a.call&&"undefined"!=typeof a.propertyIsEnumerable&& \
+!a.propertyIsEnumerable("call")))return"function"}else return"null";return \
b};qf.addNamespace=function(a){for(var \
a=a.split("."),b=window,c;a.length&&(c=a.shift());)b=b[c]?b[c]:b[c]={}};qf.Map=function(a){this._map={};this._keys=[];this._count=0;a&&this.merge(a)};
 +qf.Map.prototype=function(){function a(a,d){return \
Object.prototype.hasOwnProperty.call(a,d)}function \
b(){if(this._count!=this._keys.length){for(var b=0,d=0,e={};b<this._keys.length;){var \
g=this._keys[b];a(this._map,g)&&!a(e,g)&&(this._keys[d++]=g,e[g]=!0);b++}this._keys.length=d}}return{hasKey:function(b){return \
a(this._map,b)},length:function(){return \
this._count},getValues:function(){b.call(this);for(var \
a=[],d=0;d<this._keys.length;d++)a.push(this._map[this._keys[d]]);return \
a},getKeys:function(){b.call(this); +return \
this._keys.concat()},isEmpty:function(){return \
0==this._count},clear:function(){this._map={};this._count=this._keys.length=0},remove:function(c){if(!a(this._map,c))return!1;delete \
this._map[c];this._count--;this._keys.length>this._count*2&&b.call(this);return!0},get:function(b,d){if(a(this._map,b))return \
this._map[b];return d},set:function(b,d){a(this._map,b)||(this._count++,this._keys.push(b));this._map[b]=d},merge:function(a,b){var \
e,g,f=0;if(a instanceof qf.Map)e=a.getKeys(),g=a.getValues(); +else for(var h in \
e=[],g=[],a)e[f]=h,g[f++]=a[h];h=b||qf.Map.mergeReplace;for(f=0;f<e.length;f++)this.ha \
sKey(e[f])?this.set(e[f],h(this.get(e[f]),g[f])):this.set(e[f],g[f])}}}();qf.Map.mergeReplace=function(a,b){return \
b};qf.Map.mergeKeep=function(a){return \
a};qf.Map.mergeArrayConcat=function(a,b){"array"!=qf.typeOf(a)&&(a=[a]);"array"!=qf.typeOf(b)&&(b=[b]);return \
a.concat(b)};qf.form=function(){return{getValue:function(a){typeof \
a=="string"&&(a=document.getElementById(a));if(!a||!("type"in a))return \
null;switch(a.type.toLowerCase()){case "checkbox":case "radio":return \
a.checked?a.value:null;case "select-one":var \
b=a.selectedIndex;return-1==b?null:a.options[b].value;case "select-multiple":for(var \
b=[],c=0;c<a.options.length;c++)a.options[c].selected&&b.push(a.options[c].value);return \
b;default:return typeof \
a.value=="undefined"?null:a.value}},getSubmitValue:function(a){typeof a== \
+"string"&&(a=document.getElementById(a));if(!a||!1 in a||a.disabled)return \
null;switch(a.type.toLowerCase()){case "reset":case "button":return \
null;default:return qf.form.getValue(a)}},getContainerSubmitValue:function(){for(var \
a=new qf.Map,b=0;b<arguments.length;b++)if(arguments[b]instanceof \
qf.Map)a.merge(arguments[b],qf.Map.mergeArrayConcat);else{if("object"==qf.typeOf(arguments[b]))var \
c=arguments[b].name,d=arguments[b].value;else \
c=document.getElementById(arguments[b]).name,d=qf.form.getSubmitValue(arguments[b]); \
+if(null!==d){var e={};e[c]=d;a.merge(e,qf.Map.mergeArrayConcat)}}return \
a},setValue:function(a,b){typeof \
a=="string"&&(a=document.getElementById(a));if(a&&"type"in \
a)switch(a.type.toLowerCase()){case "checkbox":case "radio":a.checked=!!b;break;case \
"select-one":var c=a;c.selectedIndex=-1;for(var \
d,e=0;d=c.options[e];e++)if(d.value==b){d.selected=!0;break}break;case \
"select-multiple":c=a;d=b;"array"!=qf.typeOf(d)&&(d=[d]);for(var \
g=0;e=c.options[g];g++){e.selected=!1;for(var f=0,h=d.length;f<h;f++)if(e.value== \
+d[f])e.selected=!0}break;default:a.value=b}}}}();qf.$v=qf.form.getSubmitValue;qf.$cv= \
qf.form.getContainerSubmitValue;qf.classes={add:function(a,b){"string"==qf.typeOf(b)&&(b=b.split(/\\s+/));if(a.className){for(var \
c=" "+a.className+" ",d=a.className,e=0,g=b.length;e<g;e++)b[e]&&0>c.indexOf(" \
"+b[e]+" ")&&(d+=" "+b[e]);a.className=d}else a.className=b.join(" \
")},remove:function(a,b){if(a.className){"string"==qf.typeOf(b)&&(b=b.split(/\\s+/));for(var \
c=(" "+a.className+" ").replace(/[\n\t\r]/g," \
"),d=0,e=b.length;d<e;d++)b[d]&&(c=c.replace(" "+b[d]+" "," \
"));a.className=c.replace(/^\s+/,"").replace(/\s+$/,"")}}, \
+has:function(a,b){if(-1<(" "+a.className+" ").replace(/[\n\t\r]/g," ").indexOf(" \
"+b+" "))return!0;return!1}};qf.events={test:function(){var \
a={submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1},b=document.createElement("div");if(b.attachEvent)for(var \
c in{submit:!0,change:!0,focusin:!0}){var d="on"+c,e=d in \
b;e||(b.setAttribute(d,"return;"),e=typeof b[d]==="function");a[c+"Bubbles"]=e}return \
a}(),addListener:function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.atta \
chEvent("on"+b,c)},removeListener:function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent("on"+b,c)},
 +fixEvent:function(a){a=a||window.event;a.preventDefault=a.preventDefault||function() \
{this.returnValue=!1};a.stopPropagation=a.stopPropagation||function(){this.cancelBubbl \
e=!0};if(!a.target)a.target=a.srcElement;if(!a.relatedTarget&&a.fromElement)a.relatedT \
arget=a.fromElement==a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){var \
b=document.documentElement,c=document.body;a.pageX=a.clientX+(b&&b.scrollLeft||c&&c.scrollLeft||0)-(b.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
 +c&&c.scrollTop||0)-(b.clientTop||0)}if(!a.which&&a.button)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return \
a}};qf.Rule=function(a,b,c,d){this.callback=a;this.owner=b;this.message=c;this.chained \
=d||[[]]};qf.LiveRule=function(a,b,c,d,e){qf.Rule.call(this,a,b,c,e);this.triggers=d};qf.LiveRule.prototype=new \
qf.Rule;qf.LiveRule.prototype.constructor=qf.LiveRule; \
+qf.Validator=function(a,b){this.rules=b||[];this.errors=new \
qf.Map;a.validator=this;qf.events.addListener(a,"submit",qf.Validator.submitHandler);for(var \
c=0,d;d=this.rules[c];c++)if(d instanceof \
qf.LiveRule){qf.events.test.changeBubbles?qf.events.addListener(a,"change",qf.Validator.liveHandler,!0):(qf.events.addListener(a,"click",function(a){var \
a=qf.events.fixEvent(a),b=a.target;("select"==b.nodeName.toLowerCase()||"input"==b.nod \
eName.toLowerCase()&&("checkbox"==b.type||"radio"==b.type))&&qf.Validator.liveHandler(a)}),
 +qf.events.addListener(a,"keydown",function(a){var \
a=qf.events.fixEvent(a),b=a.target,d="type"in \
b?b.type:"";(13==a.keyCode&&"textarea"!=b.nodeName.toLowerCase()||32==a.keyCode&&("che \
ckbox"==d||"radio"==d)||"select-multiple"==d)&&qf.Validator.liveHandler(a)}));qf.event \
s.test.focusinBubbles?qf.events.addListener(a,"focusout",qf.Validator.liveHandler,!0):qf.events.addListener(a,"blur",qf.Validator.liveHandler,!0);break}};
 +qf.Validator.submitHandler=function(a){var \
a=qf.events.fixEvent(a),b=a.target;b.validator&&!b.validator.run(b)&&a.preventDefault()};qf.Validator.liveHandler=function(a){var \
a=qf.events.fixEvent(a),b=a.target.form;b.validator&&b.validator.runLive(a)}; \
+qf.Validator.prototype=function(){function \
a(a){for(a=document.getElementById(a);!qf.classes.has(a,"element")&&"fieldset"!=a.node \
Name.toLowerCase();)a=a.parentNode;qf.classes.remove(a,["error","valid"]);b(a);return \
a}function b(a){for(var \
a=a.getElementsByTagName("span"),b=0,c;c=a[b];b++)qf.classes.has(c,"error")&&c.parentNode.removeChild(c)}function \
c(b,e){b.hasKey(e.owner)&&(b.remove(e.owner),a(e.owner));for(var \
g=0,f;f=e.chained[g];g++)for(var h=0,i;i=f[h];h++)c(b,i)}return{msgPrefix:"Invalid \
information entered:", +msgPostfix:"Please correct these \
fields.",onStart:function(a){b(a)},onError:function(a,b){this.onFieldError(a,b)},onVal \
id:function(){this.onFormValid()},onInvalid:function(){this.onFormError()},onFieldError:function(b,c){var \
g=a(b);qf.classes.add(g,"error");var \
f=document.createElement("span");f.className="error";f.appendChild(document.createText \
Node(c));f.appendChild(document.createElement("br"));if("fieldset"!=g.nodeName.toLowerCase())g.insertBefore(f,g.firstChild);else{var \
h=g.getElementsByTagName("legend"); \
+0==h.length?g.insertBefore(f,g.firstChild):h[h.length-1].parentNode.insertBefore(f,h[ \
h.length-1].nextSibling)}},onFieldValid:function(b){b=a(b);qf.classes.add(b,"valid")}, \
onFormValid:function(){},onFormError:function(){},run:function(a){this.onStart(a);this.errors.clear();for(var \
a=0,b;b=this.rules[a];a++)this.errors.hasKey(b.owner)||this.validate(b);return \
this.errors.isEmpty()?(this.onFormValid(),!0):(this.onFormError(),!1)},runLive:function(a){for(var \
a=" "+a.target.id+" ",b=new qf.Map,g=-1;b.length()> +g;)for(var \
g=b.length(),f=0,h;h=this.rules[f];f++)if(!(!h instanceof \
qf.LiveRule||b.hasKey(f)))for(var i=0,j;j=h.triggers[i];i++)if(-1<a.indexOf(" "+j+" \
")){b.set(f,!0);c(this.errors,h);a+=h.triggers.join(" ")+" \
";break}for(f=0;h=this.rules[f];f++)b.hasKey(f)&&!this.errors.hasKey(h.owner)&&this.validate(h)},validate:function(a){for(var \
b=!1,c=a.callback.call(this),f=0,h;h=a.chained[f];f++){for(var \
i=0,j;j=h[i];i++)if(c=c&&this.validate(j),!c)break;if(b=b||c)break;c=!0}if(!b&&a.message&&!this.errors.hasKey(a.owner))this.errors.set(a.owner,
 +a.message),this.onFieldError(a.owner,a.message);else \
if(!this.errors.hasKey(a.owner))this.onFieldValid(a.owner);return \
b}}}();qf.rules=qf.rules||{};qf.rules.each=function(a){for(var \
b=0;b<a.length;b++)if(!a[b]())return!1;return!0};qf.rules.empty=function(a){switch(qf.typeOf(a)){case \
"array":for(var b=0;b<a.length;b++)if(!qf.rules.empty(a[b]))return!1;return!0;case \
"undefined":case "null":return!0;default:return""==a}}; \
+qf.rules.nonempty=function(a,b){var \
c,d=0;if("array"==qf.typeOf(a)){for(c=0;c<a.length;c++)qf.rules.nonempty(a[c],1)&&d++;return \
d>=b}else if(a instanceof qf.Map){var \
e=a.getValues();if(1==a.length()){c=a.getKeys()[0];var \
g=e[0];if("[]"==c.slice(-2)&&"array"==qf.typeOf(g))return \
qf.rules.nonempty(g,b)}for(c=0;c<e.length;c++)qf.rules.nonempty(e[c],1)&&d++;return \
d>=b}else return""!=a&&"undefined"!=qf.typeOf(a)&&"null"!=qf.typeOf(a)};


Property changes on: pear/packages/HTML_QuickForm2/trunk/data/js/min/quickform.js
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Added: pear/packages/HTML_QuickForm2/trunk/data/js/quickform-hierselect.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/data/js/quickform-hierselect.js	              \
                (rev 0)
+++ pear/packages/HTML_QuickForm2/trunk/data/js/quickform-hierselect.js	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -0,0 +1,283 @@
+/**
+ * HTML_QuickForm2: support functions for hierselect elements
+ * Package version @package_version@
+ * http://pear.php.net/package/HTML_QuickForm2
+ *
+ * Copyright 2006-2011, Alexey Borzov, Bertrand Mansion
+ * Licensed under new BSD license
+ * http://opensource.org/licenses/bsd-license.php
+ */
+
+/* $Id$ */
+
+/**
+ * @namespace Functions for hierselect elements
+ */
+qf.elements.hierselect = (function(){
+    /**
+     * Returns 'onreset' handler for form containing hierselect.
+     *
+     * This repopulates options in second and subsequent selects based on default
+     * values.
+     *
+     * @see     <a href="http://pear.php.net/bugs/bug.php?id=2970">PEAR bug \
#2970</a> +     * @param   {Element}   firstSelect First select element in hierselect \
chain +     * @returns {function()}
+     * @private
+     */
+    function _getResetHandler(firstSelect)
+    {
+        return function() {
+            setTimeout(function() {
+                if (!(firstSelect.id in qf.elements.hierselect.defaults)) {
+                    return;
+                }
+                var defaults = qf.elements.hierselect.defaults[firstSelect.id],
+                    next     = firstSelect.hierselect.next;
+                for (var i = 0; i < next.length; i++) {
+                    qf.elements.hierselect.replaceOptions(
+                        document.getElementById(next[i]),
+                        qf.elements.hierselect.getOptions(firstSelect.id, \
defaults.slice(0, i + 1)) +                    );
+                    qf.form.setValue(next[i], defaults[i + 1]);
+                }
+            }, 1);
+        };
+    };
+
+    /**
+     * Returns 'onload' handler for page containing hierselect.
+     *
+     * This resets hierselect to default values and repopulates options in second \
and +     * subsequent selects.
+     *
+     * @see     <a href="http://pear.php.net/bugs/bug.php?id=3176">PEAR bug \
#3176</a> +     * @param   {Element}   firstSelect First select element in hierselect \
chain +     * @returns {function()}
+     * @private
+     */
+    function _getOnloadHandler(firstSelect)
+    {
+        return function() {
+            if (!(firstSelect.id in qf.elements.hierselect.defaults)) {
+                return;
+            }
+            var defaults = qf.elements.hierselect.defaults[firstSelect.id],
+                next     = firstSelect.hierselect.next;
+            qf.form.setValue(firstSelect, defaults[0]);
+            for (var i = 0; i < next.length; i++) {
+                qf.form.setValue(next[i], defaults[i + 1]);
+            }
+        };
+    };
+
+    /**
+     * Stores options for a select element in options object.
+     *
+     * Useful mostly for asynchronous requests.
+     *
+     * @param   {String} selectId   ID attribute of first select element
+     * @param   {Array}  keys       Values of previous select elements
+     * @param   {Object} options    New options
+     * @private
+     */
+    function _storeOptions(selectId, keys, options)
+    {
+        if (!(selectId in qf.elements.hierselect.options)) {
+            qf.elements.hierselect.options[selectId] = [];
+        }
+        if (typeof qf.elements.hierselect.options[selectId][keys.length - 1] == \
'undefined') { +            qf.elements.hierselect.options[selectId][keys.length - 1] \
= {}; +        }
+        var ary    = qf.elements.hierselect.options[selectId][keys.length - 1];
+        var search = keys.concat();
+        while (search.length) {
+            var key = search.shift();
+            if (0 == search.length) {
+                ary[key] = options;
+            } else if (!(key in ary)) {
+                ary = ary[key] = {};
+            } else {
+                ary = ary[key];
+            }
+        }
+    };
+
+    /**
+     * The 'onchange' handler for selects, replaces the options of subsequent \
select(s). +     * @param {Event} event
+     * @private
+     */
+    function _onChangeHandler(event)
+    {
+        event = qf.events.fixEvent(event);
+        if (event.target.hierselect && 0 != event.target.hierselect.next.length) {
+            qf.elements.hierselect.cascade.call(event.target);
+        }
+    };
+
+    return {
+        /**
+         * Adds event handlers for hierselect behavior.
+         *
+         * @param {Array} selects               IDs of select elements in hierselect
+         * @param {Function} optionsCallback    function that will be called to
+         *                  get missing options (presumably via AJAX)
+         */
+        init: function(selects, optionsCallback)
+        {
+            var previous    = [];
+            var firstSelect = document.getElementById(selects[0]);
+            // add onchange listeners to all hierselect members
+            for (var select; selects.length && (select = selects.shift());) {
+                previous.push(select);
+                var el = document.getElementById(select);
+                el.hierselect = {
+                    previous: previous.concat(),
+                    next:     selects.concat(),
+                    callback: optionsCallback
+                };
+                qf.events.addListener(el, 'change', _onChangeHandler);
+            }
+            qf.events.addListener(firstSelect.form, 'reset', \
_getResetHandler(firstSelect)); +            qf.events.addListener(window, 'load', \
_getOnloadHandler(firstSelect)); +        },
+
+        /**
+         * Gets the value for a hierselect element.
+         *
+         * @param   {String[]}  selects Array of selects' ID attributes
+         * @returns {Array}
+         */
+        getValue: function(selects)
+        {
+            var value = [];
+            for (var i = 0; i < selects.length; i++) {
+                value.push(qf.form.getValue(selects[i]));
+            }
+            return value;
+        },
+
+        /**
+         * Replaces options of a select element.
+         *
+         * Options are provided in such a way rather than as {value: text, ...} \
object +         * due to the fact that browsers can iterate over an object with a \
'for in' +         * loop in random order (see bug).
+         *
+         * @see     <a href="http://pear.php.net/bugs/bug.php?id=16603">PEAR bug \
#16603</a> +         * @param   {Element} ctl   Select element
+         * @param   {Object}  options New options
+         * @param   {Array}   options.values Values of new options
+         * @param   {Array}   options.texts  Texts of new options
+         */
+        replaceOptions: function(ctl, options)
+        {
+            function unescapeEntities(str)
+            {
+                var div = document.createElement('div');
+                div.innerHTML = str;
+                return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
+            }
+
+            ctl.options.length = 0;
+            for (var i = 0; i < options.values.length; i++) {
+                ctl.options[i] = new Option(
+                    -1 == String(options.texts[i]).indexOf('&')? options.texts[i]: \
unescapeEntities(options.texts[i]), +                    options.values[i], false, \
false +                );
+            }
+        },
+
+        /**
+         * Finds options for next select element in hierselect.
+         *
+         * @param   {String} selectId   ID attribute of first select element
+         * @param   {Array}  keys       Values of previous select elements
+         * @param   {Function} callback Function to use for loading additional \
options +         * @returns {Object}
+         */
+        getOptions: function(selectId, keys, callback)
+        {
+            if (!(selectId in qf.elements.hierselect.options)
+                || typeof qf.elements.hierselect.options[selectId][keys.length - 1] \
== 'undefined' +            ) {
+                return qf.elements.hierselect.missingOptions;
+            }
+            var ary    = qf.elements.hierselect.options[selectId][keys.length - 1];
+            // we need to pass keys to a callback, so don't mangle 'em.
+            var search = keys.concat();
+            while (search.length) {
+                var key = search.shift();
+                if (0 == search.length) {
+                    if (!(key in ary) ) {
+                        ary[key] = callback? callback(keys, selectId): \
qf.elements.hierselect.missingOptions; +                    }
+                    return ary[key];
+                } else if (!(key in ary)) {
+                    ary[key] = {};
+                }
+                ary = ary[key];
+            }
+        },
+
+        /**
+         * Returns a callback that should be called on successful completion of \
asynchronous request for additional options. +         *
+         * @param   {String} selectId   ID attribute of first select element in \
hierselect +         * @param   {Array}  keys       Values of previous select \
elements +         * @returns {Function}
+         */
+        getAsyncCallback: function(selectId, keys)
+        {
+            return function(result) {
+                _storeOptions(selectId, keys, result);
+                var hs   = document.getElementById(selectId).hierselect;
+                var next = document.getElementById(hs.next[keys.length - 1]);
+                qf.elements.hierselect.replaceOptions(next, result);
+                if (keys.length < hs.next.length) {
+                    qf.elements.hierselect.cascade.call(next);
+                }
+            };
+        },
+
+        /**
+         * Replaces the options of subsequent selects based on values of this and \
previous ones. +         */
+        cascade: function()
+        {
+            // find values, starting from first upto current
+            var values = qf.elements.hierselect.getValue(this.hierselect.previous);
+            // replace options on next select
+            qf.elements.hierselect.replaceOptions(
+                document.getElementById(this.hierselect.next[0]),
+                qf.elements.hierselect.getOptions(this.hierselect.previous[0], \
values, +                                                  this.hierselect.callback)
+            );
+            // if next select is not last, call cascade on that, too
+            if (1 < this.hierselect.next.length) {
+                qf.elements.hierselect.cascade.call(document.getElementById(this.hierselect.next[0]));
 +            }
+        },
+
+        /**
+         * Options to use if no options were found. Select without options is \
invalid in HTML. +         * @type {Object}
+         */
+        missingOptions: {values: [''], texts: [' ']},
+
+        /**
+         * Options cache for second and subsequent selects in hierselect. Keyed by
+         * ID attribute of first select in chain.
+         * @type {Object}
+         */
+        options: {},
+
+        /**
+         * Default values for hierselects. Keyed by ID attribute of first select in \
chain. +         * @type {Object}
+         */
+        defaults: {}
+    };
+})();
+


Property changes on: \
pear/packages/HTML_QuickForm2/trunk/data/js/quickform-hierselect.js \
                ___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Added: pear/packages/HTML_QuickForm2/trunk/data/js/quickform.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/data/js/quickform.js	                        \
                (rev 0)
+++ pear/packages/HTML_QuickForm2/trunk/data/js/quickform.js	2011-04-26 18:42:03 UTC \
(rev 310525) @@ -0,0 +1,1246 @@
+/**
+ * HTML_QuickForm2 client-side validation library
+ * Package version @package_version@
+ * http://pear.php.net/package/HTML_QuickForm2
+ *
+ * Copyright 2006-2011, Alexey Borzov, Bertrand Mansion
+ * Licensed under new BSD license
+ * http://opensource.org/licenses/bsd-license.php
+ */
+
+/* $Id$ */
+
+/**
+ * @namespace Base namespace for QuickForm, we no longer define our stuff in global \
namespace + */
+var qf = qf || {};
+
+/**
+ * @namespace Namespace for QuickForm elements' javascript
+ */
+qf.elements = qf.elements || {};
+
+/**
+ * Enhanced version of typeof operator.
+ *
+ * Returns "null" for null values and "array" for arrays. Handles edge cases
+ * like objects passed across browser windows, etc. Borrowed from closure library.
+ *
+ * @param   {*} value   The value to get the type of
+ * @returns {string}    Type name
+ */
+qf.typeOf = function(value) {
+    var s = typeof value;
+    if ('function' == s && 'undefined' == typeof value.call) {
+        return 'object';
+    } else if ('object' == s) {
+        if (!value) {
+            return 'null';
+
+        } else {
+            if (value instanceof Array
+                || (!(value instanceof Object)
+                    && '[object Array]' == Object.prototype.toString.call(value)
+                    && 'number' == typeof value.length
+                    && 'undefined' != typeof value.splice
+                    && 'undefined' != typeof value.propertyIsEnumerable
+                    && !value.propertyIsEnumerable('splice'))
+            ) {
+                return 'array';
+            }
+            if (!(value instanceof Object)
+                && ('[object Function]' == Object.prototype.toString.call(value)
+                    || 'undefined' != typeof value.call
+                    && 'undefined' != typeof value.propertyIsEnumerable
+                    && !value.propertyIsEnumerable('call'))
+            ) {
+                return 'function';
+            }
+        }
+    }
+    return s;
+};
+
+/**
+ * Builds an object structure for the provided namespace path.
+ *
+ * Ensures that names that already exist are not overwritten. For
+ * example:
+ * <code>
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * </code>
+ * Borrowed from closure library.
+ *
+ * @param   {string}    ns name of the object that this file defines.
+ */
+qf.addNamespace = function(ns) {
+    var parts = ns.split('.');
+    var cur   = window;
+
+    for (var part; parts.length && (part = parts.shift());) {
+        if (cur[part]) {
+            cur = cur[part];
+        } else {
+            cur = cur[part] = {};
+        }
+    }
+};
+
+/**
+ * Class for Hash Map datastructure.
+ *
+ * Used for storing container values and validation errors, mostly borrowed
+ * from closure library.
+ *
+ * @param   {Object}    [inMap] Object or qf.Map instance to initialize the map
+ * @constructor
+ */
+qf.Map = function(inMap)
+{
+   /**
+    * Actual JS Object used to store the map
+    * @type {Object}
+    * @private
+    */
+    this._map   = {};
+
+   /**
+    * An array of map keys
+    * @type {String[]}
+    * @private
+    */
+    this._keys  = [];
+
+   /**
+    * Number of key-value pairs in the map
+    * @type {number}
+    * @private
+    */
+    this._count = 0;
+
+    if (inMap) {
+        this.merge(inMap);
+    }
+};
+
+qf.Map.prototype = (function(){
+    /**
+     * Wrapper function for hasOwnProperty
+     * @param   {Object}    obj
+     * @param   {*}         key
+     * @returns {boolean}
+     * @private
+     */
+    function _hasKey(obj, key)
+    {
+        return Object.prototype.hasOwnProperty.call(obj, key);
+    };
+
+    /**
+     * Removes keys that are no longer in the map from the _keys array
+     * @private
+     */
+    function _cleanupKeys()
+    {
+        if (this._count == this._keys.length) {
+            return;
+        }
+        var srcIndex  = 0;
+        var destIndex = 0;
+        var seen      = {};
+        while (srcIndex < this._keys.length) {
+            var key = this._keys[srcIndex];
+            if (_hasKey(this._map, key)
+                && !_hasKey(seen, key)
+            ) {
+                this._keys[destIndex++] = key;
+                seen[key] = true;
+            }
+            srcIndex++;
+        }
+        this._keys.length = destIndex;
+    };
+
+    return {
+        /**
+         * Whether the map has the given key
+         * @param   {*}     key
+         * @returns {boolean}
+         */
+        hasKey: function(key)
+        {
+            return _hasKey(this._map, key);
+        },
+
+        /**
+         * Returns the number of key-value pairs in the Map
+         * @returns {number}
+         */
+        length: function()
+        {
+            return this._count;
+        },
+
+        /**
+         * Returns the values of the Map
+         * @returns {Array}
+         */
+        getValues: function()
+        {
+            _cleanupKeys.call(this);
+
+            var ret = [];
+            for (var i = 0; i < this._keys.length; i++) {
+                ret.push(this._map[this._keys[i]]);
+            }
+            return ret;
+        },
+
+        /**
+         * Returns the keys of the Map
+         * @returns {String[]}
+         */
+        getKeys: function()
+        {
+            _cleanupKeys.call(this);
+            return (this._keys.concat());
+        },
+
+        /**
+         * Returns whether the Map is empty
+         * @returns {boolean}
+         */
+        isEmpty: function()
+        {
+            return 0 == this._count;
+        },
+
+        /**
+         * Removes all key-value pairs from the map
+         */
+        clear: function()
+        {
+            this._map         = {};
+            this._keys.length = 0;
+            this._count       = 0;
+        },
+
+        /**
+         * Removes a key-value pair from the Map
+         * @param   {*}         key The key to remove
+         * @returns {boolean}   Whether the pair was removed
+         */
+        remove: function(key)
+        {
+            if (!_hasKey(this._map, key)) {
+                return false;
+            }
+
+            delete this._map[key];
+            this._count--;
+            if (this._keys.length > this._count * 2) {
+                _cleanupKeys.call(this);
+            }
+            return true;
+        },
+
+        /**
+         * Returns the value for the given key
+         * @param   {*} key The key to look for
+         * @param   {*} [defaultVal] The value to return if the key is not in the \
Map +         * @returns {*}
+         */
+        get: function(key, defaultVal)
+        {
+            if (_hasKey(this._map, key)) {
+                return this._map[key];
+            }
+            return defaultVal;
+        },
+
+        /**
+         * Adds a key-value pair to the Map
+         * @param {*} key
+         * @param {*} value
+         */
+        set: function(key, value)
+        {
+            if (!_hasKey(this._map, key)) {
+                this._count++;
+                this._keys.push(key);
+            }
+            this._map[key] = value;
+        },
+
+        /**
+         * Merges key-value pairs from another Object or Map
+         * @param {Object} map
+         * @param {function(*, *)} [mergeFn] Optional function to call on values if
+         *      both maps have the same key. By default a value from the map being
+         *      merged will be stored under that key.
+         */
+        merge: function(map, mergeFn)
+        {
+            var keys, values, i = 0;
+            if (map instanceof qf.Map) {
+                keys   = map.getKeys();
+                values = map.getValues();
+            } else {
+                keys   = [];
+                values = [];
+                for (var key in map) {
+                    keys[i]     = key;
+                    values[i++] = map[key];
+                }
+            }
+
+            var fn = mergeFn || qf.Map.mergeReplace;
+
+            for (i = 0; i < keys.length; i++) {
+                if (!this.hasKey(keys[i])) {
+                    this.set(keys[i], values[i]);
+                } else {
+                    this.set(keys[i], fn(this.get(keys[i]), values[i]));
+                }
+            }
+        }
+    };
+})();
+
+/**
+ * Callback for merge(), forces to use second value.
+ *
+ * This makes Map.merge() behave like PHP's array_merge() function
+ *
+ * @param   {*} a Original value in map
+ * @param   {*} b Value in the map being merged
+ * @returns {*} second value
+ */
+qf.Map.mergeReplace = function(a, b)
+{
+    return b;
+};
+
+/**
+ * Callback for merge(), forces to use first value.
+ *
+ * This makes Map.merge() behave like PHP's + operator for arrays
+ *
+ * @param   {*} a Original value in map
+ * @param   {*} b Value in the map being merged
+ * @returns {*} first value
+ */
+qf.Map.mergeKeep = function(a, b)
+{
+    return a;
+};
+
+/**
+ * Callback for merge(), concatenates values.
+ *
+ * If the values are not arrays, they are first converted to ones.
+ *
+ * This callback makes Map.merge() behave somewhat like PHP's \
array_merge_recursive() + *
+ * @param   {*} a Original value in map
+ * @param   {*} b Value in the map being merged
+ * @returns {Array} array containing both values
+ */
+qf.Map.mergeArrayConcat = function(a, b)
+{
+    if ('array' != qf.typeOf(a)) {
+        a = [a];
+    }
+    if ('array' != qf.typeOf(b)) {
+        b = [b];
+    }
+    return a.concat(b);
+};
+
+/**
+ * @namespace Helper functions for working with form values
+ */
+qf.form = (function() {
+    /**
+     * Gets the value of select-multiple element.
+     *
+     * @param   {Element}   el  The element
+     * @returns {String[]}
+     * @private
+     */
+    function _getSelectMultipleValue(el)
+    {
+        var values = [];
+        for (var i = 0; i < el.options.length; i++) {
+            if (el.options[i].selected) {
+                values.push(el.options[i].value);
+            }
+        }
+        return values;
+    };
+
+    /**
+     * Sets the value of a select-one element.
+     * @param   {Element} el
+     * @param   {String}  value
+     * @private
+     */
+    function _setSelectSingleValue(el, value)
+    {
+        el.selectedIndex = -1;
+        for (var option, i = 0; option = el.options[i]; i++) {
+            if (option.value == value) {
+                option.selected = true;
+                return;
+            }
+        }
+    };
+
+    /**
+     * Sets the value of a select-multiple element.
+     * @param   {Element} el
+     * @param   {String|String[]} value
+     * @private
+     */
+    function _setSelectMultipleValue(el, value)
+    {
+        if ('array' != qf.typeOf(value)) {
+            value = [value];
+        }
+        for (var option, i = 0; option = el.options[i]; i++) {
+            option.selected = false;
+            for (var j = 0, l = value.length; j < l; j++) {
+                if (option.value == value[j]) {
+                    option.selected = true;
+                }
+            }
+        }
+    }
+
+    return {
+        /**
+         * Gets the value of a form element.
+         *
+         * @param   {string|Element} el
+         * @returns {string|string[]|null}
+         */
+        getValue: function(el)
+        {
+            if (typeof el == 'string') {
+                el = document.getElementById(el);
+            }
+            if (!el || !('type' in el)) {
+                return null;
+            }
+            switch (el.type.toLowerCase()) {
+                case 'checkbox':
+                case 'radio':
+                    return el.checked? el.value: null;
+                case 'select-one':
+                    var index = el.selectedIndex;
+                    return -1 == index? null: el.options[index].value;
+                case 'select-multiple':
+                    return _getSelectMultipleValue(el);
+                default:
+                    return (typeof el.value == 'undefined')? null: el.value;
+            }
+        },
+
+        /**
+         * Gets the submit value of a form element. It will return null for disabled
+         * elements and elements that cannot have submit values (buttons, reset \
controls). +         *
+         * @param   {string|Element} el
+         * @returns {string|string[]|null}
+         */
+        getSubmitValue: function(el)
+        {
+            if (typeof el == 'string') {
+                el = document.getElementById(el);
+            }
+            if (!el || (!'type' in el) || el.disabled) {
+                return null;
+            }
+            switch (el.type.toLowerCase()) {
+                case 'reset':
+                case 'button':
+                    return null;
+                default:
+                    return qf.form.getValue(el);
+            }
+        },
+
+        /**
+         * Gets the submit values of a container.
+         *
+         * @param   [...] This accepts a variable number of arguments, that are \
either +         *      strings (considered element ID attributes), objects {name: \
element name, +         *      value: element value} or instances of qf.Map, \
representing the contained elements +         * @returns qf.Map
+         */
+        getContainerSubmitValue: function()
+        {
+            var map = new qf.Map();
+            for (var i = 0; i < arguments.length; i++) {
+                if (arguments[i] instanceof qf.Map) {
+                    map.merge(arguments[i], qf.Map.mergeArrayConcat);
+                } else {
+                    if ('object' == qf.typeOf(arguments[i])) {
+                        var k  = arguments[i].name;
+                        var v  = arguments[i].value;
+                    } else {
+                        var k = document.getElementById(arguments[i]).name;
+                        var v = qf.form.getSubmitValue(arguments[i]);
+                    }
+                    if (null !== v) {
+                        var valueObj = {};
+                        valueObj[k] = v;
+                        map.merge(valueObj, qf.Map.mergeArrayConcat);
+                    }
+                }
+            }
+            return map;
+        },
+
+        /**
+         * Sets the value of a form element.
+         * @param   {String|Element} el
+         * @param   {*} value
+         */
+        setValue: function(el, value)
+        {
+            if (typeof el == 'string') {
+                el = document.getElementById(el);
+            }
+            if (!el || !('type' in el)) {
+                return;
+            }
+            switch (el.type.toLowerCase()) {
+                case 'checkbox':
+                case 'radio':
+                    el.checked = !!value;
+                    break;
+                case 'select-one':
+                    _setSelectSingleValue(el, value);
+                    break;
+                case 'select-multiple':
+                    _setSelectMultipleValue(el, value);
+                    break;
+                default:
+                    el.value = value;
+            }
+        }
+    };
+})();
+
+
+/**
+ * Alias for qf.form.getSubmitValue
+ * @type {Function}
+ */
+qf.$v = qf.form.getSubmitValue;
+
+/**
+ * Alias for qf.form.getContainerSubmitValue
+ * @type {Function}
+ */
+qf.$cv = qf.form.getContainerSubmitValue;
+
+/**
+ * @namespace Functions for CSS classes handling
+ */
+qf.classes = {
+    /**
+     * Adds a class or a list of classes to an element, without duplicating class \
names +     *
+     * @param {Node} element            DOM node to add class(es) to
+     * @param {string|string[]} name    Class name(s) to add
+     */
+    add: function(element, name)
+    {
+        if ('string' == qf.typeOf(name)) {
+            name = name.split(/\\s+/);
+        }
+        if (!element.className) {
+            element.className = name.join(' ');
+        } else {
+            var checkName = ' ' + element.className + ' ',
+                newName   = element.className;
+            for (var i = 0, len = name.length; i < len; i++) {
+                if (name[i] && 0 > checkName.indexOf(' ' + name[i] + ' ')) {
+                    newName += ' ' + name[i];
+                }
+            }
+            element.className = newName;
+        }
+    },
+
+    /**
+     * Removes a class or a list of classes from an element
+     *
+     * @param {Node} element            DOM node to remove class(es) from
+     * @param {string|string[]} name    Class name(s) to remove
+     */
+    remove: function(element, name)
+    {
+        if (!element.className) {
+            return;
+        }
+        if ('string' == qf.typeOf(name)) {
+            name = name.split(/\\s+/);
+        }
+        var className = (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ');
+        for (var i = 0, len = name.length; i < len; i++) {
+            if (name[i]) {
+                className = className.replace(' ' + name[i] + ' ', ' ');
+            }
+        }
+        element.className = className.replace(/^\s+/, '').replace(/\s+$/, '');
+    },
+
+    /**
+     * Checks whether a given element has a given class
+     *
+     * @param   {Node} element  DOM node to check
+     * @param   {string} name   Class name to check for
+     * @returns {boolean}
+     */
+    has: function(element, name)
+    {
+        if (-1 < (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ').indexOf(' \
' + name + ' ')) { +            return true;
+        }
+        return false;
+    }
+};
+
+/**
+ * @namespace Minor fixes to JS events to make them a bit more crossbrowser
+ */
+qf.events = {
+    /**
+     * Tests for specific events support
+     *
+     * Code "inspired" by jQuery, original technique from here:
+     * http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+     *
+     * @type {Object}
+     */
+    test: (function() {
+        var test = {
+            submitBubbles: true,
+            changeBubbles: true,
+            focusinBubbles: false
+        };
+        var div = document.createElement('div');
+
+        if (div.attachEvent) {
+            for (var i in {'submit': true, 'change': true, 'focusin': true}) {
+                var eventName   = 'on' + i;
+                var isSupported = (eventName in div);
+                if (!isSupported) {
+                    div.setAttribute(eventName, 'return;');
+                    isSupported = (typeof div[eventName] === 'function');
+                }
+                test[i + 'Bubbles'] = isSupported;
+            }
+        }
+        return test;
+    })(),
+
+    /**
+     * A na&iuml;ve wrapper around addEventListener() and attachEvent().
+     *
+     * QuickForm does not need a complete framework for crossbrowser event handling
+     * and does not provide one. Use one of a zillion javascript libraries if you
+     * need such a framework in your application.
+     *
+     * @param   {Element}    element
+     * @param   {String}     type
+     * @param   {function()} handler
+     * @param   {boolean}    capture
+     */
+    addListener: function(element, type, handler, capture)
+    {
+        if (element.addEventListener) {
+            element.addEventListener(type, handler, capture);
+        } else {
+            element.attachEvent('on' + type, handler);
+        }
+    },
+
+    /**
+     * A na&iuml;ve wrapper around removeEventListener() and detachEvent().
+     *
+     * @param   {Element}    element
+     * @param   {String}     type
+     * @param   {function()} handler
+     * @param   {boolean}    capture
+     */
+    removeListener: function(element, type, handler, capture)
+    {
+        if (element.removeEventListener) {
+            element.removeEventListener(type, handler, capture);
+        } else {
+            element.detachEvent('on' + type, handler);
+        }
+    },
+
+    /**
+     * Adds some standard fields to (IE's) event object.
+     *
+     * This is intended to be used in event handlers like this:
+     * <code>
+     * function handler(event) {
+     *     event = qf.events.fixEvent(event);
+     *     ...
+     * }
+     * </code>
+     *
+     * @param   {Event} [e]
+     * @returns {Event}
+     */
+    fixEvent: function(e)
+    {
+        e = e || window.event;
+
+        e.preventDefault  = e.preventDefault || function() { this.returnValue = \
false; }; +        e.stopPropagation = e.stopPropagation || function() { \
this.cancelBubble = true; }; +
+        if (!e.target) {
+            e.target = e.srcElement;
+        }
+
+        if (!e.relatedTarget && e.fromElement) {
+            e.relatedTarget = e.fromElement == e.target ? e.toElement : \
e.fromElement; +        }
+
+        if (e.pageX == null && e.clientX != null) {
+            var html = document.documentElement;
+            var body = document.body;
+            e.pageX = e.clientX + (html && html.scrollLeft || body && \
body.scrollLeft || 0) - (html.clientLeft || 0); +            e.pageY = e.clientY + \
(html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0); +    \
} +
+        if (!e.which && e.button) {
+            e.which = e.button & 1 ? 1 : (e.button & 2 ? 3 : (e.button & 4 ? 2 : \
0)); +        }
+
+        return e;
+    }
+};
+
+/**
+ * Client-side rule object
+ *
+ * @param {function()} callback
+ * @param {string}     owner
+ * @param {string}     message
+ * @param {Array}      chained
+ * @constructor
+ */
+qf.Rule = function(callback, owner, message, chained)
+{
+   /**
+    * Function performing actual validation
+    * @type {function()}
+    */
+    this.callback = callback;
+
+   /**
+    * ID of owner element
+    * @type {string}
+    */
+    this.owner    = owner;
+
+   /**
+    * Error message to set if validation fails
+    * @type {string}
+    */
+    this.message  = message;
+
+   /**
+    * Chained rules
+    * @type {Array}
+    */
+    this.chained  = chained || [[]];
+};
+
+/**
+ * Client-side rule object that should run onblur / onchange
+ *
+ * @param {function()} callback
+ * @param {string}     owner
+ * @param {string}     message
+ * @param {string[]}   triggers
+ * @param {Array}      chained
+ * @constructor
+ */
+qf.LiveRule = function(callback, owner, message, triggers, chained)
+{
+    qf.Rule.call(this, callback, owner, message, chained);
+
+   /**
+    * IDs of elements that should trigger validation
+    * @type {string[]}
+    */
+    this.triggers = triggers;
+};
+
+qf.LiveRule.prototype = new qf.Rule();
+qf.LiveRule.prototype.constructor = qf.LiveRule;
+
+/**
+ * Form validator, attaches handlers that run the given rules.
+ *
+ * @param {HTMLFormElement} form
+ * @param {qf.Rule[]} rules
+ * @constructor
+ */
+qf.Validator = function(form, rules)
+{
+   /**
+    * Validation rules
+    * @type {qf.Rule[]}
+    */
+    this.rules  = rules || [];
+
+   /**
+    * Form errors, keyed by element's ID attribute
+    * @type {qf.Map}
+    */
+    this.errors = new qf.Map();
+
+    form.validator = this;
+    qf.events.addListener(form, 'submit', qf.Validator.submitHandler);
+
+    for (var i = 0, rule; rule = this.rules[i]; i++) {
+        if (rule instanceof qf.LiveRule) {
+            if (qf.events.test.changeBubbles) {
+                qf.events.addListener(form, 'change', qf.Validator.liveHandler, \
true); +
+            } else {
+                // This is IE with change event not bubbling... We don't
+                // terribly need an onchange event here, only an event that
+                // fires sometime around onchange. Therefore no checks whether
+                // a value actually *changed*
+                qf.events.addListener(form, 'click', function (event) {
+                    event  = qf.events.fixEvent(event);
+                    var el = event.target;
+                    if ('select' == el.nodeName.toLowerCase()
+                        || 'input' == el.nodeName.toLowerCase()
+                         && ('checkbox' == el.type || 'radio' == el.type)
+                    ) {
+                        qf.Validator.liveHandler(event);
+                    }
+                });
+                qf.events.addListener(form, 'keydown', function (event) {
+                    event  = qf.events.fixEvent(event);
+                    var el = event.target, type = ('type' in el)? el.type: '';
+                    if ((13 == event.keyCode && 'textarea' != \
el.nodeName.toLowerCase()) +                        || (32 == event.keyCode && \
('checkbox' == type || 'radio' == type)) +                        || \
'select-multiple' == type +                    ) {
+                        qf.Validator.liveHandler(event);
+                    }
+                });
+            }
+
+            if (qf.events.test.focusinBubbles) {
+                qf.events.addListener(form, 'focusout', qf.Validator.liveHandler, \
true); +            } else {
+                qf.events.addListener(form, 'blur', qf.Validator.liveHandler, true);
+            }
+            break;
+        }
+    }
+};
+
+/**
+ * Event handler for form's onsubmit events.
+ * @param {Event} event
+ */
+qf.Validator.submitHandler = function(event)
+{
+    event    = qf.events.fixEvent(event);
+    var form = event.target;
+    if (form.validator && !form.validator.run(form)) {
+        event.preventDefault();
+    }
+};
+
+/**
+ * Event handler for form's onblur and onchange events
+ * @param {Event} event
+ */
+qf.Validator.liveHandler = function (event)
+{
+    event    = qf.events.fixEvent(event);
+    var form = event.target.form;
+    if (form.validator) {
+        form.validator.runLive(event);
+    }
+};
+
+qf.Validator.prototype = (function() {
+    /**
+     * Clears validation status and error message of a given element
+     *
+     * @param   {string} elementId
+     * @returns {Node}              Parent element that gets 'error' / 'valid'
+     *                              classes applied
+     * @private
+     */
+    function _clearValidationStatus(elementId)
+    {
+        var el = document.getElementById(elementId), parent = el;
+        while (!qf.classes.has(parent, 'element') && 'fieldset' != \
parent.nodeName.toLowerCase()) { +            parent = parent.parentNode;
+        }
+        qf.classes.remove(parent, ['error', 'valid']);
+
+        _clearErrors(parent);
+
+        return parent;
+    };
+
+    /**
+     * Removes <span> elements with "error" class that are children of a given \
element +     *
+     * @param   {Node} element
+     * @private
+     */
+    function _clearErrors(element)
+    {
+        var spans = element.getElementsByTagName('span');
+        for (var i = 0, span; span = spans[i]; i++) {
+            if (qf.classes.has(span, 'error')) {
+                span.parentNode.removeChild(span);
+            }
+        }
+    };
+
+    /**
+     * Removes error messages from owner element(s) of a given rule and chained \
rules +     *
+     * @param {qf.Map} errors
+     * @param {qf.Rule} rule
+     * @private
+     */
+    function _removeRelatedErrors(errors, rule)
+    {
+        if (errors.hasKey(rule.owner)) {
+            errors.remove(rule.owner);
+            _clearValidationStatus(rule.owner);
+        }
+        for (var i = 0, item; item = rule.chained[i]; i++) {
+            for (var j = 0, multiplier; multiplier = item[j]; j++) {
+                _removeRelatedErrors(errors, multiplier);
+            }
+        }
+    };
+
+    return {
+        /**
+         * Message prefix in alert in case of failed validation
+         * @type {String}
+         */
+        msgPrefix: 'Invalid information entered:',
+
+        /**
+         * Message postfix in alert in case of failed validation
+         * @type {String}
+         */
+        msgPostfix: 'Please correct these fields.',
+
+        /**
+         * Called before starting the validation. May be used e.g. to clear the \
errors from form elements. +         * @param {HTMLFormElement} form The form being \
validated currently +         */
+        onStart: function(form)
+        {
+            _clearErrors(form);
+        },
+
+        /**
+         * Called on setting the element error
+         *
+         * @param {string} elementId ID attribute of an element
+         * @param {string} errorMessage
+         * @deprecated Use onFieldError() instead
+         */
+        onError: function(elementId, errorMessage)
+        {
+            this.onFieldError(elementId, errorMessage);
+        },
+
+        /**
+         * Called on successfully validating the form
+         * @deprecated Use onFormValid() instead
+         */
+        onValid: function()
+        {
+            this.onFormValid();
+        },
+
+        /**
+         * Called on failed validation
+         * @deprecated Use onFormError() instead
+         */
+        onInvalid: function()
+        {
+            this.onFormError();
+        },
+
+        /**
+         * Called on setting the element error
+         *
+         * @param {string} elementId ID attribute of an element
+         * @param {string} errorMessage
+         */
+        onFieldError: function(elementId, errorMessage)
+        {
+            var parent = _clearValidationStatus(elementId);
+            qf.classes.add(parent, 'error');
+
+            var error = document.createElement('span');
+            error.className = 'error';
+            error.appendChild(document.createTextNode(errorMessage));
+            error.appendChild(document.createElement('br'));
+            if ('fieldset' != parent.nodeName.toLowerCase()) {
+                parent.insertBefore(error, parent.firstChild);
+            } else {
+                // error span should be inserted *after* legend, IE will render it \
before fieldset otherwise +                var legends = \
parent.getElementsByTagName('legend'); +                if (0 == legends.length) {
+                    parent.insertBefore(error, parent.firstChild);
+                } else {
+                    legends[legends.length - 1].parentNode.insertBefore(error, \
legends[legends.length - 1].nextSibling); +                }
+            }
+        },
+
+        /**
+         * Called on successfully validating the element
+         *
+         * @param {string} elementId
+         */
+        onFieldValid: function(elementId)
+        {
+            var parent = _clearValidationStatus(elementId);
+            qf.classes.add(parent, 'valid');
+        },
+
+        /**
+         * Called on successfully validating the form
+         */
+        onFormValid: function() {},
+
+        /**
+         * Called on failed validation
+         */
+        onFormError: function()
+        {
+            //alert(this.msgPrefix + '\n - ' + this.errors.getValues().join('\n - ') \
+ '\n' + this.msgPostfix); +        },
+
+        /**
+         * Performs validation using the stored rules
+         * @param   {HTMLFormElement} form    The form being validated
+         * @returns {boolean}
+         */
+        run: function(form)
+        {
+            this.onStart(form);
+
+            this.errors.clear();
+            for (var i = 0, rule; rule = this.rules[i]; i++) {
+                if (this.errors.hasKey(rule.owner)) {
+                    continue;
+                }
+                this.validate(rule);
+            }
+
+            if (this.errors.isEmpty()) {
+                this.onFormValid();
+                return true;
+
+            } else {
+                this.onFormError();
+                return false;
+            }
+        },
+
+        /**
+         * Performs live validation of an element and related ones
+         *
+         * @param {Event} event     Event triggering the validation
+         */
+        runLive: function(event)
+        {
+            var testId   = ' ' + event.target.id + ' ',
+                ruleHash = new qf.Map(),
+                length   = -1;
+
+            // first: find all rules "related" to the given element, clear their \
error messages +            while (ruleHash.length() > length) {
+                length = ruleHash.length();
+                for (var i = 0, rule; rule = this.rules[i]; i++) {
+                    if (!rule instanceof qf.LiveRule || ruleHash.hasKey(i)) {
+                        continue;
+                    }
+                    for (var j = 0, trigger; trigger = rule.triggers[j]; j++) {
+                        if (-1 < testId.indexOf(' ' + trigger + ' ')) {
+                            ruleHash.set(i, true);
+                            _removeRelatedErrors(this.errors, rule);
+                            testId += rule.triggers.join(' ') + ' ';
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // second: run all "related" rules
+            for (i = 0; rule = this.rules[i]; i++) {
+                if (!ruleHash.hasKey(i) || this.errors.hasKey(rule.owner)) {
+                    continue;
+                }
+                this.validate(rule);
+            }
+        },
+
+        /**
+         * Performs validation, sets the element's error if validation fails.
+         *
+         * @param   {qf.Rule} rule Validation rule, maybe with chained rules
+         * @returns {boolean}
+         */
+        validate: function(rule)
+        {
+            var globalValid = false, localValid = rule.callback.call(this);
+
+            for (var i = 0, item; item = rule.chained[i]; i++) {
+                for (var j = 0, multiplier; multiplier = item[j]; j++) {
+                    localValid = localValid && this.validate(multiplier);
+                    if (!localValid) {
+                        break;
+                    }
+                }
+                globalValid = globalValid || localValid;
+                if (globalValid) {
+                    break;
+                }
+                localValid = true;
+            }
+
+            if (!globalValid && rule.message && !this.errors.hasKey(rule.owner)) {
+                this.errors.set(rule.owner, rule.message);
+                this.onFieldError(rule.owner, rule.message);
+            } else if (!this.errors.hasKey(rule.owner)) {
+                this.onFieldValid(rule.owner);
+            }
+
+            return globalValid;
+        }
+    };
+})();
+
+/**
+ * @namespace Client-side implementations of Rules that are a bit too complex to \
inline + */
+qf.rules = qf.rules || {};
+
+// NB: we do not overwrite qf.rules namespace because some custom rules may be \
already added +
+/**
+ * Returns true if all the given callbacks return true, false otherwise.
+ *
+ * Client-side implementation of HTML_QuickForm2_Rule_Each, consult PHPDoc
+ * description there.
+ *
+ * @param   {function()[]} callbacks
+ * @returns {boolean}
+ */
+qf.rules.each = function(callbacks)
+{
+    for (var i = 0; i < callbacks.length; i++) {
+        if (!callbacks[i]()) {
+            return false;
+        }
+    }
+    return true;
+};
+
+/**
+ * Tests that a given value is empty.
+ *
+ * A scalar value is empty if it either null, undefined or an empty string. An
+ * array is empty if it contains only empty values.
+ *
+ * @param   {*} value
+ * @returns {boolean}
+ */
+qf.rules.empty = function(value)
+{
+    switch (qf.typeOf(value)) {
+        case 'array':
+            for (var i = 0; i < value.length; i++) {
+                if (!qf.rules.empty(value[i])) {
+                    return false;
+                }
+            }
+            return true;
+        case 'undefined':
+        case 'null':
+            return true;
+        default:
+            return '' == value;
+    }
+};
+
+/**
+ * Tests that a given value is not empty.
+ *
+ * A scalar value is not empty if it isn't either null, undefined or an empty
+ * string. A container is not empty if it contains at least 'minValid'
+ * nonempty values.
+ *
+ * @param   {*} value May usually be a string, an Array or an instance of qf.Map
+ * @param   {number} minValid Minimum number of nonempty values in Array or
+ *      qf.Map, defaults to 1
+ * @returns {boolean}
+ */
+qf.rules.nonempty = function(value, minValid)
+{
+    var i, valid = 0;
+
+    if ('array' == qf.typeOf(value)) {
+        for (i = 0; i < value.length; i++) {
+            if (qf.rules.nonempty(value[i], 1)) {
+                valid++;
+            }
+        }
+        return valid >= minValid;
+
+    } else if (value instanceof qf.Map) {
+        var values = value.getValues();
+        // corner case: group of checkboxes or something similar
+        if (1 == value.length()) {
+            var k = value.getKeys()[0], v = values[0];
+            if ('[]' == k.slice(-2) && 'array' == qf.typeOf(v)) {
+                return qf.rules.nonempty(v, minValid);
+            }
+        }
+        for (i = 0; i < values.length; i++) {
+            if (qf.rules.nonempty(values[i], 1)) {
+                valid++;
+            }
+        }
+        return valid >= minValid;
+
+    } else {
+        // in Javascript (null != '') is true!
+        return '' != value && 'undefined' != qf.typeOf(value) && 'null' != \
qf.typeOf(value); +    }
+};
+


Property changes on: pear/packages/HTML_QuickForm2/trunk/data/js/quickform.js
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision
Added: svn:eol-style
   + native

Deleted: pear/packages/HTML_QuickForm2/trunk/data/quickform-hierselect.js
===================================================================
(Binary files differ)

Deleted: pear/packages/HTML_QuickForm2/trunk/data/quickform.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/data/quickform.js	2011-04-26 16:49:39 UTC \
                (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/data/quickform.js	2011-04-26 18:42:03 UTC \
(rev 310525) @@ -1,1177 +0,0 @@
-/**
- * HTML_QuickForm2: JS library used for client-side validation support
- *
- * $Id$
- */
-
-/**
- * @namespace Base namespace for QuickForm, we no longer define our stuff in global \
                namespace
- */
-var qf = qf || {};
-
-/**
- * Enhanced version of typeof operator.
- *
- * Returns "null" for null values and "array" for arrays. Handles edge cases
- * like objects passed across browser windows, etc. Borrowed from closure library.
- *
- * @param   {*} value   The value to get the type of
- * @returns {string}    Type name
- */
-qf.typeOf = function(value) {
-    var s = typeof value;
-    if ('function' == s && 'undefined' == typeof value.call) {
-        return 'object';
-    } else if ('object' == s) {
-        if (!value) {
-            return 'null';
-
-        } else {
-            if (value instanceof Array
-                || (!(value instanceof Object)
-                    && '[object Array]' == Object.prototype.toString.call(value)
-                    && 'number' == typeof value.length
-                    && 'undefined' != typeof value.splice
-                    && 'undefined' != typeof value.propertyIsEnumerable
-                    && !value.propertyIsEnumerable('splice'))
-            ) {
-                return 'array';
-            }
-            if (!(value instanceof Object)
-                && ('[object Function]' == Object.prototype.toString.call(value)
-                    || 'undefined' != typeof value.call
-                    && 'undefined' != typeof value.propertyIsEnumerable
-                    && !value.propertyIsEnumerable('call'))
-            ) {
-                return 'function';
-            }
-        }
-    }
-    return s;
-};
-
-/**
- * Builds an object structure for the provided namespace path.
- *
- * Ensures that names that already exist are not overwritten. For
- * example:
- * <code>
- * "a.b.c" -> a = {};a.b={};a.b.c={};
- * </code>
- * Borrowed from closure library.
- *
- * @param   {string}    ns name of the object that this file defines.
- */
-qf.addNamespace = function(ns) {
-    var parts = ns.split('.');
-    var cur   = window;
-
-    for (var part; parts.length && (part = parts.shift());) {
-        if (cur[part]) {
-            cur = cur[part];
-        } else {
-            cur = cur[part] = {};
-        }
-    }
-};
-
-
-/**
- * Class for Hash Map datastructure.
- *
- * Used for storing container values and validation errors, mostly borrowed
- * from closure library.
- *
- * @param   {Object}    [inMap] Object or qf.Map instance to initialize the map
- * @constructor
- */
-qf.Map = function(inMap)
-{
-   /**
-    * Actual JS Object used to store the map
-    * @type {Object}
-    * @private
-    */
-    this._map   = {};
-
-   /**
-    * An array of map keys
-    * @type {String[]}
-    * @private
-    */
-    this._keys  = [];
-
-   /**
-    * Number of key-value pairs in the map
-    * @type {number}
-    * @private
-    */
-    this._count = 0;
-
-    if (inMap) {
-        this.merge(inMap);
-    }
-};
-
-/**
- * Wrapper function for hasOwnProperty
- * @param   {Object}    obj
- * @param   {*}         key
- * @returns {boolean}
- * @private
- */
-qf.Map._hasKey = function (obj, key)
-{
-    return Object.prototype.hasOwnProperty.call(obj, key);
-};
-
-/**
- * Whether the map has the given key
- * @param   {*}     key
- * @returns {boolean}
- */
-qf.Map.prototype.hasKey = function(key)
-{
-    return qf.Map._hasKey(this._map, key);
-};
-
-/**
- * Returns the number of key-value pairs in the Map
- * @returns {number}
- */
-qf.Map.prototype.length = function()
-{
-    return this._count;
-};
-
-/**
- * Returns the values of the Map
- * @returns {Array}
- */
-qf.Map.prototype.getValues = function()
-{
-    this._cleanupKeys();
-
-    var ret = [];
-    for (var i = 0; i < this._keys.length; i++) {
-        ret.push(this._map[this._keys[i]]);
-    }
-    return ret;
-};
-
-/**
- * Returns the keys of the Map
- * @returns {String[]}
- */
-qf.Map.prototype.getKeys = function()
-{
-    this._cleanupKeys();
-    return (this._keys.concat());
-};
-
-/**
- * Returns whether the Map is empty
- * @returns {boolean}
- */
-qf.Map.prototype.isEmpty = function()
-{
-    return 0 == this._count;
-};
-
-/**
- * Removes all key-value pairs from the map
- */
-qf.Map.prototype.clear = function()
-{
-    this._map         = {};
-    this._keys.length = 0;
-    this._count       = 0;
-};
-
-/**
- * Removes a key-value pair from the Map
- * @param   {*}         key The key to remove
- * @returns {boolean}   Whether the pair was removed
- */
-qf.Map.prototype.remove = function(key)
-{
-    if (!qf.Map._hasKey(this._map, key)) {
-        return false;
-    }
-
-    delete this._map[key];
-    this._count--;
-    if (this._keys.length > this._count * 2) {
-        this._cleanupKeys();
-    }
-    return true;
-};
-
-/**
- * Returns the value for the given key
- * @param   {*} key The key to look for
- * @param   {*} [defaultVal] The value to return if the key is not in the Map
- * @returns {*}
- */
-qf.Map.prototype.get = function(key, defaultVal)
-{
-    if (qf.Map._hasKey(this._map, key)) {
-        return this._map[key];
-    }
-    return defaultVal;
-};
-
-/**
- * Adds a key-value pair to the Map
- * @param {*} key
- * @param {*} value
- */
-qf.Map.prototype.set = function(key, value)
-{
-    if (!qf.Map._hasKey(this._map, key)) {
-        this._count++;
-        this._keys.push(key);
-    }
-    this._map[key] = value;
-};
-
-/**
- * Merges key-value pairs from another Object or Map
- * @param {Object} map
- * @param {function(*, *)} [mergeFn] Optional function to call on values if
- *      both maps have the same key. By default a value from the map being
- *      merged will be stored under that key.
- */
-qf.Map.prototype.merge = function(map, mergeFn)
-{
-    var keys, values, i = 0;
-    if (map instanceof qf.Map) {
-        keys   = map.getKeys();
-        values = map.getValues();
-    } else {
-        keys   = [];
-        values = [];
-        for (var key in map) {
-            keys[i]     = key;
-            values[i++] = map[key];
-        }
-    }
-
-    var fn = mergeFn || qf.Map.mergeReplace;
-
-    for (i = 0; i < keys.length; i++) {
-        if (!this.hasKey(keys[i])) {
-            this.set(keys[i], values[i]);
-        } else {
-            this.set(keys[i], fn(this.get(keys[i]), values[i]));
-        }
-    }
-};
-
-/**
- * Removes keys that are no longer in the map from the _keys array
- * @private
- */
-qf.Map.prototype._cleanupKeys = function()
-{
-    if (this._count == this._keys.length) {
-        return;
-    }
-    var srcIndex  = 0;
-    var destIndex = 0;
-    var seen      = {};
-    while (srcIndex < this._keys.length) {
-        var key = this._keys[srcIndex];
-        if (qf.Map._hasKey(this._map, key)
-            && !qf.Map._hasKey(seen, key)
-        ) {
-            this._keys[destIndex++] = key;
-            seen[key] = true;
-        }
-        srcIndex++;
-    }
-    this._keys.length = destIndex;
-};
-
-/**
- * Callback for merge(), forces to use second value.
- *
- * This makes Map.merge() behave like PHP's array_merge() function
- *
- * @param   {*} a Original value in map
- * @param   {*} b Value in the map being merged
- * @returns {*} second value
- */
-qf.Map.mergeReplace = function(a, b)
-{
-    return b;
-};
-
-/**
- * Callback for merge(), forces to use first value.
- *
- * This makes Map.merge() behave like PHP's + operator for arrays
- *
- * @param   {*} a Original value in map
- * @param   {*} b Value in the map being merged
- * @returns {*} first value
- */
-qf.Map.mergeKeep = function(a, b)
-{
-    return a;
-};
-
-/**
- * Callback for merge(), concatenates values.
- *
- * If the values are not arrays, they are first converted to ones.
- *
- * This callback makes Map.merge() behave somewhat like PHP's \
                array_merge_recursive()
- *
- * @param   {*} a Original value in map
- * @param   {*} b Value in the map being merged
- * @returns {Array} array containing both values
- */
-qf.Map.mergeArrayConcat = function(a, b)
-{
-    if ('array' != qf.typeOf(a)) {
-        a = [a];
-    }
-    if ('array' != qf.typeOf(b)) {
-        b = [b];
-    }
-    return a.concat(b);
-};
-
-
-/**
- * @name qf.form
- * @namespace Helper functions for working with form values
- */
-qf.addNamespace('qf.form');
-
-/**
- * Gets the value of select-multiple element.
- *
- * @param   {Element}   el  The element
- * @returns {String[]}
- * @private
- */
-qf.form._getSelectMultipleValue = function(el)
-{
-    var values = [];
-    for (var i = 0; i < el.options.length; i++) {
-        if (el.options[i].selected) {
-            values.push(el.options[i].value);
-        }
-    }
-    return values;
-};
-
-/**
- * Gets the value of a form element.
- *
- * @param   {string|Element} el
- * @returns {string|string[]|null}
- */
-qf.form.getValue = function(el)
-{
-    if (typeof el == 'string') {
-        el = document.getElementById(el);
-    }
-    if (!el || !('type' in el)) {
-        return null;
-    }
-    switch (el.type.toLowerCase()) {
-        case 'checkbox':
-        case 'radio':
-            return el.checked? el.value: null;
-        case 'select-one':
-            var index = el.selectedIndex;
-            return -1 == index? null: el.options[index].value;
-        case 'select-multiple':
-            return qf.form._getSelectMultipleValue(el);
-        default:
-            return (typeof el.value == 'undefined')? null: el.value;
-    }
-};
-
-/**
- * Gets the submit value of a form element. It will return null for disabled
- * elements and elements that cannot have submit values (buttons, reset controls).
- *
- * @param   {string|Element} el
- * @returns {string|string[]|null}
- */
-qf.form.getSubmitValue = function(el)
-{
-    if (typeof el == 'string') {
-        el = document.getElementById(el);
-    }
-    if (!el || (!'type' in el) || el.disabled) {
-        return null;
-    }
-    switch (el.type.toLowerCase()) {
-        case 'reset':
-        case 'button':
-            return null;
-        default:
-            return qf.form.getValue(el);
-    }
-};
-
-/**
- * Alias for qf.form.getSubmitValue
- * @type {Function}
- */
-qf.$v = qf.form.getSubmitValue;
-
-/**
- * Gets the submit values of a container.
- *
- * @param   [...] This accepts a variable number of arguments, that are either
- *      strings (considered element ID attributes), objects {name: element name,
- *      value: element value} or instances of qf.Map, representing the contained \
                elements
- * @returns qf.Map
- */
-qf.form.getContainerSubmitValue = function()
-{
-    var map = new qf.Map();
-    for (var i = 0; i < arguments.length; i++) {
-        if (arguments[i] instanceof qf.Map) {
-            map.merge(arguments[i], qf.Map.mergeArrayConcat);
-        } else {
-            if ('object' == qf.typeOf(arguments[i])) {
-                var k  = arguments[i].name;
-                var v  = arguments[i].value;
-            } else {
-                var k = document.getElementById(arguments[i]).name;
-                var v = qf.form.getSubmitValue(arguments[i]);
-            }
-            if (null !== v) {
-                var valueObj = {};
-                valueObj[k] = v;
-                map.merge(valueObj, qf.Map.mergeArrayConcat);
-            }
-        }
-    }
-    return map;
-};
-
-/**
- * Alisas for qf.form.getContainerSubmitValue
- * @type {Function}
- */
-qf.$cv = qf.form.getContainerSubmitValue;
-
-/**
- * Sets the value of a select-one element.
- * @param   {Element} el
- * @param   {String}  value
- * @private
- */
-qf.form._setSelectSingleValue = function(el, value)
-{
-    el.selectedIndex = -1;
-    for (var option, i = 0; option = el.options[i]; i++) {
-        if (option.value == value) {
-            option.selected = true;
-            return;
-        }
-    }
-};
-
-/**
- * Sets the value of a select-multiple element.
- * @param   {Element} el
- * @param   {String|String[]} value
- * @private
- */
-qf.form._setSelectMultipleValue = function(el, value)
-{
-    if ('array' != qf.typeOf(value)) {
-        value = [value];
-    }
-    for (var option, i = 0; option = el.options[i]; i++) {
-        option.selected = false;
-        for (var j = 0, l = value.length; j < l; j++) {
-            if (option.value == value[j]) {
-                option.selected = true;
-            }
-        }
-    }
-};
-
-/**
- * Sets the value of a form element.
- * @param   {String|Element} el
- * @param   {*} value
- */
-qf.form.setValue = function(el, value)
-{
-    if (typeof el == 'string') {
-        el = document.getElementById(el);
-    }
-    if (!el || !('type' in el)) {
-        return;
-    }
-    switch (el.type.toLowerCase()) {
-        case 'checkbox':
-        case 'radio':
-            el.checked = !!value;
-            break;
-        case 'select-one':
-            qf.form._setSelectSingleValue(el, value);
-            break;
-        case 'select-multiple':
-            qf.form._setSelectMultipleValue(el, value);
-            break;
-        default:
-            el.value = value;
-    }
-};
-
-/**
- * @name qf.events
- * @namespace Minor fixes to JS events to make them a bit more crossbrowser
- */
-qf.addNamespace('qf.events');
-
-/**
- * Tests for specific events support
- *
- * Code "inspired" by jQuery, original technique from here:
- * http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
- *
- * @type {Object}
- */
-qf.events.test = (function() {
-    var test = {
-        submitBubbles: true,
-        changeBubbles: true,
-        focusinBubbles: false
-    };
-    var div = document.createElement('div');
-
-    if (div.attachEvent) {
-        for (var i in {'submit': true, 'change': true, 'focusin': true}) {
-            var eventName   = 'on' + i;
-            var isSupported = (eventName in div);
-            if (!isSupported) {
-                div.setAttribute(eventName, 'return;');
-                isSupported = (typeof div[eventName] === 'function');
-            }
-            test[i + 'Bubbles'] = isSupported;
-        }
-    }
-    return test;
-})();
-
-/**
- * A na&iuml;ve wrapper around addEventListener() and attachEvent().
- *
- * QuickForm does not need a complete framework for crossbrowser event handling
- * and does not provide one. Use one of a zillion javascript libraries if you
- * need such a framework in your application.
- *
- * @param   {Element}    element
- * @param   {String}     type
- * @param   {function()} handler
- * @param   {boolean}    capture
- */
-qf.events.addListener = function(element, type, handler, capture)
-{
-    if (element.addEventListener) {
-        element.addEventListener(type, handler, capture);
-    } else {
-        element.attachEvent('on' + type, handler);
-    }
-};
-
-/**
- * A na&iuml;ve wrapper around removeEventListener() and detachEvent().
- *
- * @param   {Element}    element
- * @param   {String}     type
- * @param   {function()} handler
- * @param   {boolean}    capture
- */
-qf.events.removeListener = function(element, type, handler, capture)
-{
-    if (element.removeEventListener) {
-        element.removeEventListener(type, handler, capture);
-    } else {
-        element.detachEvent('on' + type, handler);
-    }
-};
-
-/**
- * Adds some standard fields to (IE's) event object.
- *
- * This is intended to be used in event handlers like this:
- * <code>
- * function handler(event) {
- *     event = qf.events.fixEvent(event);
- *     ...
- * }
- * </code>
- *
- * @param   {Event} [e]
- * @returns {Event}
- */
-qf.events.fixEvent = function(e)
-{
-    e = e || window.event;
-
-    e.preventDefault  = e.preventDefault || function() { this.returnValue = false; \
                };
-    e.stopPropagation = e.stopPropagation || function() { this.cancelBubble = true; \
                };
-
-    if (!e.target) {
-        e.target = e.srcElement;
-    }
-
-    if (!e.relatedTarget && e.fromElement) {
-        e.relatedTarget = e.fromElement == e.target ? e.toElement : e.fromElement;
-    }
-
-    if (e.pageX == null && e.clientX != null) {
-        var html = document.documentElement;
-        var body = document.body;
-        e.pageX = e.clientX + (html && html.scrollLeft || body && body.scrollLeft || \
                0) - (html.clientLeft || 0);
-        e.pageY = e.clientY + (html && html.scrollTop || body && body.scrollTop || \
                0) - (html.clientTop || 0);
-    }
-
-    if (!e.which && e.button) {
-        e.which = e.button & 1 ? 1 : (e.button & 2 ? 3 : (e.button & 4 ? 2 : 0));
-    }
-
-    return e;
-};
-
-
-/**
- * @name qf.classes
- * @namespace Functions for CSS classes handling
- */
-qf.addNamespace('qf.classes');
-
-/**
- * Adds a class or a list of classes to an element, without duplicating class names
- *
- * @param {Node} element            DOM node to add class(es) to
- * @param {string|string[]} name    Class name(s) to add
- */
-qf.classes.add = function(element, name)
-{
-    if ('string' == qf.typeOf(name)) {
-        name = name.split(/\\s+/);
-    }
-    if (!element.className) {
-        element.className = name.join(' ');
-    } else {
-        var checkName = ' ' + element.className + ' ',
-            newName   = element.className;
-        for (var i = 0, len = name.length; i < len; i++) {
-            if (name[i] && 0 > checkName.indexOf(' ' + name[i] + ' ')) {
-                newName += ' ' + name[i];
-            }
-        }
-        element.className = newName;
-    }
-};
-
-/**
- * Removes a class or a list of classes from an element
- *
- * @param {Node} element            DOM node to remove class(es) from
- * @param {string|string[]} name    Class name(s) to remove
- */
-qf.classes.remove = function(element, name)
-{
-    if (!element.className) {
-        return;
-    }
-    if ('string' == qf.typeOf(name)) {
-        name = name.split(/\\s+/);
-    }
-    var className = (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ');
-    for (var i = 0, len = name.length; i < len; i++) {
-        if (name[i]) {
-            className = className.replace(' ' + name[i] + ' ', ' ');
-        }
-    }
-    element.className = className.replace(/^\s+/, '').replace(/\s+$/, '');
-};
-
-/**
- * Checks whether a given element has a given class
- *
- * @param   {Node} element  DOM node to check
- * @param   {string} name   Class name to check for
- * @returns {boolean}
- */
-qf.classes.has = function(element, name)
-{
-    if (-1 < (' ' + element.className + ' ').replace(/[\n\t\r]/g, ' ').indexOf(' ' + \
                name + ' ')) {
-        return true;
-    }
-    return false;
-};
-
-
-/**
- * Form validator, attaches onsubmit handler that runs the given rules.
- *
- * @param {HTMLFormElement} form
- * @param {Array} rules
- * @constructor
- */
-qf.Validator = function(form, rules)
-{
-   /**
-    * Validation rules
-    * @type {Object[]}
-    */
-    this.rules  = rules || [];
-
-   /**
-    * Form errors, keyed by element's ID attribute
-    * @type {qf.Map}
-    */
-    this.errors = new qf.Map();
-
-    form.validator = this;
-    qf.events.addListener(form, 'submit', qf.Validator.submitHandler);
-
-    for (var i = 0, rule; rule = this.rules[i]; i++) {
-        if (typeof rule.triggers != 'undefined') {
-            if (qf.events.test.changeBubbles) {
-                qf.events.addListener(form, 'change', qf.Validator.liveHandler, \
                true);
-
-            } else {
-                // This is IE with change event not bubbling... We don't
-                // terribly need an onchange event here, only an event that
-                // fires sometime around onchange. Therefore no checks whether
-                // a value actually *changed*
-                qf.events.addListener(form, 'click', function (event) {
-                    event  = qf.events.fixEvent(event);
-                    var el = event.target;
-                    if ('select' == el.nodeName.toLowerCase()
-                        || 'input' == el.nodeName.toLowerCase()
-                         && ('checkbox' == el.type || 'radio' == el.type)
-                    ) {
-                        qf.Validator.liveHandler(event);
-                    }
-                });
-                qf.events.addListener(form, 'keydown', function (event) {
-                    event  = qf.events.fixEvent(event);
-                    var el = event.target, type = ('type' in el)? el.type: '';
-                    if ((13 == event.keyCode && 'textarea' != \
                el.nodeName.toLowerCase())
-                        || (32 == event.keyCode && ('checkbox' == type || 'radio' == \
                type))
-                        || 'select-multiple' == type
-                    ) {
-                        qf.Validator.liveHandler(event);
-                    }
-                });
-            }
-
-            if (qf.events.test.focusinBubbles) {
-                qf.events.addListener(form, 'focusout', qf.Validator.liveHandler, \
                true);
-            } else {
-                qf.events.addListener(form, 'blur', qf.Validator.liveHandler, true);
-            }
-            break;
-        }
-    }
-};
-
-/**
- * Event handler for form's onsubmit events.
- * @param {Event} event
- */
-qf.Validator.submitHandler = function(event)
-{
-    event    = qf.events.fixEvent(event);
-    var form = event.target;
-    if (form.validator && !form.validator.run(form)) {
-        event.preventDefault();
-    }
-};
-
-/**
- * Event handler for form's onblur and onchange events
- * @param {Event} event
- */
-qf.Validator.liveHandler = function (event)
-{
-    event    = qf.events.fixEvent(event);
-    var form = event.target.form;
-    if (form.validator) {
-        form.validator.runLive(event);
-    }
-};
-
-/**
- * Message prefix in alert in case of failed validation
- * @type {String}
- */
-qf.Validator.prototype.msgPrefix  = 'Invalid information entered:';
-
-/**
- * Message postfix in alert in case of failed validation
- * @type {String}
- */
-qf.Validator.prototype.msgPostfix = 'Please correct these fields.';
-
-/**
- * Called before starting the validation. May be used e.g. to clear the errors from \
                form elements.
- * @param {HTMLFormElement} form The form being validated currently
- */
-qf.Validator.prototype.onStart = function(form)
-{
-    this._clearErrors(form);
-};
-
-/**
- * Called on setting the element error
- *
- * @param {string} elementId ID attribute of an element
- * @param {string} errorMessage
- * @deprecated Use onFieldError() instead
- */
-qf.Validator.prototype.onError = function(elementId, errorMessage)
-{
-    this.onFieldError(elementId, errorMessage);
-};
-
-/**
- * Called on successfully validating the form
- * @deprecated Use onFormValid() instead
- */
-qf.Validator.prototype.onValid = function()
-{
-    this.onFormValid();
-};
-
-/**
- * Called on failed validation
- * @deprecated Use onFormError() instead
- */
-qf.Validator.prototype.onInvalid = function()
-{
-    this.onFormError();
-};
-
-/**
- * Called on setting the element error
- *
- * @param {string} elementId ID attribute of an element
- * @param {string} errorMessage
- */
-qf.Validator.prototype.onFieldError = function(elementId, errorMessage)
-{
-    var parent = this._clearValidationStatus(elementId);
-    qf.classes.add(parent, 'error');
-
-    var error = document.createElement('span');
-    error.className = 'error';
-    error.appendChild(document.createTextNode(errorMessage));
-    error.appendChild(document.createElement('br'));
-    if ('fieldset' != parent.nodeName.toLowerCase()) {
-        parent.insertBefore(error, parent.firstChild);
-    } else {
-        // error span should be inserted *after* legend, IE will render it before \
                fieldset otherwise
-        var legends = parent.getElementsByTagName('legend');
-        if (0 == legends.length) {
-            parent.insertBefore(error, parent.firstChild);
-        } else {
-            legends[legends.length - 1].parentNode.insertBefore(error, \
                legends[legends.length - 1].nextSibling);
-        }
-    }
-};
-
-/**
- * Called on successfully validating the element
- *
- * @param {string} elementId
- */
-qf.Validator.prototype.onFieldValid = function(elementId)
-{
-    var parent = this._clearValidationStatus(elementId);
-    qf.classes.add(parent, 'valid');
-};
-
-/**
- * Called on successfully validating the form
- */
-qf.Validator.prototype.onFormValid = function() {};
-
-/**
- * Called on failed validation
- */
-qf.Validator.prototype.onFormError = function()
-{
-    /*alert(this.msgPrefix + '\n - ' + this.errors.getValues().join('\n - ') + '\n' \
                + this.msgPostfix);*/
-};
-
-/**
- * Clears validation status and error message of a given element
- *
- * @param   {string} elementId
- * @returns {Node}              Parent element that gets 'error' / 'valid'
- *                              classes applied
- * @private
- */
-qf.Validator.prototype._clearValidationStatus = function(elementId)
-{
-    var el = document.getElementById(elementId), parent = el;
-    while (!qf.classes.has(parent, 'element') && 'fieldset' != \
                parent.nodeName.toLowerCase()) {
-        parent = parent.parentNode;
-    }
-    qf.classes.remove(parent, ['error', 'valid']);
-
-    this._clearErrors(parent);
-
-    return parent;
-};
-
-/**
- * Removes <span> elements with "error" class that are children of a given element
- *
- * @param   {Node} element
- * @private
- */
-qf.Validator.prototype._clearErrors = function(element)
-{
-    var spans = element.getElementsByTagName('span');
-    for (var i = 0, span; span = spans[i]; i++) {
-        if (qf.classes.has(span, 'error')) {
-            span.parentNode.removeChild(span);
-        }
-    }
-};
-
-/**
- * Removes error messages from owner element(s) of a given rule and chained rules
- *
- * @param {Array} rule
- * @private
- */
-qf.Validator.prototype._removeRelatedErrors = function(rule)
-{
-    if (this.errors.hasKey(rule.owner)) {
-        this.errors.remove(rule.owner);
-        this._clearValidationStatus(rule.owner);
-    }
-    if (typeof rule.chained != 'undefined') {
-        for (var i = 0; i < rule.chained.length; i++) {
-            for (var j = 0; j < rule.chained[i].length; j++) {
-                this._removeRelatedErrors(rule.chained[i][j]);
-            }
-        }
-    }
-};
-
-
-/**
- * Performs validation using the stored rules
- * @param   {HTMLFormElement} form    The form being validated
- * @returns {boolean}
- */
-qf.Validator.prototype.run = function(form)
-{
-    this.onStart(form);
-
-    this.errors.clear();
-    for (var i = 0, rule; rule = this.rules[i]; i++) {
-        if (this.errors.hasKey(rule.owner)) {
-            continue;
-        }
-        this.validate(rule);
-    }
-
-    if (this.errors.isEmpty()) {
-        this.onFormValid();
-        return true;
-
-    } else {
-        this.onFormError();
-        return false;
-    }
-};
-
-/**
- * Performs live validation of an element and related ones
- *
- * @param {Event} event     Event triggering the validation
- */
-qf.Validator.prototype.runLive = function(event)
-{
-    var testId   = ' ' + event.target.id + ' ',
-        ruleHash = new qf.Map(),
-        length   = -1;
-
-    // first: find all rules "related" to the given element, clear their error \
                messages
-    while (ruleHash.length() > length) {
-        length = ruleHash.length();
-        for (var i = 0, rule; rule = this.rules[i]; i++) {
-            if (typeof rule.triggers == 'undefined' || ruleHash.hasKey(i)) {
-                continue;
-            }
-            for (var j = 0, trigger; trigger = rule.triggers[j]; j++) {
-                if (-1 < testId.indexOf(' ' + trigger + ' ')) {
-                    ruleHash.set(i, true);
-                    this._removeRelatedErrors(rule);
-                    testId += rule.triggers.join(' ') + ' ';
-                    break;
-                }
-            }
-        }
-    }
-
-    // second: run all "related" rules
-    for (i = 0; rule = this.rules[i]; i++) {
-        if (!ruleHash.hasKey(i) || this.errors.hasKey(rule.owner)) {
-            continue;
-        }
-        this.validate(rule);
-    }
-};
-
-/**
- * Performs validation, sets the element's error if validation fails.
- *
- * @param   {Object} rule Validation rule, maybe with chained rules
- * @returns {boolean}
- */
-qf.Validator.prototype.validate = function(rule)
-{
-    var globalValid, localValid = rule.callback.call(this);
-
-    if (typeof rule.chained == 'undefined') {
-        globalValid = localValid;
-
-    } else {
-        globalValid = false;
-        for (var i = 0; i < rule.chained.length; i++) {
-            for (var j = 0; j < rule.chained[i].length; j++) {
-                localValid = localValid && this.validate(rule.chained[i][j]);
-                if (!localValid) {
-                    break;
-                }
-            }
-            globalValid = globalValid || localValid;
-            if (globalValid) {
-                break;
-            }
-            localValid = true;
-        }
-    }
-
-    if (!globalValid && rule.message && !this.errors.hasKey(rule.owner)) {
-        this.errors.set(rule.owner, rule.message);
-        this.onFieldError(rule.owner, rule.message);
-    } else if (!this.errors.hasKey(rule.owner)) {
-        this.onFieldValid(rule.owner);
-    }
-
-    return globalValid;
-};
-
-/**
- * @name qf.rules
- * @namespace Client-side implementations of Rules that are a bit too complex to \
                inline
- */
-qf.addNamespace('qf.rules');
-
-/**
- * Returns true if all the given callbacks return true, false otherwise.
- *
- * Client-side implementation of HTML_QuickForm2_Rule_Each, consult PHPDoc
- * description there.
- *
- * @param   {function()[]} callbacks
- * @returns {boolean}
- */
-qf.rules.each = function(callbacks)
-{
-    for (var i = 0; i < callbacks.length; i++) {
-        if (!callbacks[i]()) {
-            return false;
-        }
-    }
-    return true;
-};
-
-/**
- * Tests that a given value is empty.
- *
- * A scalar value is empty if it either null, undefined or an empty string. An
- * array is empty if it contains only empty values.
- *
- * @param   {*} value
- * @returns {boolean}
- */
-qf.rules.empty = function(value)
-{
-    switch (qf.typeOf(value)) {
-        case 'array':
-            for (var i = 0; i < value.length; i++) {
-                if (!qf.rules.empty(value[i])) {
-                    return false;
-                }
-            }
-            return true;
-        case 'undefined':
-        case 'null':
-            return true;
-        default:
-            return '' == value;
-    }
-};
-
-/**
- * Tests that a given value is not empty.
- *
- * A scalar value is not empty if it isn't either null, undefined or an empty
- * string. A container is not empty if it contains at least 'minValid'
- * nonempty values.
- *
- * @param   {*} value May usually be a string, an Array or an instance of qf.Map
- * @param   {number} minValid Minimum number of nonempty values in Array or
- *      qf.Map, defaults to 1
- * @returns {boolean}
- */
-qf.rules.nonempty = function(value, minValid)
-{
-    var i, valid = 0;
-
-    if ('array' == qf.typeOf(value)) {
-        for (i = 0; i < value.length; i++) {
-            if (qf.rules.nonempty(value[i], 1)) {
-                valid++;
-            }
-        }
-        return valid >= minValid;
-
-    } else if (value instanceof qf.Map) {
-        var values = value.getValues();
-        // corner case: group of checkboxes or something similar
-        if (1 == value.length()) {
-            var k = value.getKeys()[0], v = values[0];
-            if ('[]' == k.slice(-2) && 'array' == qf.typeOf(v)) {
-                return qf.rules.nonempty(v, minValid);
-            }
-        }
-        for (i = 0; i < values.length; i++) {
-            if (qf.rules.nonempty(values[i], 1)) {
-                valid++;
-            }
-        }
-        return valid >= minValid;
-
-    } else {
-        // in Javascript (null != '') is true!
-        return '' != value && 'undefined' != qf.typeOf(value) && 'null' != \
                qf.typeOf(value);
-    }
-};

Modified: pear/packages/HTML_QuickForm2/trunk/docs/examples/js/dualselect.js
===================================================================
(Binary files differ)

Modified: pear/packages/HTML_QuickForm2/trunk/js/src/validator.js
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/js/src/validator.js	2011-04-26 16:49:39 UTC \
                (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/js/src/validator.js	2011-04-26 18:42:03 UTC \
(rev 310525) @@ -1,17 +1,77 @@
 /* $Id$ */

 /**
- * Form validator, attaches onsubmit handler that runs the given rules.
+ * Client-side rule object
  *
+ * @param {function()} callback
+ * @param {string}     owner
+ * @param {string}     message
+ * @param {Array}      chained
+ * @constructor
+ */
+qf.Rule = function(callback, owner, message, chained)
+{
+   /**
+    * Function performing actual validation
+    * @type {function()}
+    */
+    this.callback = callback;
+
+   /**
+    * ID of owner element
+    * @type {string}
+    */
+    this.owner    = owner;
+
+   /**
+    * Error message to set if validation fails
+    * @type {string}
+    */
+    this.message  = message;
+
+   /**
+    * Chained rules
+    * @type {Array}
+    */
+    this.chained  = chained || [[]];
+};
+
+/**
+ * Client-side rule object that should run onblur / onchange
+ *
+ * @param {function()} callback
+ * @param {string}     owner
+ * @param {string}     message
+ * @param {string[]}   triggers
+ * @param {Array}      chained
+ * @constructor
+ */
+qf.LiveRule = function(callback, owner, message, triggers, chained)
+{
+    qf.Rule.call(this, callback, owner, message, chained);
+
+   /**
+    * IDs of elements that should trigger validation
+    * @type {string[]}
+    */
+    this.triggers = triggers;
+};
+
+qf.LiveRule.prototype = new qf.Rule();
+qf.LiveRule.prototype.constructor = qf.LiveRule;
+
+/**
+ * Form validator, attaches handlers that run the given rules.
+ *
  * @param {HTMLFormElement} form
- * @param {Array} rules
+ * @param {qf.Rule[]} rules
  * @constructor
  */
 qf.Validator = function(form, rules)
 {
    /**
     * Validation rules
-    * @type {Object[]}
+    * @type {qf.Rule[]}
     */
     this.rules  = rules || [];

@@ -25,7 +85,7 @@
     qf.events.addListener(form, 'submit', qf.Validator.submitHandler);

     for (var i = 0, rule; rule = this.rules[i]; i++) {
-        if (typeof rule.triggers != 'undefined') {
+        if (rule instanceof qf.LiveRule) {
             if (qf.events.test.changeBubbles) {
                 qf.events.addListener(form, 'change', qf.Validator.liveHandler, \
true);

@@ -133,7 +193,8 @@
     /**
      * Removes error messages from owner element(s) of a given rule and chained \
                rules
      *
-     * @param {Array} rule
+     * @param {qf.Map} errors
+     * @param {qf.Rule} rule
      * @private
      */
     function _removeRelatedErrors(errors, rule)
@@ -142,11 +203,9 @@
             errors.remove(rule.owner);
             _clearValidationStatus(rule.owner);
         }
-        if (typeof rule.chained != 'undefined') {
-            for (var i = 0; i < rule.chained.length; i++) {
-                for (var j = 0; j < rule.chained[i].length; j++) {
-                    _removeRelatedErrors(errors, rule.chained[i][j]);
-                }
+        for (var i = 0, item; item = rule.chained[i]; i++) {
+            for (var j = 0, multiplier; multiplier = item[j]; j++) {
+                _removeRelatedErrors(errors, multiplier);
             }
         }
     };
@@ -297,7 +356,7 @@
             while (ruleHash.length() > length) {
                 length = ruleHash.length();
                 for (var i = 0, rule; rule = this.rules[i]; i++) {
-                    if (typeof rule.triggers == 'undefined' || ruleHash.hasKey(i)) {
+                    if (!rule instanceof qf.LiveRule || ruleHash.hasKey(i)) {
                         continue;
                     }
                     for (var j = 0, trigger; trigger = rule.triggers[j]; j++) {
@@ -323,31 +382,25 @@
         /**
          * Performs validation, sets the element's error if validation fails.
          *
-         * @param   {Object} rule Validation rule, maybe with chained rules
+         * @param   {qf.Rule} rule Validation rule, maybe with chained rules
          * @returns {boolean}
          */
         validate: function(rule)
         {
-            var globalValid, localValid = rule.callback.call(this);
+            var globalValid = false, localValid = rule.callback.call(this);

-            if (typeof rule.chained == 'undefined') {
-                globalValid = localValid;
-
-            } else {
-                globalValid = false;
-                for (var i = 0; i < rule.chained.length; i++) {
-                    for (var j = 0; j < rule.chained[i].length; j++) {
-                        localValid = localValid && \
                this.validate(rule.chained[i][j]);
-                        if (!localValid) {
-                            break;
-                        }
-                    }
-                    globalValid = globalValid || localValid;
-                    if (globalValid) {
+            for (var i = 0, item; item = rule.chained[i]; i++) {
+                for (var j = 0, multiplier; multiplier = item[j]; j++) {
+                    localValid = localValid && this.validate(multiplier);
+                    if (!localValid) {
                         break;
                     }
-                    localValid = true;
                 }
+                globalValid = globalValid || localValid;
+                if (globalValid) {
+                    break;
+                }
+                localValid = true;
             }

             if (!globalValid && rule.message && !this.errors.hasKey(rule.owner)) {

Modified: pear/packages/HTML_QuickForm2/trunk/package.xml
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/package.xml	2011-04-26 16:49:39 UTC (rev \
                310524)
+++ pear/packages/HTML_QuickForm2/trunk/package.xml	2011-04-26 18:42:03 UTC (rev \
310525) @@ -541,9 +541,23 @@
      <tasks:replace from="@data_dir@" to="data_dir" type="pear-config" />
     </file>
    </dir> <!-- /docs/examples -->
+   <dir name="data/js">
+    <dir name="min">
+     <file name="quickform.js" role="data">
+      <tasks:replace from="@package_version@" to="version" type="package-info" />
+     </file>
+     <file name="quickform-hierselect.js" role="data">
+      <tasks:replace from="@package_version@" to="version" type="package-info" />
+     </file>
+    </dir> <!-- /data/js/min -->
+    <file name="quickform.js" role="data">
+     <tasks:replace from="@package_version@" to="version" type="package-info" />
+    </file>
+    <file name="quickform-hierselect.js" role="data">
+     <tasks:replace from="@package_version@" to="version" type="package-info" />
+    </file>
+   </dir> <!-- /data/js -->
    <file name="data/quickform.css" role="data" />
-   <file name="data/quickform.js" role="data" />
-   <file name="data/quickform-hierselect.js" role="data" />
   </dir>
  </contents>
  <dependencies>
@@ -564,8 +578,10 @@
  <phprelease>
   <filelist>
    <install as="quickform.css" name="data/quickform.css" />
-   <install as="quickform.js" name="data/quickform.js" />
-   <install as="quickform-hierselect.js" name="data/quickform-hierselect.js" />
+   <install as="js/quickform.js" name="data/js/quickform.js" />
+   <install as="js/quickform-hierselect.js" name="data/js/quickform-hierselect.js" \
/> +   <install as="js/min/quickform.js" name="data/js/min/quickform.js" />
+   <install as="js/min/quickform-hierselect.js" \
name="data/js/min/quickform-hierselect.js" />  <install \
as="examples/controller/simple.php" name="docs/examples/controller/simple.php" />  \
<install as="examples/controller/tabbed.php" \
name="docs/examples/controller/tabbed.php" />  <install \
as="examples/controller/wizard.php" name="docs/examples/controller/wizard.php" />

Modified: pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/CompareTest.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/CompareTest.php	2011-04-26 \
                16:49:39 UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/CompareTest.php	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -213,7 +213,7 @@
             array('bar', array('id' => 'bar'))
         );
         $compare = new HTML_QuickForm2_Rule_Compare($foo, '...', $bar);
-        $this->assertContains('triggers: ["foo","bar"]', $compare->getJavascript());
+        $this->assertContains('["foo","bar"]', $compare->getJavascript());
     }
 }
 ?>

Modified: pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/EachTest.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/EachTest.php	2011-04-26 \
                16:49:39 UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/EachTest.php	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -228,7 +228,7 @@
         $rule->expects($this->any())->method('getJavascriptCallback')
              ->will($this->returnValue('a callback'));
         $each = new HTML_QuickForm2_Rule_Each($mockContainer, 'an error', $rule);
-        $this->assertContains('triggers: ["foo","bar"]', $each->getJavascript());
+        $this->assertContains('["foo","bar"]', $each->getJavascript());
     }
 }
 ?>
\ No newline at end of file

Modified: pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/NonemptyTest.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/NonemptyTest.php	2011-04-26 \
                16:49:39 UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/Rule/NonemptyTest.php	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -190,7 +190,7 @@
         $bar = $mockContainer->addElement('text', 'bar', array('id' => 'bar'));

         $nonEmpty = new HTML_QuickForm2_Rule_Nonempty($mockContainer, 'an error');
-        $this->assertContains('triggers: ["foo","bar"]', \
$nonEmpty->getJavascript()); +        $this->assertContains('["foo","bar"]', \
$nonEmpty->getJavascript());  }
 }
 ?>
\ No newline at end of file

Modified: pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/RuleTest.php
===================================================================
--- pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/RuleTest.php	2011-04-26 \
                16:49:39 UTC (rev 310524)
+++ pear/packages/HTML_QuickForm2/trunk/tests/QuickForm2/RuleTest.php	2011-04-26 \
18:42:03 UTC (rev 310525) @@ -214,11 +214,12 @@
             'HTML_QuickForm2_Rule', array('validateOwner', 'getJavascriptCallback'),
             array($el)
         );
-        $rule->expects($this->exactly(2))->method('getJavascriptCallback')
+        $rule->expects($this->any())->method('getJavascriptCallback')
              ->will($this->returnValue('a callback'));

-        $this->assertContains('triggers: ["foo"]', $rule->getJavascript());
-        $this->assertNotContains('triggers:', $rule->getJavascript(false));
+        $this->assertContains('qf.LiveRule', $rule->getJavascript());
+        $this->assertContains('["foo"]', $rule->getJavascript());
+        $this->assertNotContains('qf.LiveRule', $rule->getJavascript(false));
     }

     public function testChainedValidationTriggers()
@@ -247,10 +248,10 @@
                 ->will($this->returnValue('a callback'));

         $script = $ruleFoo->and_($ruleBar->and_($ruleBaz))->getJavascript();
-        preg_match('/triggers: \[(.+?)\]/', $script, $m);
-        $this->assertContains('foo', $m[1]);
-        $this->assertContains('bar', $m[1]);
-        $this->assertContains('baz', $m[1]);
+        preg_match('/\[\S+\]/', $script, $m);
+        $this->assertContains('foo', $m[0]);
+        $this->assertContains('bar', $m[0]);
+        $this->assertContains('baz', $m[0]);
     }
 }
 ?>



-- 
PEAR CVS Mailing List (http://pear.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic