一个基于Extjs 3的支持单元格合并的面板

论坛 期权论坛 脚本     
匿名技术用户   2020-12-29 09:28   11   0
这是一个支持单元格合并的Panel,不是基于 GridPanel,而是基于 Ext.Panel 这个类。内部表格实现是基于 html 的 <table> 标签的。支持增删。效果图如下:

代码依赖 Extjs 3 和 JQuery,请自行添加。废话少说,上代码。

js 部分(直接调用 tableTag() 方法就可以显示出效果了):

Array.prototype.remove=function(dx)
{
 if(isNaN(dx) || dx > this.length || dx < 0) return false;

 for(var i=0,n=0;i<this.length;i++)
 {
  if(this[i] != this[dx]) this[n++] = this[i]
 }
 this.length -= 1
};


Ext.ns('Ext.ns.my.tag');

/**
 * 这是一个可以自适应、自动合并的表格面板。此代码依赖 JQuery。
 */
Ext.ns.my.tag.TagTablePanel = Ext.extend(Ext.Panel,
{
 /**
  * 默认自动显示滚动条
  */
 autoScroll : true, 
 /**
  * 列标题。元素是 js 对象。下面是对象各个字段的意义:
  * 
  * header: 
  *   必输项;类型:文本。表示列标题
  * 
  * width: 
  *   可选项;类型:文本,正整数;单位:px。表示列宽。支持正整数,和'*', 'auto'。默认 '*'。
  * 
  * textHorizontalAlignment: 
  *   可选项;类型:文本。表示数据域文本水停靠位置。可选的值为:'left'、'center'、'justify'、
  *   'inherit'和'right'。默认不设定此项。
  * 
  * renderer:
  *   可选项;函数。用于根据数据渲染显示。参数 (oldValue, rowNum, colNum, record)。返回一个
  *   HTML 字符串。默认照常显示数据文本。
  * 
  * hidden: 
  *   可选项;类型:布尔值。表示当前列是否隐藏。true 表示隐藏。默认 false(不隐藏)。
  */
 colHeaders: [],
 /**
  * 要合并的列的序号,从 0 开始
  */
    mergeCols: [],
    /**
     * 面板中的数据
     */
    records: [],
    /**
     * 被点击的单元格所在的列号,默认 -1
     */
    selectedColNumber: -1,
    /**
     * 被点击的单元格所在的行号,默认 -1
     */
    selectedRowNumber: -1,
    initComponent : function()
    {
     Ext.ns.my.tag.TagTablePanel.superclass.initComponent.call(this); // 调用父类的初始化方法

     // 初始化表格和列标题
     this.html = '<table id="' + this.id + '-table" class="table-tag-table">' + this.getHeaderHtml(this) + '</table>';
 },
 /**
  * 单元格被点击时,执行的回调函数。
  * 
  * 参数 _this:指 Panel 本身
  * 参数 cell :指单元格所在的 Dom(td)
  */
 cellClickCallback : function(_this, cell) { },
 /**
  * private
  * 获取表示列宽的 CSS 文本。
  */
 getColWidthStyleText: function(width)
 {
  var w = 'width: ';
  if(width == undefined || width == '*') w += '*;';
  else if(width == 'auto') w += 'auto;';
  else w += (width + 'px;');
  
  return w;
 },
 /**
  * private
  * 获取列标题所在行的 HTML 表示,用于生成列标题。
  * 
  * 参数 _this:指 Panel 本身
  */
 getHeaderHtml: function(_this)
 {
     var html = '<tr>', co = null, width = null;
     
     var width = null; // 表示列宽的 CSS 样式文本
     
     for(var i = 0; i < _this.colHeaders.length; i++)
     {
      co = _this.colHeaders[i];
      
      if(co.hidden == true) continue; // 如果隐藏,就干脆不渲染。
      width = _this.getColWidthStyleText(co.width);
      
      html += ('<th class="table-tag-th" style="' + width + '">' + co.header + "</th>");
     }
     html += '</tr>';
     
     return html;
 },
 /**
  * 获取被选择的单元格的 ID(td 的 ID)。
  * 如果没有选择到单元格,返回 false。
  */
 getSelectedCellId: function()
 {
  return (this.selectedRowNumber < 0 || this.selectedColNumber < 0) ? false 
    : (this.id + '-row-' + this.selectedRowNumber + '-col-' + this.selectedColNumber);
 },
 /**
  * private
  * 一个自动合并单元格的方法。这个合并是根据数据实际值来的,而非渲染结果
  */
 merge : function()
 {
  var mergeCols = this.mergeCols;
  var records = this.records;
  
  /**
   * 按列合并单元格
   */
  for(var t = 0; t < mergeCols.length; t++)
  {
   var col = mergeCols[t];
      var theCell = $('#' + this.id + '-row-0-col-' + col);               // 注意行和列
      var theRecord = records[0];
      for(var i = 1; i < records.length; ++i)
      {
       var currCell = $('#' + this.id + '-row-' + i + '-col-' + col);  // 注意行和列
       var currRecord = records[i];
       if(currRecord[col] == theRecord[col] && currCell.is(":visible") )
       {
        var rowspan = theCell.attr('rowspan');
        if(rowspan == undefined) rowspan = 1;
        
        theCell.attr('rowspan', parseInt(rowspan) + 1);
        currCell.hide();    // 注意行和列
       }
       else
       {
        theCell = currCell;
        theRecord = currRecord;
       }
      }
  }
 },
 /**
  * 设置被选择的单元格中的文本值。
  * 如果一个单元格是跨行的,那么意味着,它下面的几行数据相同,但是被隐藏了。这时,会同步更改相邻的相同数据。
  */
 getSeletedValue:function()
 {
  var td = $("#" + (this.id + '-row-' + this.selectedRowNumber + '-col-' + this.selectedColNumber));
  
  return (this.selectedRowNumber < 0 || this.selectedColNumber < 0) ? false : td.text();
 },
 /**
  * 改变被选择的单元格中的文本值。
  * 
  * 参数 value:新的值
  */
 setSeletedValue:function(value)
 {
  // 先更改数据域中的响应数据,再重新进行渲染
  
  var row = this.selectedRowNumber, col = this.selectedColNumber;
  
  if(this.records.length <= 0) return false;
  
  // 往下找相同的数据进行更改
  var basev = this.records[row][col];
  for(var i = row; i < this.records.length; i++)
  {
   if(this.records[i][col] == basev) this.records[i][col] = value;
   else break;
  }
  
  // 往上找相同的数据进行更改
  for(var i = row; i >= 0; i--)
  {
   if(this.records[i][col] == basev) this.records[i][col] = value;
   else break;
  }
  
  this.selfAdapt(this); // 渲染
 },
 /**
  * private
  * 获取排序算法。重写这个方法时,请确保它应该被用于对数据进行分类
  * 
  * 参数 _this:指 Panel 本身
  */
 getSortAalgorithm: function(_this) 
 {
  // 记录比较方法
  var recordSort = function(a, b) 
  {
   var mergeCols = _this.mergeCols;
   
   for(var i = 0; i < mergeCols.length; i++)
   {
    var col = mergeCols[i];
    if(a[col] > b[col]) return 1;
    else if(a[col] < b[col]) return -1;
   }
   
   return 0;
  };
  
  return recordSort;
 },
 /**
  * private
  * 根据列编号获取单元格文本的水平居中值。在方法 selfAdapt 中调用,用于渲染表格数据域。
  */
 getTextHorizontalAlignmentByCol: function(col, _this)
 {
  var t = _this.colHeaders[col].textHorizontalAlignment;
  if(t == undefined) return '';
  else return "text-align: " + t + ';';
 },
 /**
  * private
  * 获取渲染后的文本。
  */
 getRenderedText: function(oldValue, row, col, record, _this)
 {
  var t = _this.colHeaders[col].renderer;
  if(t == undefined) return oldValue;
  else return t(oldValue, row, col, record);
 },
 /**
  * private 
  * 判断单元格所在的列要不要隐藏。
  */
 isHiddenColumn: function(col, _this)
 {
  var t = _this.colHeaders[col].hidden;
  
  if(t == undefined || t == false) return false;
  else return true;
 },
 /**
  * private
  * 自适应,也就是根据数据内容重新渲染。
  * 
  * 参数 me:指 Panel 本身
  */
 selfAdapt: function(me)
 {
  me.records.sort(me.getSortAalgorithm(me)); // 根据要被合并的列进行排序
  
  var tbl = $('#' + me.id + '-table');  // 获取表格
  
  tbl.children().remove(); // 移除表下面的所有行,等待添加。
  
  $(me.getHeaderHtml(me)).appendTo(tbl); // 添加标题
  
  
  /**
   * 重新添加所有行
   */
  var row = null;
  for(var i = 0; i < me.records.length; ++i)
  {
   var row = '<tr id="' + (me.id + '-row-' + i) +'" class="table-tag-tr">';
   
   var cel = null; // 表示单元格的 HTML 字符串
   var tha = null; // 数据文本的水平停靠方式
   var rdt = null; // 被渲染后的文本
   var ich = null; // 单元格所在的列是否被隐藏
   for(var j = 0; j < me.records[i].length; j++)
   {
    ich = me.isHiddenColumn(j, me);
    if(ich) continue; // 如果隐藏,就直接跳过渲染
    
    tha = me.getTextHorizontalAlignmentByCol(j, me); 
    rdt = me.getRenderedText(me.records[i][j], i, j, me.records[i], me);
    
    cel = '<td' 
     + ' id="' + (me.id + '-row-' + i + '-col-' + j) +'"' 
     + ' class="table-tag-td"' 
     + ' style="' + tha + '">' + rdt + '</td>';
    row += cel;
   }
   row += '</tr>';
      $(row).appendTo(tbl);
  } 
  
  // 合并单元格
  me.merge(); 
  
  // 添加单元格事件
  $(".table-tag-td").click(function()
  {
   var inf = this.id.split('-');
   me.selectedRowNumber = inf[inf.length - 3];
   me.selectedColNumber = inf[inf.length - 1];
   
   me.cellClickCallback(me, this); // 单元格单机事件
  });
 },
 /**
  * 添加一行。数据应该是数组的形式,注意数据与标题之间的对应。
  * 会自动进行基本校验,如果失败,返回false。
  * 
  * 参数 record:要增加的数据,数组形式
  */
 addRow : function(record)
 {
  // 对数据进行校验
  if(this.records.length > 0 && this.records[0].length != record.length)
  {
   return false;
  }
 
  this.records.push(record);
  
  this.selfAdapt(this);
 },
 /**
  * 根据行号移除一行。移除失败,就返回 false。
  * 
  * 参数 rowNum:被删除行的行号
  */
 removeRow: function(rowNum) // 参数:行号
 {
  if(isNaN(rowNum) || rowNum < 0 || this.records <= rowNum) return false;
  
  this.records.remove(rowNum); // 移除数据
  this.selfAdapt(this);        // 重新渲染
 }
});


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


function tableTag() 
{

 var testdata =
 [
   // 年级,班级,学生,分数,班级平均分
   ['数学', '拓扑学', '张朝阳', 97, 91],
   ['数学', '概率论', '刘向阳', 67, 88],
   ['数学', '分析',   '段明海', 89, 91],
   ['数学', '概率论', '红太阳', 99, 88],
   ['数学', '拓扑学', '章北海', 98, 91],
   ['数学', '概率论', '左明阳', 87, 88],
   
   ['化学', '有机化学', '赵钱孙', 85, 91],
   
   ['物理学', '力学',   '小透', 91, 85],
   ['物理学', '热力学', '小兰', 84, 93],
   ['物理学', '力学',   '小红', 62, 85],
   ['物理学', '电磁学', '小明', 85, 93],
   ['物理学', '力学',   '小绿', 93, 85],
   ['物理学', '电磁学', '大为', 96, 93],

   
   ['化学', '有机化学', '周吴郑', 95, 91]
 ];
 
 
 var c = 0;
 
 /**
  * 一个随机获取数据的方法
  */
 var getARecord = function()
 {
  var cd = testdata[c++];
  if(c == testdata.length)
   c = 0;
  
  return cd;
 };
 
 /**
  * 表格所在 panel 的 id。
  * 
  * panel 的 id 给定了,那么
  * 表格的 id 也确定了,就是 [id]-table;
  * 表格行的 id 也确定了,就是 [id]-row-[row];
  * 单元格的 id 也确定了,就是 [id]-row-[row]-col-[col]
  */
 var id = 'table-tag-panel'; 
 var table = new Ext.ns.my.tag.TagTablePanel(
 {
  id: id,
  colHeaders: 
  [ 
    { 
     header: '学科', width: 60, textHorizontalAlignment: 'center',  
     renderer: function(oldValue, rowNum, colNum, record) 
     {
      return '<a href="#"><b>' + oldValue + "</b></a>";
     }
    }, 
    { header: '课程' }, 
    { header: '讲师', textHorizontalAlignment: 'right' }, 
    { header: '课时', width: 50, hidden: true }, 
    { header: '平均课时', width: 50, textHorizontalAlignment: 'center' }
  ],
  mergeCols: [0, 1, 4],
     title: '<table>标签演示',
     region: 'center', // 放在中心区域
     tbar:
      
     [{
      text: '【增加一行】',
      handler: function(a, b)
      {
       Ext.getCmp(id).addRow(getARecord());
      }
     },
     {
      text: '【删除第4行】',
      handler: function(a, b)
      {
       Ext.getCmp(id).removeRow(3);
      }
     },
     {
      text: '【取值】',
      handler: function(a, b)
      {
       var val = Ext.getCmp(id).getSeletedValue();
       Ext.Msg.alert("数据", val == false ? "尚未选择单元格" : val);
      }
     },
     {
      text: '【改值】',
      handler: function(a, b)
      {
       Ext.getCmp(id).setSeletedValue("XXX");
      }
     }]
 });


 // 创建一个窗口。
 var win = new Ext.Window(
 {
  title:   "HTML标签: <table>",
  width:  600,
  height:  480,
  layout:  'border',
  resizable: false,
  modal:  true,   // 设置窗口为模态
  bodyStyle: { background : '#FFFFFF' },
  items : [ table ],
  buttonAlign: 'center', //居中 
  buttons : 
  [{
   text : "关闭",
   handler : function(){ win.close(); }
  }]
 });

 // 显示窗口
 win.show();
 
}

表格样式 css.


.table-tag-table {
 width: 100%; /* 表格宽度自适应 */
}

.table-tag-tr {
 border: 1px solid black;
}

.table-tag-th {
 background-color: #FF2F2F;
 font-weight: bold;
 text-align: center;
 color: white;
 width: *;
 padding: 10px;
}

.table-tag-td {
 word-break: break-all;
 padding: 10px;
 background-color: #D8E4F3;
 cursor:pointer;
}




分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP