Refactor and improve the auto-complete foreign-key support in the insert form
authorGuillaume (ioguix) de Rorthais <ioguix@free.fr>
Thu, 1 Oct 2009 23:26:01 +0000 (01:26 +0200)
committerGuillaume (ioguix) de Rorthais <ioguix@free.fr>
Thu, 1 Oct 2009 23:26:01 +0000 (01:26 +0200)
- clean the code
- add support for multi-colmn foreign key
- show all the table column in the choice list
- add pagination in the list to browse prev/next 11 values
- use of jquery lib

aciur.js [deleted file]
ajax-ac-insert.php [new file with mode: 0644]
autocomplete.php [deleted file]
classes/database/Postgres.php
classes/database/Postgres73.php
classes/database/Postgres81.php
js/ac_insert_row.js [new file with mode: 0644]
lang/english.php
lang/recoded/english.php
tables.php
themes/default/global.css

diff --git a/aciur.js b/aciur.js
deleted file mode 100644 (file)
index 8ce0d5a..0000000
--- a/aciur.js
+++ /dev/null
@@ -1,400 +0,0 @@
-var nkeycode = 0;
-var curopt = 0;
-var bnsr = false;
-var aSuggests = [];
-var otxb = null;
-var xo = null;
-var acv = false;
-var fac_c = 1;
-var c_fac_c = 1;
-var asg = new Array();
-var rasg = new Array();
-var rasgc = new Array();
-var iMoR = false;
-var g_c_ns = "";
-var g_c_tb = "";
-var g_c_fk = "";
-var g_c_sid = "";
-var g_c_db = "";
-var g_c_v = "";
-var g_i_ac = true;
-
-document.body.onclick=function() {deAC();};
-
-function aS(aSuggests) {
-    if (aSuggests.length > 0) {
-        tA(aSuggests[curopt]);
-    }
-}
-
-function hKU(oEvent) {
-       nkeycode = oEvent.keyCode;
-       var iKeyCode = oEvent.keyCode;
-       if(iKeyCode!=13) {
-               if(iKeyCode==40 && (curopt+1)<=(rasg.length-1)) {
-                       curopt++;
-                       bnsr = true;
-                       tA(rasg[curopt]);
-                       htr(document.getElementById('option_tr_'+(curopt+1)));
-               } else if(iKeyCode==38 && ((curopt-1)<=(rasg.length-1) && (curopt-1)>=0)) {
-                       curopt--;
-                       bnsr = true;
-                       tA(rasg[curopt]);
-                       htr(document.getElementById('option_tr_'+(curopt+1)));
-               } else {
-                       if ( (iKeyCode!=8 && iKeyCode <= 32) || (iKeyCode >= 33 && iKeyCode <= 46) || (iKeyCode >= 112 && iKeyCode <= 123)) {
-                       } else {
-                               var sTextboxValue = otxb.value.toLowerCase();
-                               SG1(sTextboxValue);                             
-                               bnsr = false;
-                       }
-               }
-       }
-       else {
-               if(acv) {
-                       hideAC();
-               }
-               return false;
-       }
-}
-
-function initac(ns,tb,fk,sid,db,v) {
-    g_c_ns = ns;
-    g_c_tb = tb;
-    g_c_fk = fk;
-    g_c_sid = sid;
-    g_c_db = db;
-}
-
-function sR(p_start,p_len) {
-if(!bnsr) {
-    if (otxb.createTextRange) {
-        var oRange = otxb.createTextRange(); 
-        oRange.moveStart("character", p_start); 
-        oRange.moveEnd("character", p_len - otxb.value.length);
-        oRange.select();
-    } else if (otxb.setSelectionRange) {
-        otxb.setSelectionRange(p_start, p_len);
-    }
-} else {
-       if (otxb.createTextRange) {
-        var oRange = otxb.createTextRange();
-        oRange.moveStart("character",p_len);
-        oRange.moveEnd("character",p_len);
-        oRange.select();
-       } 
-               else if (otxb.setSelectionRange) {
-                otxb.setSelectionRange(p_len,p_len);
-               }
-       }
-    otxb.focus();
-}
-
-function tA(p_suggestion) {
-       if(nkeycode!=8) {
-               if ((otxb.createTextRange || otxb.setSelectionRange) && p_suggestion) {
-                       var p_len = otxb.value.length; 
-                       otxb.value = p_suggestion; 
-                       sR(p_len, p_suggestion.length);
-               }
-       }
-}
-
-function findPosX(obj) {
-       if(obj) {
-               var curleft = 0;
-               if (obj.offsetParent) {
-                       while (obj.offsetParent)
-                       {
-                               curleft += obj.offsetLeft
-                               obj = obj.offsetParent;
-                       }
-               }
-               else if (obj.x)
-                       curleft += obj.x;
-               return curleft;
-       }
-}
-
-function findPosY(obj) {
-       if(obj) {
-               var curtop = 0;
-               var n = 0;
-               if (obj.y) {
-                       curtop += obj.y;
-               }
-               else if (obj.offsetParent) {
-                       while (obj.offsetParent) {
-                               curtop += obj.offsetTop;
-                               obj = obj.offsetParent;
-                       }
-               }
-               return curtop;
-       }
-}
-
-function hideAC() {
-               var d = document.getElementById('ac');
-               d.innerHTML='';
-               d.style.zIndex=0;
-               d.style.border=0;
-               d.style.visibility='hidden';
-               acv = false;
-}
-
-function deAC() {
-       if(acv) {
-               hideAC();
-       }
-}
-
-function makenormaltr(id) {
-       var tr = document.getElementById(id);
-       tr.style.backgroundColor='white';tr.style.color='black';
-}
-
-function fillinval(val) {
-       var tx = document.getElementById('fac' + c_fac_c);
-       tx.value=val;
-       hideAC();
-}
-
-function showAC() {
-       var d = document.getElementById('ac');
-       d.innerHTML='';
-       var tb = document.getElementById('fac'+c_fac_c+'_ph');
-       d.style.top=findPosY(tb)+'px';
-       d.style.visibility='visible';
-       acv = true;
-       d.style.left=findPosX(tb)+'px';
-       d.style.border='1px solid black';
-       d.style.position='absolute';
-       d.style.width=(document.getElementById('aciwp' + c_fac_c).offsetWidth-3) + "px";
-       d.style.zIndex=20;
-       d.style.backgroundColor='white';
-       d.style.margin='0px';
-       d.style.padding='0px';
-       d.style.textAlign='left';
-       document.getElementById("ac_form").onsubmit=function(){return false;};
-       return d;
-}
-
-function aftr() {
-       for(i=0;i<rasg.length;i++) {
-               ftr(document.getElementById('option_tr_'+(i+1)));
-       }
-}
-
-function htr(obj) {
-       aftr();
-       obj.style.backgroundColor='#3d80df';
-       obj.style.color='white';
-}
-
-function ftr(obj) {
-       obj.style.backgroundColor='white';
-       obj.style.color='black'; 
-}
-
-function buildSelectOptions() {
-       if(rasg.length>0) {
-       var t = document.createElement('table');
-       var tb = document.createElement('tbody');
-       var d = showAC();
-       t.style.width='100%';
-       t.style.margin='0px';
-       t.style.padding='0px';
-       t.cellPadding='0px';
-       t.cellSpacing='0px';
-       t.style.zIndex=6;
-       t.style.visibility='visible';
-       for(i=0;i<rasg.length;i++) {
-               var tr = document.createElement('tr');
-               var td = document.createElement('td');
-               var tx = document.createTextNode(rasg[i]);
-               if(i==0) {
-                       td.style.color='white';
-                       td.id='option_tr_1';
-                       td.style.backgroundColor='#3d80df';
-               } else {
-                       td.id='option_tr_' + (i+1);
-                       td.style.color='black';
-                       td.style.backgroundColor='white';
-               }
-               td.onmouseover=function() { htr(this); this.style.cursor='pointer'; };
-               td.onmouseout=function() { ftr(this); this.style.cursor='normal'; };
-               td.onclick=function() { fillinval(this.firstChild.innerHTML); };
-               td.style.paddingLeft='10px';
-               td.style.fontSize='9pt';
-               td.style.fontFamily='Arial';
-               td.appendChild(tx);
-               tr.appendChild(td);
-               tb.appendChild(tr);
-       }
-       t.appendChild(tb);
-       d.appendChild(t);
-       } else {
-               hideAC();
-       }
-}
-
-function dF(v) {
-       g_i_ac = document.getElementById(v).checked;
-}
-
-function makeAC(tx,n,ns,tb,fk,sid,db) {
-       otxb = document.getElementById(tx);
-       c_fac_c = n;
-       if(document.getElementById('no_ac').checked) {
-               if(acv) {
-                       hideAC();
-               }
-               otxb = document.getElementById(tx);
-               v = otxb.value;
-               curopt = 0;
-               bnsr = false;
-               otxb.onkeyup = function (oEvent) {
-                       if (!oEvent) { oEvent = window.event; } hKU(oEvent); 
-               };
-               initac(ns, tb,fk,sid,db,v);
-       } else {
-               otxb.onkeyup = function() {};
-       }
-}
-
-function gfacc(s) {
-       return s.substr(3);
-}
-
-function iA(parent, node, referenceNode) {
-    parent.insertBefore(node, referenceNode.nextSibling);
-}
-
-var asg = new Array();
-var rasg = new Array();
-var rasgc = new Array();
-
-function rS(tx) {
-    rasg = [];
-    rasgc = [];
-    var sTextboxValue = otxb.value.toLowerCase(); 
-    if (sTextboxValue.length > 0) {
-        for (var i=0;i<asg.length;i++) { 
-       var c = asg[i];
-            if (c.indexOf(sTextboxValue) == 0) {
-                rasg.push(asg[i]);
-            }
-        }
-       buildSelectOptions();
-    } else {
-               hideAC();
-       }
-}
-
-function SG1(s) {
-       if(!iMoR && s.length>0) {
-               pr("autocomplete.php","ns="+g_c_ns+"&tb="+g_c_tb+"&database="+g_c_db+"&server="+g_c_sid+"&fk="+g_c_fk+"&v="+escapeHTML(s)+"");
-       } else if(!s.length) {
-               hideAC();
-       }
-}
-
-function escapeHTML(s) {
-       s = escape(s);
-       s = s.replace(/\//g,"%2F");
-       s = s.replace(/\?/g,"%3F");
-       s = s.replace(/=/g,"%3D");
-       s = s.replace(/&/g,"%26");
-       s = s.replace(/@/g,"%40");
-       return s;
-}
-
-function rEB(v) {
-       var il = document.getElementsByTagName('input');
-       for(i in il) {
-               if(il[i].className=='ac_field' || il[i].className=='normal_field') {
-                       if(v==false) {
-                               il[i].className='normal_field';
-                               il[i].setAttribute("autocomplete",'on');
-                       } else {
-                               il[i].className='ac_field';
-                               il[i].setAttribute("autocomplete",'off');
-                       }
-               }
-       }
-}
-
-function pgsg(v) {
-       prgsp(v);
-}
-
-function prgsp(v) {
-       var d = v.split("PPA_EOF;|"); // pondered using RegExp here, but overhead to gain ratio is not convincing
-       var l = d.length;
-       asg = [];
-       for(i=0;i<l;i++) {
-               asg.push(d[i]);
-       }
-       rS(otxb);
-       aS(rasg);
-}
-
-function rS(tx) {
-    rasg = [];
-    rasgc = [];
-    var sTextboxValue = otxb.value.toLowerCase(); 
-    if (sTextboxValue.length > 0) {
-        for (var i=0;i<asg.length;i++) { 
-       var c = asg[i];
-            if (c.indexOf(sTextboxValue) == 0) {
-                rasg.push(asg[i]);
-            }
-        }
-       buildSelectOptions();
-    } else {
-               hideAC();
-       }
-}
-
-function pr(pg,sr) {
-        mii();
-        xo.open("POST",pg,true);
-        xo.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-        xo.onreadystatechange=xoc;
-        xo.send(sr);
-}
-
-function mii() {
-        if(!xo) {
-                /*@cc_on @*//*@if (@_jscript_version >= 5) try {
-                xo = new ActiveXObject("Msxml2.XMLHTTP");
-                }
-                catch (e) {
-                try {
-                xo = new ActiveXObject("Microsoft.XMLHTTP");
-                }
-                catch (E) {
-                xo = false;
-                }
-                }
-                @end @*/if (!xo && typeof XMLHttpRequest!='undefined') {
-                xo = new XMLHttpRequest();
-                }
-        }
-}
-
-function xoc(){
-        iMoR = true;
-        szr = "";
-        if (xo.readyState==4) {
-                if(xo.status==200) {
-                       pgsg(xo.responseText);
-                        iMoR = false;
-                }
-                else {
-                        alert("Oops, something unexpected happened, try again");
-                        window.location.reload();
-                }
-        }
-}
-
diff --git a/ajax-ac-insert.php b/ajax-ac-insert.php
new file mode 100644 (file)
index 0000000..ee33868
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+       include_once('./libraries/lib.inc.php');
+
+       if(isset($_POST['offset']))
+               $offset = " OFFSET {$_POST['offset']}";
+       else {
+               $_POST['offset'] = 0;
+               $offset = " OFFSET 0";
+       }
+       $keyspos = array_combine($_POST['fkeynames'], $_POST['keys']);
+       $keysnames = array_combine($_POST['fkeynames'], $_POST['keynames']);
+
+       $q = "SELECT *
+               FROM \"{$_POST['f_schema']}\".\"{$_POST['f_table']}\"
+               WHERE \"{$_POST['fkeynames'][$_POST['fattpos']]}\"::text LIKE '{$_POST['fvalue']}%'
+               ORDER BY \"{$_POST['fkeynames'][$_POST['fattpos']]}\" LIMIT 12 {$offset};";
+
+       $res = $data->selectSet($q);
+
+       if (!$res->EOF) {
+               echo "<table class=\"ac_values\">";
+               echo '<tr>';
+               foreach (array_keys($res->fields) as $h) {
+                       echo '<th>';
+
+                       if (in_array($h,$_POST['fkeynames']))
+                               echo '<img src="'. $misc->icon('ForeignKey') .'" alt="[referenced key]" />';
+
+                       echo htmlentities($h), '</th>';
+                       
+               }
+               echo "</tr>\n";
+               $i=0;
+               while ((!$res->EOF) && ($i < 11)) {
+                       echo "<tr class=\"acline\">";
+                       foreach ($res->fields as $n => $v) {
+                               if (in_array($n,$_POST['fkeynames']))
+                                       echo "<td><a href=\"javascript:void(0)\" class=\"fkval\" name=\"{$keyspos[$n]}\">",htmlentities($v), "</a></td>";
+                               else
+                                       echo "<td><a href=\"javascript:void(0)\">", htmlentities($v), "</a></td>";
+                       }
+                       echo "</tr>\n";
+                       $i++;
+                       $res->moveNext();
+               }               
+               echo "</table>\n";
+
+               $page_tests='';
+
+               $js = "<script type=\"text/javascript\">\n";
+               
+               if ($_POST['offset']) {
+                       echo "<a href=\"javascript:void(0)\" id=\"fkprev\">&lt;&lt; Prev</a>";
+                       $js.= "fkl_hasprev=true;\n";
+               }
+               else
+                       $js.= "fkl_hasprev=false;\n";
+
+               if ($res->recordCount() == 12) {
+                       $js.= "fkl_hasnext=true;\n";
+                       echo "&nbsp;&nbsp;&nbsp;<a href=\"javascript:void(0)\" id=\"fknext\">Next &gt;&gt;</a>";
+               }
+               else
+                       $js.= "fkl_hasnext=false;\n";
+               
+               echo $js ."</script>";
+       }
+       else {
+               printf("<p>{$lang['strnofkref']}</p>", "\"{$_POST['f_schema']}\".\"{$_POST['f_table']}\".\"{$_POST['fkeynames'][$_POST['fattpos']]}\"");
+
+               if ($_POST['offset'])
+                       echo "<a href=\"javascript:void(0)\" class=\"fkprev\">Prev &lt;&lt;</a>";
+       }
+?>
diff --git a/autocomplete.php b/autocomplete.php
deleted file mode 100644 (file)
index 5a662df..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-       include_once('libraries/lib.inc.php');
-       $data->clean($_REQUEST['tb']);
-       $data->clean($_REQUEST['ns']);
-       $data->clean($_REQUEST['fk']);
-       $data->clean($_REQUEST['v']);
-
-       // FIXME: At some point this should be schema qualified
-       $szSQL = 'SELECT * FROM "' . $_REQUEST['ns'] . '"."' . $_REQUEST['tb'] . '" WHERE "' . $_REQUEST['fk']
-               . "\"::text LIKE '" . $_REQUEST['v'] . "%' ORDER BY \"". $_REQUEST['fk'] .'" LIMIT 11';
-
-       $objRes = $data->selectSet($szSQL);
-       $arrayRes = array();
-       while (!$objRes->EOF) {
-               $arrayRes[] = $objRes->fields[$_REQUEST['fk']];
-               $objRes->moveNext();
-       }
-
-       echo implode('PPA_EOF;|', $arrayRes);
-?>
index 07066d812b08b9a8b145920da09d7761dfddc35b..e7c884116963dd24eefcc7cfe9e7fc42948129f4 100755 (executable)
@@ -250,8 +250,9 @@ class Postgres extends ADODB_base {
         * @param $value The value of the field.  Note this could be 'numeric(7,2)' sort of thing...
         * @param $type The database type of the field
         * @param $actions An array of javascript action name to the code to execute on that action
+        * @param $extra Some extra attributes to add.
         */
-       function printField($name, $value, $type, $actions = array(),$szExtra="") {
+       function printField($name, $value, $type, $actions = array(), $extra='') {
                global $lang;
 
                // Determine actions string
@@ -276,7 +277,7 @@ class Postgres extends ADODB_base {
                                        echo "</select>\n";
                                }
                                else {
-                                       echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$action_str} {$szExtra} />\n";
+                                       echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$action_str} {$extra} />\n";
                                }
                                break;
                        case 'bytea':
@@ -303,7 +304,7 @@ class Postgres extends ADODB_base {
                                echo "</textarea>\n";
                                break;
                        default:
-                               echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$action_str} {$szExtra} />\n";
+                               echo "<input name=\"", htmlspecialchars($name), "\" value=\"", htmlspecialchars($value), "\" size=\"35\"{$action_str} {$extra} />\n";
                                break;
                }
        }
@@ -1163,7 +1164,6 @@ class Postgres extends ADODB_base {
                                                AND pc.relkind='S'
                                        ) IS NOT NULL AS attisserial,
                                        pg_catalog.col_description(a.attrelid, a.attnum) AS comment
-
                                FROM
                                        pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef
                                        ON a.attrelid=adef.adrelid
@@ -3268,11 +3268,10 @@ class Postgres extends ADODB_base {
 
                // get the max number of col used in a constraint for the table
                $sql = "SELECT DISTINCT
-                               max(SUBSTRING(array_dims(c.conkey) FROM  E'^\\\[.*:(.*)\\\]$')) as nb
-               FROM
-                     pg_catalog.pg_constraint AS c
-                 JOIN pg_catalog.pg_class AS r ON (c.conrelid = r.oid)
-                     JOIN pg_catalog.pg_namespace AS ns ON r.relnamespace=ns.oid
+                       max(SUBSTRING(array_dims(c.conkey) FROM  E'^\\\[.*:(.*)\\\]$')) as nb
+               FROM pg_catalog.pg_constraint AS c
+                       JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid)
+                       JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid)
                WHERE
                        r.relname = '$table' AND ns.nspname='". $this->_schema ."'";
 
@@ -3283,10 +3282,11 @@ class Postgres extends ADODB_base {
 
                $sql = '
                        SELECT
-                               c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc,
+                               c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc,
                                ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema,
-                               r2.relname as f_table, f1.attname as p_field, f2.attname as f_field,
-                               pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment
+                               r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field,
+                               f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
+                               c.conrelid, c.confrelid
                        FROM
                                pg_catalog.pg_constraint AS c
                                JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
index 87315b97f038be6ab78fd830e13a24331d93abd5..1ebc89b45d0f7432d217a7840a5f04d9a2f7cf6b 100644 (file)
@@ -426,8 +426,8 @@ class Postgres73 extends Postgres74 {
        }
 
                $sql = "
-               SELECT contype, conname, consrc, ns1.nspname as p_schema, sub.relname as p_table,
-                       f_schema, f_table, p_field, f_field, indkey
+               SELECT oid AS conid, contype, conname, consrc, ns1.nspname as p_schema, sub.relname as p_table,
+                       f_schema, f_table, p_field, f_field, indkey, confrelid
                FROM (
                        SELECT
                        contype, conname,
@@ -436,8 +436,8 @@ class Postgres73 extends Postgres74 {
                        ELSE
                                'CHECK (' || consrc || ')'
                        END AS consrc, r1.relname,
-                       f1.attname as p_field, ns2.nspname as f_schema, r2.relname as f_table,
-                       conrelid, r1.relnamespace, f2.attname as f_field, NULL AS indkey
+                       f1.attname as p_field, f1.attnum AS p_attnum, ns2.nspname as f_schema, r2.relname as f_table,
+                       conrelid, r1.relnamespace, f2.attname as f_field, f2.attnum AS f_attnum, NULL AS indkey
                  FROM
                        pg_catalog.pg_constraint AS c
                        JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
index f8479a3f2b4de7b354a6cef7bf585d622c02c01e..d387592f2669437ecbdb48bd5986ab641a11ee0c 100644 (file)
@@ -167,10 +167,12 @@ class Postgres81 extends Postgres82 {
 
                $sql = '
                        SELECT
-                               c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid,true) AS consrc,
+                               c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid,true) AS consrc,
                                ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema,
-                               r2.relname as f_table, f1.attname as p_field, f2.attname as f_field,
-                               pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment
+                               r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum,
+                               f2.attname as f_field, f2.attnum AS f_attnum,
+                               pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment,
+                               confrelid
                        FROM
                                pg_catalog.pg_constraint AS c
                                JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid)
diff --git a/js/ac_insert_row.js b/js/ac_insert_row.js
new file mode 100644 (file)
index 0000000..ae062a3
--- /dev/null
@@ -0,0 +1,219 @@
+var fkl_hasnext=false;
+var fkl_hasprev=false;
+
+/* hide the value list */
+function hideAc() {
+       jQuery.ppa.o=0;
+       with (jQuery.ppa) {
+               fklist.hide();
+               fkbg.hide();
+       }
+}
+
+/* enable/disable auto-complete feature */
+function triggerAc(ac) {
+       if (ac) {
+               jQuery.ppa.attrs
+                       .keyup(autocomplete)
+                       .keypress(move)
+                       .addClass('ac_field');
+       }
+       else {
+               jQuery.ppa.attrs
+                       .removeClass('ac_field')
+                       .unbind('keyup',autocomplete)
+                       .unbind('keypress',move);
+       }
+}
+
+/* select the given index value and highlight it */
+function selectVal(index) {
+       if (index == jQuery.ppa.i)
+               return;
+
+       // we catch the header as well so it takes th index 0
+       var trs = jQuery.ppa.fklist.find('tr');
+
+       // change colors for unselected
+       if (jQuery.ppa.i > 0)
+               trs.eq(jQuery.ppa.i).find('*').css({
+                       'background-color': '#fff',
+                       'color': ''
+               });
+               
+       // change colors for newly selected
+       trs.eq(index).find('*').css({
+               'background-color': '#3d80df',
+               'color': '#fff'
+       });
+
+       jQuery.ppa.i = index;
+}
+
+function openlist(e) {
+       var elt = jQuery(e);
+       var attnum = elt.attr('id').match(/\d+/)[0];
+       var conid = attrs['attr_'+attnum];
+
+       var constr = constrs["constr_" + conid];
+
+       // get the changed attribute position in the arrays 
+       for (i=0; (constr.pattnums[i] != attnum);i++);
+       
+       var datas = {
+               fattpos: i,
+               fvalue: e.value,
+               database: database,
+               'keys[]': constr.pattnums,
+               'keynames[]': constr.pattnames,
+               'fkeynames[]': constr.fattnames,
+               f_table: constr.f_table,
+               f_schema: constr.f_schema,
+               offset: jQuery.ppa.o
+       };
+
+       jQuery.ajax({
+               url: 'ajax-ac-insert.php?server=' + server,
+               type: 'post',
+               data: datas,
+               success: function (ret) {
+                       jQuery.ppa.i = 0;
+                       jQuery.ppa.fkbg.show();
+                       with(jQuery.ppa.fklist) {
+                               html(ret);
+                               appendTo('#row_att_'+ attnum);
+                               css('width',elt.css('width'));
+                               show();
+                               jQuery.ppa.numrow = find('tr').length;
+                       }
+               }
+       });
+}
+
+
+/* move the cursor down or up,
+ * load available next/prev values if going out of bound */
+function move(event) {
+       /* selecting next value down.
+        * if the list is closed, it will open next */
+       if(event.keyCode == 40) {
+               if (jQuery.ppa.fklist[0].style.display == 'block')  {
+                       if ((jQuery.ppa.i + 1) < jQuery.ppa.numrow) {
+                               selectVal(jQuery.ppa.i + 1);
+                       }
+                       else if (fkl_hasnext == true) {
+                               jQuery.ppa.o+=11;
+                               openlist(this);
+                       }
+               }
+               else {
+                       openlist(this);
+               }
+       }
+       /* selecting prev value up */
+       else if(event.keyCode == 38) {
+               if ((jQuery.ppa.i - 1) > 0) {
+                       selectVal(jQuery.ppa.i - 1);
+               }
+               else if ((fkl_hasprev == true) && (jQuery.ppa.i == 1)) {
+                       jQuery.ppa.o-=11;
+                       openlist(this);
+               }
+               else {
+                       selectVal(jQuery.ppa.numrow -1);
+               }
+       }
+}
+
+/* open/update the value list on keyup event */
+function autocomplete(event) {
+
+       /* if pressing enter, fire a click on the selected line */
+       if (event.keyCode == 13) {
+               if (jQuery.ppa.i > 0) {
+                       jQuery.ppa.fklist.find('tr').eq(jQuery.ppa.i).click();
+               }
+               hideAc();
+               return false;
+       }
+       /* ignoring 38:up and 40:down */
+       else if ( event.keyCode == 38 || event.keyCode == 40 ) {
+               return false;
+       }
+       /* ignoring 9:tab, 37:left, 39:right, 16:shift, ctrl: 17, alt:18, 20:lockmaj */
+       else if ( event.keyCode == 9 || event.keyCode == 37     || event.keyCode == 39
+       || event.keyCode == 16 || event.keyCode == 17
+       || event.keyCode == 18 || event.keyCode == 20) {
+               return true;
+       }
+       /* esc */
+       else if (event.keyCode == 27) {
+               hideAc();
+       }
+       /* request the list of possible values asynchronously */
+       else {
+               openlist(this);
+       }
+
+       return true;
+}
+
+/* bind actions on values lines: hover for style change, click for select */
+with(jQuery('tr.acline')) {
+       live('mouseover', function () {
+               selectVal(jQuery('table.ac_values tr').index(this));
+       });
+
+       live('click', function () {
+               var a = jQuery(this).find('td > a.fkval');
+
+               for (i=0; i < a.length; i++) {
+                       jQuery('input[name=values\\['+ a[i].name +'\\]]').val(a[i].innerHTML);
+               }
+               hideAc();
+       });
+}
+
+jQuery('#fkprev').live('click', function () {
+       jQuery.ppa.o-=11;
+       /* get the field that is the previous html elt from the #fklist
+        * and trigger its keyup to refresh the list */
+       jQuery('#fklist').prev().keyup().focus();
+});
+
+jQuery('#fknext').live('click', function () {
+       jQuery.ppa.o+=11;
+       /* get the field that is the previous html elt from the #fklist
+        * and trigger its keyup to refresh the list */
+       jQuery('#fklist').prev().keyup().focus();
+});
+
+jQuery(document).ready(function () {
+       /* register some global value in the ppa namespace */
+       jQuery.ppa = {
+               fklist: jQuery('#fklist'),
+               attrs: jQuery('input[id^=attr_]'),
+               fkbg: jQuery('#fkbg'),
+               i:0, // selected value indice
+               o:0 // offset when navigating prev/next
+       };
+
+       /* close the list when clicking outside of it */
+       jQuery.ppa.fkbg.click(function (e) {
+               hideAc();
+       });
+
+       /* do not submit the form when selecting a value by pressing enter */
+       jQuery.ppa.attrs
+               .keydown(function (e) {
+                       if (e.keyCode == 13 && jQuery.ppa.fklist[0].style.display == 'block')
+                               return false;
+               });
+       
+       /* enable/disable auto-complete according to the checkbox */
+       triggerAc(
+               jQuery('#no_ac').click(function () {
+                       triggerAc(this.checked);
+               })[0].checked
+       );
+});
index 66c6704562c1faa850c64ce5ffd48f157d09fbf2..d1834e4fcf76c706c49347038f1526c7b7b93181 100644 (file)
        $lang['strinsertrow'] = 'Insert row';
        $lang['strrowinserted'] = 'Row inserted.';
        $lang['strrowinsertedbad'] = 'Row insert failed.';
+       $lang['strnofkref'] = 'There is no matching value in the foreign key %s.';
        $lang['strrowduplicate'] = 'Row insert failed, attempted to do duplicate insert.';
        $lang['streditrow'] = 'Edit row';
        $lang['strrowupdated'] = 'Row updated.';
index 4ebe06b40fab2d8ea74132e92075f11f84670c5a..c231e2de039b95bc3b251fe5d5a6e43449e03780 100644 (file)
        $lang['strinsertrow'] = 'Insert row';
        $lang['strrowinserted'] = 'Row inserted.';
        $lang['strrowinsertedbad'] = 'Row insert failed.';
+       $lang['strnofkref'] = 'There is no matching value for this foreign key in %s.';
        $lang['strrowduplicate'] = 'Row insert failed, attempted to do duplicate insert.';
        $lang['streditrow'] = 'Edit row';
        $lang['strrowupdated'] = 'Row updated.';
index 313c484f13474c067abf4e96b9404867c6990501..23a034716f57ddc5fe9ad9aad45b70b721cb8da2 100644 (file)
                global $data, $misc, $conf;
                global $lang;
 
-               $bAllowAC = (($conf['autocomplete'] != 'disable') ? TRUE : FALSE);
+               $auto_complete = ($conf['autocomplete'] != 'disable');
 
                if ($confirm) {
                        $misc->printTrail('table');
                        $misc->printMsg($msg);
 
                        $attrs = $data->getTableAttributes($_REQUEST['table']);
-                       if($bAllowAC) {
-                               $constraints = $data->getConstraintsWithFields($_REQUEST['table']);
-
-                               $arrayLocals = array();
-                               $arrayRefs = array();
-                               $nC = 0;
-                               while(!$constraints->EOF) {
-                                       // FIXME: add a better support for FKs on multi columns
-                                       if ($constraints->fields['contype'] == 'f') {
-                                               $arrayLocals[$nC] = $constraints->fields['p_field'];
-                                               $arrayRefs[$nC] = array($constraints->fields['f_schema'], $constraints->fields['f_table'], $constraints->fields['f_field']);
-                                               $nC++;
+                       $fksprops = array(
+                               'byconstr' => array(),
+                               'byfield' => array(),
+                       );
+                       if($auto_complete) {
+                               $constrs = $data->getConstraintsWithFields($_REQUEST['table']);
+
+                               if (!$constrs->EOF) {
+                                       $conrelid = $constrs->fields['conrelid'];
+                                       while(!$constrs->EOF) {
+                                               if ($constrs->fields['contype'] == 'f') {
+                                                       if (!isset($fksprops['byconstr'][$constrs->fields['conid']])) {
+                                                               $fksprops['byconstr'][$constrs->fields['conid']] = array (
+                                                                       'confrelid' => $constrs->fields['confrelid'],
+                                                                       'f_table' => $constrs->fields['f_table'],
+                                                                       'f_schema' => $constrs->fields['f_schema'],
+                                                                       'pattnums' => array(),
+                                                                       'pattnames' => array(),
+                                                                       'fattnames' => array()
+                                                               );
+                                                       }
+
+                                                       $fksprops['byconstr'][$constrs->fields['conid']]['pattnums'][] = $constrs->fields['p_attnum'];
+                                                       $fksprops['byconstr'][$constrs->fields['conid']]['pattnames'][] = $constrs->fields['p_field'];
+                                                       $fksprops['byconstr'][$constrs->fields['conid']]['fattnames'][] = $constrs->fields['f_field'];
+
+                                                       if (!isset($fksprops['byfield'][$constrs->fields['p_attnum']]))
+                                                               $fksprops['byfield'][$constrs->fields['p_attnum']] = array();
+                                                       $fksprops['byfield'][$constrs->fields['p_attnum']] = $constrs->fields['conid'];
+                                               }
+                                               $constrs->moveNext();
+                                       }
+                                       
+                                       echo "<script type=\"text/javascript\">\n";
+                                       echo "var constrs = {};\n";
+                                       foreach ($fksprops['byconstr'] as $conid => $props) {
+                                               echo "constrs.constr_{$conid} = {\n";
+                                               echo 'pattnums: [', implode(',',$props['pattnums']), "],\n";
+                                               echo "f_table:\"", htmlentities($props['f_table']), "\",\n";
+                                               echo "f_schema:\"", htmlentities($props['f_schema']), "\",\n";
+                                               $_='';
+                                               foreach ($props['pattnames'] as $n) {
+                                                       $_.= ",'". htmlentities($n, ENT_QUOTES) ."'";
+                                               }
+                                               echo 'pattnames: [', substr($_, 1), "],\n";
+
+                                               $_='';
+                                               foreach ($props['fattnames'] as $n) {
+                                                       $_.= ",'". htmlentities($n, ENT_QUOTES) ."'";
+                                               }
+
+                                               echo 'fattnames: [', substr($_, 1), "]\n";
+                                               echo "};\n";
+                                       }
+
+                                       echo "var attrs = {};\n";
+                                       foreach ($fksprops['byfield'] as $attnum => $cstrs ) {
+                                               echo "attrs.attr_{$attnum} = {$fksprops['byfield'][$attnum]};\n";
                                        }
-                                       $constraints->moveNext();
+
+                                       echo "var table='", htmlentities($_REQUEST['table']), "';";
+                                       echo "var server='", htmlentities($_REQUEST['server']), "';";
+                                       echo "var database='", htmlentities($_REQUEST['database']), "';";
+                                       echo "</script>\n";
+
+                                       echo '<div id="fkbg"></div>';
+                                       echo '<div id="fklist"></div>';
                                }
+                               else $auto_complete = false;
                        }
 
                        echo "<form action=\"tables.php\" method=\"post\" id=\"ac_form\">\n";
                                $fields = array();
                                while (!$attrs->EOF) {
                                        $fields[$attrs->fields['attnum']] = $attrs->fields['attname'];
-                                       $szValueName = "values[{$attrs->fields['attnum']}]";
-                                       $szEvents = '';
-                                       $szDivPH = '';
-                                       if($bAllowAC) {
-                                               $idxFound = array_search($attrs->fields['attname'], $arrayLocals);
-                                               // In PHP < 4.2.0 array_search returns NULL on failure
-                                               if ($idxFound !== NULL && $idxFound !== FALSE) {
-                                                       $szEvent = "makeAC('{$szValueName}',{$i},'{$arrayRefs[$idxFound][0]}','{$arrayRefs[$idxFound][1]}','{$arrayRefs[$idxFound][2]}','{$_REQUEST['server']}','{$_REQUEST['database']}');";
-                                                       $szEvents = "onfocus=\"{$szEvent}\" onblur=\"hideAC();document.getElementById('ac_form').onsubmit=function(){return true;};\" onchange=\"{$szEvent}\" id=\"{$szValueName}\" onkeyup=\"{$szEvent}\" autocomplete=\"off\" class='ac_field'";
-                                                       $szDivPH = "<div id=\"fac{$i}_ph\"></div>";
-                                               }
-                                       }
                                        $attrs->fields['attnotnull'] = $data->phpBool($attrs->fields['attnotnull']);
                                        // Set up default value if there isn't one already
                                        if (!isset($_REQUEST['values'][$attrs->fields['attnum']]))
                                        echo "<td class=\"data{$id}\" style=\"white-space:nowrap;\">", $misc->printVal($attrs->fields['attname']), "</td>";
                                        echo "<td class=\"data{$id}\" style=\"white-space:nowrap;\">\n";
                                        echo $misc->printVal($data->formatType($attrs->fields['type'], $attrs->fields['atttypmod']));
-                                       echo "<input type=\"hidden\" name=\"types[", htmlspecialchars($attrs->fields['attnum']), "]\" value=\"",
+                                       echo "<input type=\"hidden\" name=\"types[{$attrs->fields['attnum']}]\" value=\"",
                                                htmlspecialchars($attrs->fields['type']), "\" /></td>";
                                        echo "<td class=\"data{$id}\" style=\"white-space:nowrap;\">\n";
-                                       echo "<select name=\"format[", htmlspecialchars($attrs->fields['attnum']), "]\">\n";
+                                       echo "<select name=\"format[{$attrs->fields['attnum']}]\">\n";
                                        echo "<option value=\"VALUE\"", ($_REQUEST['format'][$attrs->fields['attnum']] == 'VALUE') ? ' selected="selected"' : '', ">{$lang['strvalue']}</option>\n";
                                        echo "<option value=\"EXPRESSION\"", ($_REQUEST['format'][$attrs->fields['attnum']] == 'EXPRESSION') ? ' selected="selected"' : '', ">{$lang['strexpression']}</option>\n";
                                        echo "</select>\n</td>\n";
                                        echo "<td class=\"data{$id}\" style=\"white-space:nowrap;\">";
                                        // Output null box if the column allows nulls (doesn't look at CHECKs or ASSERTIONS)
                                        if (!$attrs->fields['attnotnull']) {
-                                               echo "<input type=\"checkbox\" name=\"nulls[", htmlspecialchars($attrs->fields['attnum']), "]\"",
+                                               echo "<input type=\"checkbox\" name=\"nulls[{$attrs->fields['attnum']}]\"",
                                                        isset($_REQUEST['nulls'][$attrs->fields['attnum']]) ? ' checked="checked"' : '', " /></td>";
                                        }
                                        else {
                                                echo "&nbsp;</td>";
                                        }
-                                       echo "<td class=\"data{$id}\" id=\"aciwp{$i}\" style=\"white-space:nowrap;\">", $data->printField($szValueName,
-                                       $_REQUEST['values'][$attrs->fields['attnum']], $attrs->fields['type'],array(),$szEvents),$szDivPH ,"</td>";
+                                       echo "<td class=\"data{$id}\" id=\"row_att_{$attrs->fields['attnum']}\" style=\"white-space:nowrap;\">";
+                                       if ($auto_complete && isset($fksprops['byfield'][$attrs->fields['attnum']])) {
+                                               echo $data->printField("values[{$attrs->fields['attnum']}]", $_REQUEST['values'][$attrs->fields['attnum']],
+                                                       'fktype'/*force FK*/,array(), "id=\"attr_{$attrs->fields['attnum']}\" autocomplete=\"off\"");
+                                       }
+                                       else {
+                                               echo $data->printField("values[{$attrs->fields['attnum']}]", $_REQUEST['values'][$attrs->fields['attnum']],
+                                                       $attrs->fields['type'],array());
+                                       }
+                                       echo "</td>\n";
                                        echo "</tr>\n";
                                        $i++;
                                        $attrs->moveNext();
                                }
                                echo "</table>\n";
-                               if($bAllowAC) {
-                                       echo '<script src="aciur.js" type="text/javascript"></script>';
-                                       echo "<div id=\"ac\"></div>";
+                               if($auto_complete) {
+                                       echo "<script src=\"libraries/js/jquery.js\" type=\"text/javascript\"></script>";
+                                       echo "<script src=\"js/ac_insert_row.js\" type=\"text/javascript\"></script>";
                                }
 
                                if (!isset($_SESSION['counter'])) { $_SESSION['counter'] = 0; }
                                echo "<p><input type=\"submit\" name=\"insert\" value=\"{$lang['strinsert']}\" />\n";
                                echo "<input type=\"submit\" name=\"insertandrepeat\" value=\"{$lang['strinsertandrepeat']}\" />\n";
                                echo "<input type=\"submit\" name=\"cancel\" value=\"{$lang['strcancel']}\" />\n";
-                               if($bAllowAC) {
-                                       $szChecked = $conf['autocomplete'] != 'default off' ? 'checked="checked"' : '';
-                                       echo "<input type=\"checkbox\" name=\"no_ac\" id=\"no_ac\" onclick=\"rEB(this.checked);\" value=\"1\" {$szChecked} /><label for='no_ac' onmouseover='this.style.cursor=\"pointer\";'>{$lang['strac']}</label>\n";
+                               
+                               if($auto_complete) {
+                                       if ($conf['autocomplete'] != 'default off')
+                                               echo "<input type=\"checkbox\" id=\"no_ac\" value=\"1\" checked=\"checked\" /><label for=\"no_ac\">{$lang['strac']}</label>\n";
+                                       else
+                                               echo "<input type=\"checkbox\" id=\"no_ac\" value=\"0\" /><label for=\"no_ac\">{$lang['strac']}</label>\n";
                                }
                                echo "</p>\n";
-                               echo "<script type=\"text/javascript\">rEB(document.getElementById('no_ac').checked);</script>";
                        }
                        else { 
                                echo "<p>{$lang['strnofieldsforinsert']}</p>\n";
index 485222394525959aa73665a3406ac3feaa93da6d..1584a9df733808f303d26126ac32d142d3c2b012 100644 (file)
@@ -5,7 +5,6 @@
  */
 
 /** ELEMENTS */
-
 body, td
 {
        background-color: #FFFFFF;
@@ -275,7 +274,6 @@ table.tabs {
 .tab .icon {
        display: block;
 }
-
 a:active 
 {
        color: #989973;
@@ -411,13 +409,44 @@ pre.data
 .arg_tr_pc {
 }
 
-.ac_field {
-border:1px solid #D9D95F;
+#fkbg {
+       display:none;
+       position:fixed;
+       top:0;left:0;
+       width:100%;
+       height:100%;
+       z-index:10;
+}
+#fklist {
+       display:none;
+       position:absolute;
+       background:#fff;
+       border:1px solid #000;
+       overflow:auto;
+       z-index:15;
+}
+#fklist table {
+       border-collapse:collapse;
+       border: 1px solid #aaa;
+}
+#fklist th {border: 1px solid #aaa}
+#fklist td,
+#fklist th {
+       padding: 3px 10px;
+       border-right: 1px solid #aaa;
+       font-size: 12px;
+}
+#fklist td a {
+       display:block;
+       color:#000;
+}
+#fklist td a.fkval {
+       color:red;
 }
-
 .normal_field {
 }
-
 .pre {
-white-space:pre;
-}
\ No newline at end of file
+       white-space:pre;
+}
+.ac_values {width:100%}
+.ac_field {border:1px solid #D9D95F}
\ No newline at end of file