使用 MVC 模式的 JavaScript DataGrid






4.81/5 (24投票s)
此网格支持调整、拖动和排序列。它使用 MVC 模式实现。
引言
MVC 模式是将应用程序或 GUI 控件分解为三部分的方式:控制器、模型和视图。MVC 模式旨在处理输入、数据处理和将数据渲染到 GUI 领域。用户输入、数据处理和数据渲染由视图、模型和控制器对象分离并处理。
控制器解释用户发出的鼠标和键盘输入,并将这些命令映射到视图和模型对象以改变状态。模型管理一个或多个数据元素。视图管理显示器的客户端区域,并负责向用户呈现数据。视图通常有一套知道如何渲染图形的方法包。控制器是一个负责用户与应用程序交互的对象。控制器接受用户输入并强制视图和模型执行操作。例如,如果用户单击鼠标按钮或单击界面上的某个项目,控制器负责确定应用程序应如何响应最终用户。模型、视图和控制器是相关的。因此,它们必须相互引用(下图显示了模型-视图-控制器关系)。
我认为 MVC 范式是分解 GUI 控件的最佳方式,这就是我选择这种模式的原因,而且我确实喜欢这种模式。无论您使用什么环境,无论是 Java、C# 还是 JavaScript,这都无关紧要。其思想是将应用程序分解为三个逻辑类。
--------------
| Model |
--------------
/\ /\
/ \
/ \
/ \
/ \
-------------- <------ --------------
| View | | Controller |
-------------- --------------
背景
我的热情是 AJAX 应用程序和 JavaScript 控件。我不喜欢在浏览器中使用 ActiveX 或 Java 小程序。当然,有很多原因您必须这样做,但在大多数情况下,我认为通过 JavaScript 代码实现会更好。我指的是 GUI 控件。
代码
如上所述,MVC 范式假定将应用程序或 GUI 控件分解为三个逻辑类。此控件中有三个逻辑类。
var dataSource = new DataSource();//implements the model
var view = new View(idHolder,columnsWSize,dataSource);
var controller = new Controller(view, dataSource);
controller
类处理所有事件并强制 dataSource
和 view
类执行某些操作。
this.onRemoveRow = function(rowId)
{
view.removeRow(rowId);
dataSource.removeRow(rowId);
}
this.onUpdateRow = function(rowID)
{
view.setGridUnselectedState();
view.setRowWriteState(rowID);
}
this.onAddRow = function()
{
view.setGridUnselectedState();
view.setRowAddState();
}
view.dataClickedEventHandler = function()
{
thisRef.hideAllMenu();
view.setGridUnselectedState();
event.cancelBubble = true;
}
....
dataSource
类有一套处理数据的方法。
var dataHeaders = new Array();
var dataRows = new Array();
var sortDirection= asc;
var sortIndex = 0;
function sortItems(data1,data2)
{
var data1Value = data1[sortIndex];
var data2Value = data2[sortIndex];
if(data1Value==null)
data1Value = "";
if(data2Value==null)
data2Value = "";
data1Value = data1Value.toUpperCase();
data2Value = data2Value.toUpperCase();
if(sortDirection==asc)
{
if(data1Value < data2Value)
return -1;
else
if(data1Value > data2Value)
return 1;
else
return 0;
}
else
{
if(data1Value > data2Value)
return -1;
else
if(data1Value < data2Value)
return 1;
else
return 0;
}
}
this.clear = function()
{
dataHeaders = new Array();
dataRows = new Array();
sortDirection= asc;
sortIndex = 0;
}
...
view
类有一套处理 GUI 的方法。
this.isRowWriteState = function(rowId)
{
for(var index=0;index < dataContent.rows.length;index++)
{
if(dataContent.rows[index].cells
[dataContent.rows[index].cells.length-1].firstChild.children
[1]!=null && dataContent.rows[index].rowId == rowId)
return true;
}
}
this.setRowSelectedState = function(rowId)
{
for(var index=0; index < dataContent.rows.length; index++)
{
if(dataContent.rows[index].rowId==rowId)
{
for(var index1 = 0; index1 <
dataContent.rows[index].cells.length; index1++)
{
var width = dataContent.rows[index].cells[index1].style.width;
dataContent.rows[index].cells[index1].style.cssText =
styleCellSelectedData;
dataContent.rows[index].cells[index1].style.width = width;
}
break;
}
}
this.selectedRowId = rowId;
}
this.setGridReadState = function()
{
for(var index=0;index < dataContent.rows.length;index++)
{
//if the stub column has buttons that means the row is in write state
if(dataContent.rows[index].cells[dataContent.rows
[index].cells.length-1].firstChild.children[1]!=null)
{
this.setRowReadState(dataContent.rows[index].rowId);
}
}
}
this.setGridNotAddState = function()
{
if(data.children.length==2)
data.children[0].parentElement.removeChild(data.children[0]);
}
...
缺点
此网格有一些缺点。它只支持 Internet Explorer(我没有太多时间为它编写额外的代码使其成为跨浏览器控件)。但这是一个次要的缺点;主要缺点是代码执行。在调整列大小和创建期间,执行效果不是很好。由于它涉及单元格中文本的截断,我编写了执行截断的实用方法。
function junkTextToWSize(str , wSize , cssText)
{
var textLabel = document.getElementById("uxTextLabel");
if(textLabel==null)
{
textLabel = document.createElement("");
textLabel.id = "uxTextLabel";
document.body.appendChild(textLabel);
}
textLabel.innerText = str;
textLabel.style.cssText = cssText;
textLabel.style.visibility = "hidden";
textLabel.style.position = "absolute";
if(textLabel.offsetWidth>=wSize)
{
textLabel.innerText = "..."+textLabel.innerText
while(textLabel.offsetWidth>=wSize)
{
textLabel.innerText = textLabel.innerText.substr
(0,textLabel.innerText.length-1);
}
var retString = textLabel.innerText.substr
(3,textLabel.innerText.length-3)+"...";
return retString;
}
else
return str;
}
此方法创建一个临时 span 元素来检查文本的大小。如果文本标签太长,它必须被截断并带有后缀“...”。对于 Internet Explorer 来说,调用 offsetWidth
太昂贵了。我编写了一个简单的函数来尝试调用 offsetWidth
。
function buildTable()
{
var table = document.all.uxTest.appendChild
(document.createElement("<table>"));
for(var index=0; index<401; index++ )
{
var row = table.insertRow();
for(var index1=0; index1 < 11; index1++)
{
var cell = row.insertCell()
cell.innerText = index+"string"+index1;
// var offsetWidth = cell.offsetWidth;
}
}
}
buildTable();
此调用大约需要 6 秒,但如果您取消注释“var offsetWidth = cell.offsetWidth;
”,则需要超过 1 分钟才能执行。这就是我将网格中的行数限制为 300 行的原因。我仍然对如何在不调用 offsetwidth
属性的情况下计算文本宽度感兴趣。
网格功能
此网格支持拖动列、调整列大小和排序列。双击列标题将导致网格按单击的标题列排序。要调整列大小,您需要按住鼠标按钮移动标题分隔符。如果文本标签太长而无法放入单元格中,它将导致调整大小列中的文本标签被截断。要拖动列,您需要按住鼠标按钮将鼠标移动到标题上。
网格还有两种菜单:选定行菜单和网格菜单。右键单击行将导致出现选定行菜单,右键单击行以外的任何地方将导致出现网格菜单。网格有两个事件:“onRowUpdated
”和“onRowAdded
”。这些事件在单击“Add
”、“Update
”按钮时触发,并返回包含网格行新值的 obj
。
要将行设置为“add
”、“update
”状态,您需要调用网格方法“grid.addRow()
”、“grid.updateRow(rowId)
”。
Using the Code
在任何 Web 应用程序中使用它都非常简单。
var grid = new GolikGrid(null,
[150,30,150,100,150,150,150,150,150,150],500,900 ,"uxGridHolder");
var dataSource = grid.getDataSource();
dataSource.addHeaderItem("Company Name");
dataSource.addHeaderItem("Contact Name");
dataSource.addHeaderItem("Company Title");
dataSource.addHeaderItem("Address");
dataSource.addHeaderItem("City");
dataSource.addHeaderItem("Region");
dataSource.addHeaderItem("Postal Code");
dataSource.addHeaderItem("Country");
dataSource.addHeaderItem("Phone");
dataSource.addHeaderItem("Fax");
dataSource.addRowItem(1,new Array("Wilman Kala","Serch",
"Owner/Marketing Assistant","Adenauerallee 900",
"Seattle","SP","51100","Germany","0241-039123","089-0877451"));
dataSource.addRowItem(2,new Array("Wilman Kala","Serch",
"Owner/Marketing Assistant","Adenauerallee 900",
"Seattle","SP","51100","Germany","0241-039123","089-0877451"));
dataSource.addRowItem(3,new Array("Wilman Kala","Serch",
"Owner/Marketing Assistant","Adenauerallee 900",
"Seattle","SP","51100","Germany","0241-039123","089-0877451"));
dataSource.addRowItem(4,new Array("HILARION-Abastos","Dima",
"Sales Representative","Luisenstr. 48","Sao Paulo",
"WA","8200","Denmark","(5) 555-4729","(171) 555-9199"));
grid.dataBind();
var grid = new GolikGid(param1,param2,param3,param4,param5);
Param1
- 网格的 ID,可以省略,并可以传递null
而不是实际 ID。
注意:如果您只使用一个网格,可以忽略此参数并传递null
,但如果您要使用多个网格,则必须为每个网格传递唯一的 ID。Param2
– 列宽度的数组。
注意:最好将列设置为文本的实际宽度,否则它必须调用“junkTextToWSize
”方法,这会导致过载。Param3
- 网格的高度Param4
- 网格的宽度Param5
– 网格的 ID 保持器
注意:如果省略参数并传递null
,则网格将被添加到body
元素而不是指定的元素。
dataSource.addRowItem(param1,param2);
Param1
- 行的 IDParam2
– 单元格数组
初始化菜单
如前所述,网格中有两种菜单:项目菜单和网格菜单。网格菜单在行以外的任何地方(在滚动条、标题、应该有行的空白区域)右键单击时出现,行菜单在行上右键单击时出现。构建您自己的菜单非常简单。您需要做的就是创建一个对象,其中“key
”将被视为菜单项名称,“value”将被视为菜单项处理程序,然后将其传递给相应的方法。
var rowMenuItems = {"Add New":onMenuAddNew,"Update":onMenuUpdateRow,
"Remove":onMenuRemoveRow,"Refresh":onMenuRefresh};
var gridMenuItems = {"Refresh":onMenuRefresh,
"Remove Column":onMenuRemoveColumn,
"Add New Row":onMenuAddNew,"Help":onMenuHelp};
grid.setItemMenu(rowMenuItems);
grid.setGridMenu(gridMenuItems);
您还需要实现菜单项的所有已声明处理程序。例如,“Help
”网格菜单项处理程序
function onMenuHelp()
{
window.open("about:blank");
}
在网格中添加、更新和删除行
网格没有将行设置为“update
”、“add
”状态的本机事件,但当这些操作完成时会触发两个事件。
//assign the event handler for onRowUpdated and onRowAdded event
grid.onRowUpdated = onGridRowUpdated;
grid.onRowAdded = onGridRowAdded;
要将行设置为更新状态,您需要在某个地方调用“grid.updateRow(rowId)
”。例如,“Update
”行菜单项处理程序。
function onMenuUpdateRow()
{
grid.updateRow(grid.getSelectedRowId());
}
由于“onMenuUpdateRow
”是行菜单的处理程序,因此可以通过调用“grid.getSelectedRowId()
”方法获取“selected RowId
”。此调用将选定的行设置为更新状态。用户可以通过单击取消按钮来取消更新。一旦用户单击“更新”按钮,网格就会触发事件“grid.onRowUpdated
”。
例如,“onGridRowUpdated
”事件处理程序。
function onGridRowUpdated(row)
{
var updateResult = "The grid row has been updated.\n";
updateResult+= "ID "+row.id+"\n";
updateResult+= "Company Name: "+row
["Company Name"]+"\n";
updateResult+= "Contact Name: "+row
["Contact Name"]+"\n";
updateResult+= "Company Title: "+row
["Company Title"]+"\n";
updateResult+= "Address: "+row
["Address"]+"\n";
updateResult+= "City: "+row["City"]+"\n";
updateResult+= "Region: "+row
["Region"]+"\n";
updateResult+= "Postal Code: "+row
["Postal Code"]+"\n";
updateResult+= "Country: "+row
["Country"]+"\n";
updateResult+= "Phone: "+row
["Phone"]+"\n";
updateResult+= "Fax: "+row["Fax"]+"\n";
alert(updateResult);
customDataSource.updateRow(row.id,new Array(
row["Company Name"] ,row["Contact Name"],
row["Company Title"],row["Address"],
row["City"],row["Region"],
row["Postal Code"],row["Country"],
row["Phone"],row["Fax"]));
}
此事件处理程序有一个参数,其中包含更新行的数据。每个值都可以通过标题名称访问,然后保存到某个地方(在当前示例中,它是一个自定义的 datasource
对象,在页面刷新之前作为持久存储实现)。
“添加新”行菜单项处理程序也是如此。
function onMenuAddNew()
{
grid.addRow();
}
function onGridRowAdded(row)
{
var updateResult = "The grid row has been added.\n";
updateResult+= "Company Name: "+row
["Company Name"]+"\n";
updateResult+= "Contact Name: "+row
["Contact Name"]+"\n";
updateResult+= "Company Title: "+row
["Company Title"]+"\n";
updateResult+= "Address: "+row
["Address"]+"\n";
updateResult+= "City: "+row
["City"]+"\n";
updateResult+= "Region: "+row
["Region"]+"\n";
updateResult+= "Postal Code: "+row
["Postal Code"]+"\n";
updateResult+= "Country: "+row
["Country"]+"\n";
updateResult+= "Phone: "+row
["Phone"]+"\n";
updateResult+= "Fax: "+row
["Fax"]+"\n";
customDataSource.addRowItem(new Array(row["Company Name"],
row["Contact Name"],row["Company Title"],
row["Address"],row["City"],
row["Region"],row["Postal Code"],
row["Country"],row["Phone"],row["Fax"]));
if(window.confirm("The row has been successfully added,
refresh the grid?"))
initGrid();
}
“onMenuAddNew
”行菜单项处理程序不需要行 ID,因此此行菜单事件处理程序可以添加到网格菜单。添加新行时,您必须为此行分配新的唯一 ID,因此您需要将其放入持久存储中,然后重新初始化网格以查看新项。
“onMenuRemoveRow
”行菜单项处理程序不能添加到网格菜单,因为它需要只有在行菜单项上下文中才能访问的删除行的 ID。
function onMenuRemoveRow()
{
var selectedId = grid.getSelectedRowId();
if(window.confirm("Are sure you want to remove this item?"))
{
grid.removeRow(selectedId);
customDataSource.removeRow(selectedId);
}
}
这种方法提供了一种灵活的方式来构建您自己的菜单,但它不一定只能构建菜单。您可以通过其他方式对网格进行任何修改。例如,它可能是一个带有“删除、更新、添加、刷新”图标的漂亮工具栏。
请不要在您的项目中原样使用此网格。它只是一个处理数据的网格演示版本,需要进行良好的测试。
如果您觉得这篇文章有趣,请为我投票。