diff options
author | Leonardo Sápiras | 2010-08-26 23:47:53 +0000 |
---|---|---|
committer | ioguix | 2010-08-26 23:47:53 +0000 |
commit | ffed251517cce0de23d9c60362d0223cab0ee76e (patch) | |
tree | 6c7f5e1e5829166a83b8095e83cefa8b5126f8b6 | |
parent | 0ed9c429c038307e7bc57dea7cc5bb6c59a36489 (diff) |
Add feature: Browsing on Foreign Keys, in both pure xhtml and ajax
done during GSoC 2010, with mentoring and some help from ioguix.
-rw-r--r-- | CREDITS | 3 | ||||
-rw-r--r-- | HISTORY | 5 | ||||
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | display.php | 255 | ||||
-rw-r--r-- | js/display.js | 78 | ||||
-rw-r--r-- | themes/default/global.css | 22 |
6 files changed, 319 insertions, 46 deletions
@@ -69,6 +69,7 @@ Contributors - Tomasz Pala - Ivan Zolotukhin - Kristoffer `spq` Janke +- Leonardo Augusto Sapiras (Improve phpPgAdmin ergonomy during the GSoC 2010, with JGuillaume 'ioguix' De Rorthais as mentor) Third Party Libraries @@ -80,4 +81,4 @@ Corporate Sponsors - SpikeSource (www.spikesource.com) - Slony support - Google Summer of Code (http://code.google.com/soc/2006/pgsql/appinfo.html?csaid=DB096D908B948D89) - phpPgAdmin Improvements - Google Summer of Code (http://code.google.com/soc/2007/postgres/appinfo.html?csaid=E89B3D5E2DC4170A) - Full Text Search in PostgreSQL GUI Tools - +- Google Summer of Code (http://code.google.com/p/google-summer-of-code-2010-postgresql/we_dont_have_the_complete_link_yet) - Improve phpPgAdmin ergonomy @@ -19,13 +19,16 @@ Features * Add ability to create indexes concurrently * Allow user to logicaly group their server under custom named node in the browser tree * New theme and a theme switcher on the introduction page +* Auto refresh Locks page +* Auto refresh Processes page +* Link in the bottom of the page to go to top of page +* Browsing on Foreign Keys (When browsing a table, clicking on a FK value, jump to the PK row) Bugs * Fix problems with query tracking on overly long queries * Ensure pg_dump paths are valid * Fix problems with table names containing quotes * Fix autocompletion support for cross-schema objects -* Fix several problems for object names containing html charecters Translations * Czech (Marek Cernocky) @@ -88,8 +88,6 @@ Tables * Allow PK and UNIQUE and FKs during create table (Jawed) * When adding a column or creating a table, prevent size on non-size types (eg. integer(2)). You can find these by looking at format_type in the postgresql source code. -* When browsing a table, clicking on a FK value should jump to the - PK row. (ioguix) * Add WITH storage_parameter option to create table [8.2] * Add last vacuum and analyze information from statistics tables [8.2] * Restrict operators (from $selectOps array) to appropriate types (ie. no LIKE for int4 fields) diff --git a/display.php b/display.php index c16fe01b..f47c1b4f 100644 --- a/display.php +++ b/display.php @@ -246,6 +246,175 @@ } } + + /* build & return the FK information data structure + * used when deciding if a field should have a FK link or not*/ + function &getFKInfo() { + global $data, $misc, $lang; + + // Get the foreign key(s) information from the current table + $fkey_information = array('byconstr' => array(), 'byfield' => array()); + + if (isset($_REQUEST['table'])) { + $constraints = $data->getConstraintsWithFields($_REQUEST['table']); + if ($constraints->recordCount() > 0) { + + /* build the common parts of the url for the FK */ + $fk_return_url = "{$misc->href}&subject=table&table=". urlencode($_REQUEST['table']); + if (isset($_REQUEST['page'])) $fk_return_url .= "&page=" . urlencode($_REQUEST['page']); + if (isset($_REQUEST['query'])) $fk_return_url .= "&query=" . urlencode($_REQUEST['query']); + if (isset($_REQUEST['search_path'])) $fk_return_url .= "&search_path=" . urlencode($_REQUEST['search_path']); + + /* yes, we double urlencode fk_return_url so parameters here don't + * overwrite real one when included in the final url */ + $fkey_information['common_url'] = $misc->getHREF('schema') .'&subject=table&return_url=display.php?' + . urlencode($fk_return_url) .'&return_desc='. urlencode($lang['strback']); + + /* build the FK constraints data structure */ + while (!$constraints->EOF) { + $constr =& $constraints->fields; + if ($constr['contype'] == 'f') { + + if (!isset($fkey_information['byconstr'][$constr['conid']])) { + $fkey_information['byconstr'][$constr['conid']] = array ( + 'url_data' => 'table='. urlencode($constr['f_table']) .'&schema='. urlencode($constr['f_schema']), + 'fkeys' => array(), + 'consrc' => $constr['consrc'] + ); + } + + $fkey_information['byconstr'][$constr['conid']]['fkeys'][$constr['p_field']] = $constr['f_field']; + + if (!isset($fkey_information['byfield'][$constr['p_field']])) + $fkey_information['byfield'][$constr['p_field']] = array(); + + $fkey_information['byfield'][$constr['p_field']][] = $constr['conid']; + } + $constraints->moveNext(); + } + } + } + + return $fkey_information; + } + + /* Print table header cells + * @param $sortLink must be urlencoded already + * */ + function printTableHeaderCells(&$rs, $sortLink, $withOid) { + global $misc, $data, $conf; + $j = 0; + + foreach ($rs->fields as $k => $v) { + + if (($k == $data->id) && ( !($withOid && $conf['show_oids']) )) { + $j++; + continue; + } + $finfo = $rs->fetchField($j); + + if ($sortLink === false) { + echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n"; + } + else { + echo "<th class=\"data\"><a href=\"display.php?{$sortLink}&sortkey=", ($j + 1), "&sortdir="; + // Sort direction opposite to current direction, unless it's currently '' + echo ($_REQUEST['sortdir'] == 'asc' && $_REQUEST['sortkey'] == ($j + 1)) ? 'desc' : 'asc'; + echo "&strings=", urlencode($_REQUEST['strings']), + "&page=" . urlencode($_REQUEST['page']), "\">", + $misc->printVal($finfo->name), "</a></th>\n"; + } + $j++; + } + + reset($rs->fields); + } + + /* Print data-row cells */ + function printTableRowCells(&$rs, &$fkey_information, $withOid) { + global $data, $misc, $conf; + $j = 0; + + if (!isset($_REQUEST['strings'])) $_REQUEST['strings'] = 'collapsed'; + + foreach ($rs->fields as $k => $v) { + $finfo = $rs->fetchField($j++); + + if (($k == $data->id) && ( !($withOid && $conf['show_oids']) )) continue; + elseif ($v !== null && $v == '') echo "<td> </td>"; + else { + echo "<td style=\"white-space:nowrap;\">"; + + if (($v !== null) && isset($fkey_information['byfield'][$k])) { + foreach ($fkey_information['byfield'][$k] as $conid) { + + $query_params = $fkey_information['byconstr'][$conid]['url_data']; + + foreach ($fkey_information['byconstr'][$conid]['fkeys'] as $p_field => $f_field) { + $query_params .= '&'. urlencode("fkey[{$f_field}]") .'='. urlencode($rs->fields[$p_field]); + } + + /* $fkey_information['common_url'] is already urlencoded */ + $query_params .= '&'. $fkey_information['common_url']; + echo "<div style=\"display:inline-block;\">"; + echo "<a class=\"fk fk_". htmlentities($conid) ."\" href=\"display.php?{$query_params}\">"; + echo "<img src=\"".$misc->icon('ForeignKey')."\" style=\"vertical-align:middle;\" alt=\"[fk]\" title=\"" + . htmlentities($fkey_information['byconstr'][$conid]['consrc']) + ."\" />"; + echo "</a>"; + echo "</div>"; + } + echo $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed'), 'class' => 'fk_value')); + } else { + echo $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed'))); + } + echo "</td>"; + } + } + } + + /* Print the FK row, used in ajax requests */ + function doBrowseFK() { + global $data, $misc, $lang; + + $ops = array(); + foreach($_REQUEST['fkey'] as $x => $y) { + $ops[$x] = '='; + } + $query = $data->getSelectSQL($_REQUEST['table'], array(), $_REQUEST['fkey'], $ops); + $_REQUEST['query'] = $query; + + $fkinfo =& getFKInfo(); + + $max_pages = 1; + // Retrieve page from query. $max_pages is returned by reference. + $rs = $data->browseQuery('SELECT', $_REQUEST['table'], $_REQUEST['query'], + null, null, 1, 1, $max_pages); + + echo "<a href=\"\" style=\"display:table-cell;\" class=\"fk_delete\"><img alt=\"[delete]\" src=\"". $misc->icon('Delete') ."\" /></a>\n"; + echo "<div style=\"display:table-cell;\">"; + + if (is_object($rs) && $rs->recordCount() > 0) { + /* we are browsing a referenced table here + * we should show OID if show_oids is true + * so we give true to withOid in functions bellow + * as 3rd paramter */ + + echo "<table><tr>"; + printTableHeaderCells($rs, false, true); + echo "</tr>"; + echo "<tr class=\"data1\">\n"; + printTableRowCells($rs, $fkinfo, true); + echo "</tr>\n"; + echo "</table>\n"; + } + else + echo $lang['strnodata']; + + echo "</div>"; + + exit; + } /** * Displays requested data @@ -269,6 +438,16 @@ } $misc->printTrail(isset($subject) ? $subject : 'database'); + + /* This code is used when browsing FK in pure-xHTML (without js) */ + if (isset($_REQUEST['fkey'])) { + $ops = array(); + foreach($_REQUEST['fkey'] as $x => $y) { + $ops[$x] = '='; + } + $query = $data->getSelectSQL($_REQUEST['table'], array(), $_REQUEST['fkey'], $ops); + $_REQUEST['query'] = $query; + } if (isset($object)) { if (isset($_REQUEST['query'])) { @@ -314,7 +493,9 @@ isset($_REQUEST['query']) ? $_REQUEST['query'] : null, $_REQUEST['sortkey'], $_REQUEST['sortdir'], $_REQUEST['page'], $conf['max_rows'], $max_pages); - + + $fkey_information =& getFKInfo(); + // Build strings for GETs $gets = $misc->href; if (isset($object)) $gets .= "&" . urlencode($subject) . '=' . urlencode($object); @@ -336,8 +517,9 @@ if (is_object($rs) && $rs->recordCount() > 0) { // Show page navigation $misc->printPages($_REQUEST['page'], $max_pages, "display.php?page=%s&{$gets}&{$getsort}&nohistory=t&strings=" . urlencode($_REQUEST['strings'])); - echo "<table>\n<tr>"; - + + echo "<table id=\"data\">\n<tr>"; + // Check that the key is actually in the result set. This can occur for select // operations where the key fields aren't part of the select. XXX: We should // be able to support this, somehow. @@ -353,31 +535,11 @@ if (sizeof($key) > 0) echo "<th colspan=\"2\" class=\"data\">{$lang['stractions']}</th>\n"; - $j = 0; - foreach ($rs->fields as $k => $v) { - if (isset($object) && $k == $data->id && !$conf['show_oids']) { - $j++; - continue; - } - $finfo = $rs->fetchField($j); - // Display column headers with sorting options, unless we're PostgreSQL - // 7.0 and it's a non-TABLE mode - if ($type != 'TABLE') { - echo "<th class=\"data\">", $misc->printVal($finfo->name), "</th>\n"; - } - else { - echo "<th class=\"data\"><a href=\"display.php?{$gets}&sortkey=", ($j + 1), "&sortdir="; - // Sort direction opposite to current direction, unless it's currently '' - echo ($_REQUEST['sortdir'] == 'asc' && $_REQUEST['sortkey'] == ($j + 1)) ? 'desc' : 'asc'; - echo "&strings=", urlencode($_REQUEST['strings']), - "&page=" . urlencode($_REQUEST['page']), "\">", - $misc->printVal($finfo->name), "</a></th>\n"; - } - $j++; - } - + /* we show OIDs only if we are in TABLE or SELECT type browsing */ + printTableHeaderCells($rs, $gets, isset($object)); + echo "</tr>\n"; - + $i = 0; reset($rs->fields); while (!$rs->EOF) { @@ -406,21 +568,15 @@ urlencode($_REQUEST['page']), "&{$key_str}&{$gets}&{$getsort}\">{$lang['strdelete']}</a></td>\n"; } } - $j = 0; - foreach ($rs->fields as $k => $v) { - $finfo = $rs->fetchField($j++); - if (isset($_REQUEST['table']) && $k == $data->id && !$conf['show_oids']) continue; - elseif ($v !== null && $v == '') echo "<td> </td>"; - else { - echo "<td style=\"white-space:nowrap;\">", - $misc->printVal($v, $finfo->type, array('null' => true, 'clip' => ($_REQUEST['strings']=='collapsed'))), "</td>"; - } - } + + print printTableRowCells($rs, $fkey_information, isset($object)); + echo "</tr>\n"; $rs->moveNext(); $i++; } - echo "</table>\n"; + echo "</table>\n"; + echo "<p>", $rs->recordCount(), " {$lang['strrows']}</p>\n"; // Show page navigation $misc->printPages($_REQUEST['page'], $max_pages, "display.php?page=%s&{$gets}&{$getsort}&strings=" . urlencode($_REQUEST['strings'])); @@ -450,7 +606,7 @@ // Create report if (isset($_REQUEST['query']) && ($subject !== 'report') && $conf['show_reports'] && isset($rs) && is_object($rs) && $rs->recordCount() > 0) echo "\t<li><a href=\"reports.php?{$misc->href}&action=create&report_sql=", - urlencode($_REQUEST['query']), "&paginate=", urlencode($_REQUEST['paginate']), "\">{$lang['strcreatereport']}</a></li>\n"; + urlencode($_REQUEST['query']), "&paginate=", (isset($_REQUEST['paginate'])? urlencode($_REQUEST['paginate']):'f'), "\">{$lang['strcreatereport']}</a></li>\n"; // Create view and download if (isset($_REQUEST['query']) && isset($rs) && is_object($rs) && $rs->recordCount() > 0) { @@ -475,10 +631,25 @@ "\">{$lang['strrefresh']}</a></li>\n"; echo "</ul>\n"; } - + + + /* shortcuts: this function exit the script for ajax purpose */ + if ($action == 'dobrowsefk') { + doBrowseFK(); + } + + $scripts = "<script src=\"libraries/js/jquery.js\" type=\"text/javascript\"></script>\n"; + $scripts .= "<script src=\"js/display.js\" type=\"text/javascript\"></script>"; + + $scripts .= "<script type=\"text/javascript\">\n"; + $scripts .= "var Display = {\n"; + $scripts .= "errmsg: '". str_replace("'", "\'", $lang['strconnectionfail']) ."'\n"; + $scripts .= "};\n"; + $scripts .= "</script>\n"; + // If a table is specified, then set the title differently if (isset($_REQUEST['subject']) && isset($_REQUEST[$_REQUEST['subject']])) - $misc->printHeader($lang['strtables']); + $misc->printHeader($lang['strtables'], $scripts); else $misc->printHeader($lang['strqueryresults']); @@ -498,7 +669,7 @@ break; case 'confdelrow': doDelRow(true); - break; + break; default: doBrowse(); break; diff --git a/js/display.js b/js/display.js new file mode 100644 index 00000000..f9180c67 --- /dev/null +++ b/js/display.js @@ -0,0 +1,78 @@ +$(document).ready(function() { + + /* init some needed tags and values */ + + $('table#data').wrap('<div id="fkcontainer" class="fk" />'); + $('#fkcontainer').append('<div id="root" />'); + + jQuery.ppa = { + root: $('#root'), + }; + + $("a.fk").live('click', function (event) { + /* make the cursor being a waiting cursor */ + $('body').css('cursor','wait'); + + query = $.ajax({ + type: 'GET', + dataType: 'html', + data: {action:'dobrowsefk'}, + url: $(this).attr('href'), + cache: false, + context: $(this), + contentType: 'application/x-www-form-urlencoded', + success: function(answer) { + pdiv = this.closest('div.fk'); + divclass = this.attr('class').split(' ')[1]; + + /* if we are clicking on a FK from the original table + (level 0), we are using the #root div as parent-div */ + if (pdiv[0].id == 'fkcontainer') { + /* computing top position, which is the topid as well */ + var top = this.position().top + 2 + this.height(); + /* if the requested top position is different than + the previous topid position of #root, empty and position it */ + if (top != jQuery.ppa.root.topid) + jQuery.ppa.root.empty() + .css({ + left: (pdiv.position().left) +'px', + top: top + 'px' + }) + /* this "topid" allows to track if we are + opening a FK from the same line in the original table */ + .topid = top; + + pdiv = jQuery.ppa.root; + + /* Remove equal rows in the root div */ + jQuery.ppa.root.children('.'+divclass).remove(); + } + else { + /* Remove equal rows in the pdiv */ + pdiv.children('div.'+divclass).remove(); + } + + /* creating the data div */ + newdiv = $('<div class="fk '+divclass+'">').html(answer); + + /* appending it to the level-1 div */ + pdiv.append(newdiv); + }, + + error: function() { + this.closest('div.fk').append('<p class="errmsg">'+Display.errmsg+'</p>'); + }, + + complete: function () { + $('body').css('cursor','auto'); + } + }); + + return false; // do not refresh the page + }); + + $(".fk_delete").live('click', function (event) { + $(this).closest('div').remove(); + return false; // do not refresh the page + }); +}); diff --git a/themes/default/global.css b/themes/default/global.css index fd762a47..5fee3295 100644 --- a/themes/default/global.css +++ b/themes/default/global.css @@ -466,3 +466,25 @@ pre.error border-left: 1px dotted #999; font-size: smaller; } + +div.fk { + background: white; + border:1px solid black; + margin-left: 20px; +} + +div#fkcontainer { + margin: 0; + position: relative; + width: 100%; + background: none; + border:0px; +} + +div#root{ + position: absolute; +} + +div.fk_value { + display:inline-block; +} |