65.9K
CodeProject 正在变化。 阅读更多。
Home

通用数据库浏览器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2014年1月14日

CPOL

17分钟阅读

viewsIcon

24907

downloadIcon

503

本文介绍了一个可用于多个不同数据库的数据库浏览器。

引言

本文介绍了一种通用的查询、显示和浏览数据库内容的方法,只需修改客户端 HTML 文件中的下拉选择框选项以及服务器端 PHP 文件中的连接字符串,即可访问所提供类型的任何数据库。

背景

本文摘自我的著作《Web 2.0 富互联网应用程序开发实践指南》,书中我讨论了构建 RIA / 单页应用程序网站的方法。完整的可运行示例可以在与本书配套的网站 www.web2ria.com 上找到,方法是点击“Server-Side”标签,然后点击“Database Browser”菜单项。

在 RIA 网站示例中,可以访问 4 个不同的数据源,它们位于不同的 URL,涵盖 3 种不同类型的数据源:MySQL、Microsoft SQL Server 和 ODBC 纯文本字符分隔值(CSV)文件。所采取的方法是允许用户在网页中根据需要构建 SQL 查询,以选择、查看和操作数据库中的底层数据,从而以期望的方式查看所需信息。

来自数据库的元数据,例如表名和表中的字段名,会被查询并用于填充下拉选择框,然后这些选择框用于构建 SQL 查询。查询结果显示在可滚动的 HTML 表格中,这些表格具有固定的表头,从而提高了可读性。数据库信息可以通过选择起始点和返回记录数进行分页,同时还可以滚动浏览任何给定查询返回的记录。

Using the Code

在与本书配套的网站上,有一个 .zip 文件,其中包含本书中讨论的小部件,包括本文介绍的数据库示例。虽然小部件 widgets.zip 中“TableFuncs”文件夹的数据库源代码可以在您的“localhost”上使用,并且可以从 localhost 访问“genome”数据库,但其他数据源无法从 localhost 访问。

因此,建议的使用 dbBrowse.html 和 queryDB.php 页面的方式是:将它们上传到数据库所在的位置,然后从那里访问数据库。首先应编辑源代码,以反映 dbBrowse.html 数据库选项下拉框中的数据库名称,以及 queryDB.php 中的连接字符串,以反映访问特定数据库所需的 URL、用户名和密码。

一旦从下拉列表中选择了一个数据库,就会在数据库的第一个表上运行默认查询(SELECT * FROM TableName),然后用户可以通过从“Select Table”下拉列表中选择表名,从“Select Field”下拉列表中选择字段,并指定“Where”和“Sort By”子句(如果需要)来构建 SQL 查询以优化搜索。如果指定了 WHERE 子句,则会选择“=”下拉列表中的一个选项(例如 =, LIKE, <, >, <=, >=),并将值键入或复制到“=”和“AND/OR”下拉列表之间的文本框中。构建好查询后,通过单击“Run Query”按钮来运行它。

在 MS SQL Server 的“Northwind”数据库中,默认结果表还允许通过双击“Address”字段运行映射应用程序。将显示一个显示被点击地址的地图,您应该通过点击地图顶部的带圆圈的“+”图标来放大,以查看街道详细信息。用于映射的字段(例如,“Northwind”数据库中的“Country”、“City”、“Postal Code”,“Customers”表)也可以通过在运行查询之前在“Select Map Field”下拉列表中选择选项来指定,并且选择的第一个字段将是当该字段在某一行中被点击时激活映射的字段。

dbBrowse 中可用的数据库选择都位于不同的 Internet IP 地址,如第 5.5 图所示的“Select Database”下拉列表中。

图 5.5 - 选择数据库

这些数据库不包含任何专有信息,并且都可以在公共领域自由获取。有两个 MySQL 数据库,“world”和“genome”,一个 Microsoft SQL Server 数据库,“Northwind”,以及一个“SNOMED_CORE_SUBSET_201002.txt”ASCII 文本文件,使用 Open Database Connectivity (ODBC) 文本文件 SQL 驱动程序进行访问。

通过更改 dbBrowse 中的“Select database”下拉列表以及 queryDB 连接字符串,可以替换其他同类数据库。

dbBrowse.html 页面使用以下 JavaScript 代码初始化 AJAX。使用特性检测以跨浏览器方式进行初始化,然后使用 AJAX 调用下一页之后代码块中的默认 .csv ASCII 文本文件。

function createXMLHttpRequest() 
{
    if (typeof XMLHttpRequest != "undefined")	        //  Feature detect for
    {						        //  non-Microsoft browsers
         return new XMLHttpRequest();
    } 
    else if (typeof ActiveXObject != "undefined")         //  Feature detect for
    {						        //  Microsoft browsers
         return new ActiveXObject("Microsoft.XMLHTTP");
    }
    else 
    {
         throw new Error("XMLHttpRequest not supported");
    }
}

下一页的代码执行实际的 AJAX 调用,将数据加载到文件“vitalSigns.csv”的数组中。文件首先按回车/换行符 (\r\n) 分割以获取行/记录,然后每行/记录按逗号 (,) 分割以获取字段/列。

此示例中的所有字段都是字符串,除了第 2、3 和第 6 列,它们被转换为数字类型。但是,不对文件的第一行进行任何转换,因为这是列标题/名称。

如果只是为了显示,所有字段都可以保留为字符串,只有在需要进行计算时才需要转换数字数据。在这种特定情况下,文件结构是已知的,但如果需要在不知道文件结构的情况下转换字段,测试每个字段的代码片段将是这样的:

if ((varName*1) == varName) 运行将 varName 视为数字的代码;
else 运行将 varName 视为文本的代码;

请注意,下一页的 `loadFile()` 函数与第 4 章中的 ‘`loadVitals()`’ 函数非常相似。此 AJAX 函数用于加载在 ‘dbBrowse.html’ 上最初显示的 .CSV 文件,尽管它也可用于加载任何 .CSV 文件。

function loadFile(FileToRun, fileParams)
 {
     var requestFile = createXMLHttpRequest();
     requestFile.open("GET", FileToRun, true);
     requestFile.onreadystatechange = function()	    // Request state has changed
     {
         if (requestFile.readyState == 4) 		    // Request is complete 
         {
	 tempText = requestFile.responseText;
              tempText2 = tempText.split("\r\n");	           // Line separator = ‘\r\n’
	 if (tempText2.length > 1)
	 {
	     var tempVSarray;
	     for (var i=0; i<tempText2.length; i++)        // Loop through each line
	     {
	         tempVSarray = tempText2[i];
	         tempVSarray = tempVSarray.split(",");     // Field separator = ‘,’
	         for (var j=0; j< tempVSarray.length; j++)  // Loop through each
	         {					           // item in the line
		     if ((i>0) && (j != 1) && (j != 2) && (j != 5))
                               {
                                   tempVSarray[j] = tempVSarray[j]*1; // Convert some
                               }				               // columns to numeric
	         }		
	          vitalSignsArray[i] = tempVSarray; 	// Load line array
	     }						// into table array
 	 }
	 setTable("dataTable" ,vitalSignsArray);        // Populate table with
          }						       // id ‘dataTable’ from
     }						       // ‘vitalSignsArray’ array
     if ((fileParams == null) || (fileParams == ""))
     {requestFile.send();}
     else 
     {
	requestFile.setRequestHeader('Content-Type', 
                                                   'application/x-www-form-urlencoded');
	requestFile.setRequestHeader('Content-length',fileParams.length);
	requestFile.send(fileParams);
     }
 }

函数末尾附近的 ‘`requestFile.setRequestHeader`’ 行被配置为返回任何文件格式的信息,而不仅仅是“AJAX”名称中“X”指定的 XML 格式。

跨浏览器功能

使用第 3 章末尾所示的 JavaScript 代码以跨浏览器方式实现了浏览器检测。

数据表是使用下一页所示的 `setTable()` 函数即时构建的,使用 AJAX 调用时生成的 ‘`vitalSignsArray`’ 数组中的数据。在函数末尾附近,代码会为已检测到的浏览器进行分支。

现有的 ID 为 ‘`contentLines`’ 的 HTML `

` 元素用于容纳使用 ‘`createElement`’ 创建的、ID 为 ‘`dataTable`’ 的新 HTML `` 元素。

在接下来的两页代码中,因为数组中的底层数据的第一行是标题,当 ‘v’ 的值为零时,会使用表 `

` 元素而不是数据行的 `` 元素创建标题。并且使用 ‘header’ 类设置了标题的区分外观。

表 `

` 单元格元素使用 `` 锚点创建,以提供内置的“onclick”功能。并且表头元素通过 `arraySort()` 函数提供表排序,因此点击表头可以对表进行排序。

‘`text-decoration`’ 被设置为 ‘none’,因此表头标题不会被下划线,‘color’ 参数被设置,以便在单击表头/锚点元素时表头标题颜色保持不变,并且光标外观被设置为 ‘hand’,以向用户指示一个活动的可点击元素。

位于倒数第二页顶部的 ‘`setColor(lineNum);`’ 函数会交替数据行的背景颜色,以便用户更容易地在表格中跟踪数据行。

在创建结构后,两个循环,外层使用变量 ‘v’ 控制,内层使用变量 ‘w’ 控制,构建 `

` 的数据行。每个 ‘v’ 值引用一行数据,每个 ‘w’ 值引用行中的一个字段。

在内层循环结束时,代码会为 Internet Explorer 和非 IE 浏览器进行分支,以避免使用‘setAttribute’ JavaScript 命令,因为该命令在此上下文中与 IE 结合使用时在设置字段的样式/背景和边框属性方面存在问题。

在函数末尾,‘`fxheaderInit()`’ 和 ‘`fxheader()`’ 函数实现了固定表头和滚动数据行,以实现用户在表格中滚动时的效果。

function setTable(tblId,tempArray)
{
    var clTable = document.getElementById("contentLines");
    clTable.display = "block";
    var vsTable = document.createElement('TABLE');	//  Set up table structure
    vsTable.setAttribute("id",tblId);
    vsTable.setAttribute("border","1");
    vsTable.setAttribute("cellpadding","2");
    vsTable.setAttribute("cellspacing","0");
    vsTable.setAttribute("class","mt");
    vsTable.setAttribute("style","width:750px;background:#F5F5DC;
                            border:1px solid lightgray;color:#000080;
                            font- weight:bold;");                                                                                                                                                                   
    clTable.appendChild(vsTable);
    var vsTBody = document.createElement('TBODY');
    vsTable.appendChild(vsTBody);
    var tbody = document.getElementById(tblId).
                            getElementsByTagName('tbody')[0]; 
    lineNum = 0;
    for (v=0;v<tempArray.length;v++)			//  Loop through rows
    {

                     SEE   LOOP   ON   NEXT   PAGE

    }
    fxheaderInit(tblId,250,1,0);				//  Set up and implement
    fxheader();						//  fixed header scrolling
}

for (v=0;v<tempArray.length;v++)		//  Loop through rows
{
     if (v == 0) {Color = "#dddddd";} 
     else 						//  Switch alternate row
     {						//  background colors
	fontW = "normal";
	setColor(lineNum);
     }
     var row = document.createElement('TR');
     tbody.appendChild(row);
     lineArray = tempArray[v];
     for (w=0;w<lineArray.length;w++)		//  Loop through fields
     {
          var cellW = "cell"+w;
          if (v == 0) cellW = document.createElement("TH");   // Create headers
          else cellW = document.createElement("TD"); 	    //  Create data rows
          space = " ";
          if ((lineArray[w]*1) > 99) {space = "";}
          else if ((lineArray[w]*1) < 10) {space = "  ";}
          if (v > 0) cellW.innerHTML = space+lineArray[w];
          else cellW.innerHTML = "<a href='javascript:
                arraySort("+w+");' 					// Sort by
                style='color:#000080;cursor:hand;cursor: pointer;	// column
                text-decoration:none;'>"+space+lineArray[w]+"</a>";  // contents
          if (tblId == "dataTable");
         {
	 if (w == 0) {cellW.setAttribute("class","firstTD");}
	 else cellW.setAttribute("class","otherTD");
	 if (v == 0)					    //  Set up headers
	 {
	      cellW.setAttribute("class","header");
	      cellW.setAttribute("id","col"+w);
	      cellW.setAttribute("onmouseover","
                                               style.cursor='hand';style.cursor='pointer';");
	 }
           }
           if (browser == "Internet Explorer") 		    // Branch for IE
          {
                  cellW.style.cssText="background:"+Color+
                                        ";border:solid #d3d3d3 1px;font-size:13px";
          }
          else cellW.setAttribute("style","background:"+Color+";
                                                       border:solid #d3d3d3 1px;font-size:13px;");
          row.appendChild(cellW);
       }
    }

表排序代码如下所示,它对包含文本数据(字母和数字形式)的列执行内存数组排序。通过单击列标题对列内容进行排序,每次单击表头时按升序和降序交替排序 - 请参阅 www.web2ria.com/dbBrowse.html。

function arraySort(sortCol)
{
   var header = vitalSignsArray.shift();		// Remove Headers
   if ((sortCol == 5) || (sortCol == 2)) 
   {
      setCol(sortCol,0); 				// setCol() handles text 
   }						// with "/" or "-"
   else
   {
      if (typeof vitalSignsArray[0][sortCol] == 'string')
     {z = vitalSignsArray.naturalSort(sortCol);}	// Sort alphabetical data
      else z = vitalSignsArray.deepsort(sortCol);	// Sort numeric data
   }
   results = "";
   if (direct == "Asc") direct = "Desc";		// Switch next sort direction
   else direct = "Asc";
   document.getElementById("contentLines").innerHTML = "";
   if ((sortCol == 5) || (sortCol == 2)) setCol(sortCol,1);
   vitalSignsArray.unshift(header);		// Replace headers
   setTable("dataTable");			// Build HTML <table>
}

此示例中的排序使用数组 ‘`vitalSignsArray`’ 作为数据源,`setTable()` 函数也使用此数组来填充 HTML 表格。

下一页的 ‘`deepSort()`’ 函数处理数字列的排序。

   	Array.prototype.deepsort= function()
	{ 
	    var i, order=arguments, L=order.length, tem; 
	    return this.sort(function(a, b)
	    { 
	        i= 0; 
	        while(i < L)
	       { 
	            tem= order[i++]; 
           		var ao= a[tem] || 0, bo= b[tem] || 0; 
	            if(ao== bo) continue; 
           		if (direct == "Asc") return ao> bo? 1: -1; 
		else return bo> ao? 1: -1;
	        } 
	        return 0; 
	    }); 
	}

下一页的 ‘`naturalSort()`’ 函数处理字符串列的排序。

	Array.prototype.naturalSort= function()
	{
		var a, b, a1, b1, rx=/(\d+)|(\D+)/g, rd=/\d+/;
		return this.sort(function(as, bs)
		{
			a= String(as).toLowerCase().match(rx);
			b= String(bs).toLowerCase().match(rx);
			while(a.length && b.length)
			{
				a1= a.shift();
				b1= b.shift();
				if(rd.test(a1) || rd.test(b1))
				{
				      if(!rd.test(a1)) return 1;
				      if(!rd.test(b1)) return -1;
				      if(a1!= b1) 
				      {
				          if (direct == "Asc") return a1-b1;
				          else return b1-a1;
				      }
				}
				else if(a1!= b1) 
				{
					return a1> b1? 1: -1;
				}
			}
			return a.length- b.length;
		});
	}

从“Select Database”下拉框中的一个选项中选择一个数据库,当选择更改时,将调用函数 ‘`dbSel()`’,方式与以下伪代码类似:

    <select id="dbSel" onChange="if (this.value != 0) dbSel(this.value);">
       <option value=0 selected>Select Database</option>
       <option value="One Database">One Database</option>
       <option value="Another Database">Another Database</option>
       <option value="Etc."> Etc.</option>
    </select>

函数 ‘`dbSel`’ 的代码显示在下方。

  function dbSel(dbNum,startRec,recNum)
  {
       var dbSel;
        if ((startRec == "") && (recNum == ""))
        {
	startRec = 0;
	recNum = 1000;
         }
         document.getElementById("dbSelFrame").style.visibility = "visible";
         dbSel = "queryDB.php?dbSel="+dbNum +
                                     "&startRec="+startRec+"&recNum="+recNum;		   
         document.getElementById("dbSelFrame").src = dbSel;
  }

在此函数中,选定的数据库被传递到 queryDB.php,该文件通过更改 `