1 /** 2 * @version 1.1 3 * @author ideawu@163.com 4 * @link http://www.ideawu.net/ 5 * @class 6 * 用于显示数据表格的JavaScript控件. 集成的分页控件, 可对表格中的数据集进行客户端分页. 7 * 8 * @param {String} id: HTML节点的id, 控件将显示在该节点中. 9 * @returns {TableView}: 返回分页控件实例. 10 * @requires jQuery {@link PagerView} {@link SortView} 11 * 12 * @example 13 * ### HTML代码: 14 * <div id="my_div"></div> 15 * 16 * ### JavaScript代码: 17 * var table = new TableView('my_div'); 18 * table.dataKey = 'id'; 19 * table.header = { 20 * 'id' : 'Id', 21 * 'name' : 'Name', 22 * }; 23 * 24 * table.add({id:1, name:'Tom'}); 25 * table.render(); 26 */ 27 var TableView = function(id){ 28 // TODO: 实现有序哈希类. 29 /* 因为哈希表的实现可能是元素无序的, 所以使用数组代替. 为此, 定义了数据操作方法. */ 30 function array_index_of_key(arr, key, val){ 31 for(var i in arr){ 32 if(arr[i][key] == val){ 33 return parseInt(i); 34 } 35 } 36 return -1; 37 } 38 39 function array_index_of_item(arr, item){ 40 for(var i in arr){ 41 if(arr[i] == item){ 42 return parseInt(i); 43 } 44 } 45 return -1; 46 } 47 48 function array_get(arr, key, val){ 49 var index = array_index_of_key(arr, key, val); 50 if(index != -1){ 51 return arr[index]; 52 } 53 return false; 54 } 55 56 function array_del(arr, key, val){ 57 var index = array_index_of_key(arr, key, val); 58 if(index != -1){ 59 var a1 = arr.slice(0, index); 60 var a2 = arr.slice(index + 1); 61 return a1.concat(a2); 62 } 63 return arr; 64 } 65 66 var self = this; 67 this.id = id; 68 this._rendered = false; 69 this._filter_text = ''; 70 this.rows = []; 71 this._display_rows = []; // 过滤后的数据集 72 73 /** 74 * 当前控件所处的HTML节点引用. 75 * @type DOMElement 76 */ 77 this.container = null; 78 79 /** 80 * 数据集的每一条记录的唯一标识字段名. 类似数据库表的主键字段名. 81 * @type String 82 */ 83 this.dataKey = ''; 84 /** 85 * 要显示的数据表格的标题. 86 * @type String 87 */ 88 this.title = ''; 89 /** 90 * 要显示的记录的字段, 以及所对应的字段名. 如 'id' : '编号'. 91 * @type Object 92 */ 93 this.header = {}; 94 /** 95 * 集成的分页控件, 可对表格中的数据集进行客户端分页. 96 * @type PagerView 97 */ 98 this.pager = {}; 99 /** 100 * 集成的排序控件, 用于显示分页按钮/链接. 101 * @type SortView 102 */ 103 this.sort = {}; 104 105 /** 106 * @class 107 * 用于确定要显示哪些内部控件, 控件对应的属性为Boolean类型, 取值为true时显示. 108 */ 109 function DisplayOptions(){ 110 /** 111 * 标题 112 * @type Boolean 113 */ 114 this.title = true; 115 /** 116 * 计数 117 * @type Boolean 118 */ 119 this.count = true; 120 /** 121 * 行选择框 122 * @type Boolean 123 */ 124 this.marker = true; 125 /** 126 * 过滤器 127 * @type Boolean 128 */ 129 this.filter = false; 130 /** 131 * 分页 132 * @type Boolean 133 */ 134 this.pager = false; 135 /** 136 * 排序 137 * @type Boolean 138 */ 139 this.sort = false; 140 /** 141 * 是否多选 142 * @type Boolean 143 */ 144 this.multiple = true; 145 /** 146 * 调试 147 * @type Boolean 148 */ 149 this.debug = false; 150 }; 151 152 /** 153 * 用于确定要显示哪些内部控件. 154 * @type TableView-DisplayOptions 155 */ 156 this.display = new DisplayOptions(); 157 158 /** 159 * 获取数据集指定id一条记录. 160 * @returns {Object} 数据集中的一条记录. 161 */ 162 this.get = function(id){ 163 return array_get(this.rows, self.dataKey, id); 164 }; 165 166 /** 167 * 添加一条记录, 如果控件已经被渲染, 会导致一次刷新. 168 * @param {Object} row: 记录对象. 169 */ 170 this.add = function(row){ 171 var index = array_index_of_item(self.rows, row); 172 if(index != -1){ 173 return; 174 } 175 this.rows.push(row); 176 this._display_rows.push(row); 177 if(self._rendered){ 178 self.render(); 179 } 180 }; 181 182 /** 183 * 添加记录列表, 如果控件已经被渲染, 会导致一次刷新. 184 * 用本方法替代连续多次{@link TableView#add()}, 以提高性能. 185 * @param {Array[Object]} rows: 记录对象的数组. 186 */ 187 this.addRange = function(rows){ 188 var index = {}; 189 for(var i in self.rows){ 190 var rid = self.rows[i][self.dataKey]; 191 index[rid] = true; 192 } 193 194 for(var i in rows){ 195 var row = rows[i]; 196 var rid = row[self.dataKey]; 197 198 if(!index[rid]){ 199 this.rows.push(row); 200 this._display_rows.push(row); 201 } 202 } 203 if(self._rendered){ 204 self.render(); 205 } 206 }; 207 208 /** 209 * 删除一个记录对象, 如果控件已经被渲染, 会导致一次刷新. 210 * 可以在调用本方法前, 调用{@link TableView#get()}方法通过id获取要删除的记录对象. 211 * @param {Object} row: 记录对象. 212 */ 213 this.del = function(row){ 214 var rid = row[self.dataKey]; 215 self.rows = array_del(self.rows, self.dataKey, rid); 216 self._display_rows = array_del(self._display_rows, self.dataKey, rid); 217 if(self._rendered){ 218 self.render(); 219 } 220 }; 221 222 /** 223 * 删除记录对象列表, 如果控件已经被渲染, 会导致一次刷新. 224 * 用本方法替代连续多次{@link TableView#del()}, 以提高性能. 225 * @param {Array[Object]} rows: 记录对象的数组. 226 */ 227 this.delRange = function(rows){ 228 var index = {}; 229 for(var i in rows){ 230 var rid = rows[i][self.dataKey]; 231 index[rid] = true; 232 } 233 234 var n_rows = []; 235 for(var i in self.rows){ 236 var row = self.rows[i]; 237 var rid = row[self.dataKey]; 238 if(!index[rid]){ 239 n_rows.push(row); 240 } 241 } 242 self.rows = n_rows; 243 244 var n_rows = []; 245 for(var i in self._display_rows){ 246 var row = self._display_rows[i]; 247 var rid = row[self.dataKey]; 248 if(!index[rid]){ 249 n_rows.push(row); 250 } 251 } 252 self._display_rows = n_rows; 253 254 255 if(self._rendered){ 256 self.render(); 257 } 258 }; 259 260 /** 261 * 内部方法. 用于全选或者取消全选行. 262 */ 263 this._toggleSelect = function(){ 264 var c = $(self.container).find('th.marker input[type=checkbox]')[0]; 265 if(c.checked){ 266 self.selectAll(); 267 }else{ 268 self.unselectAll(); 269 } 270 }; 271 272 /** 273 * 使用者重写本方法, 进行行双击回调. 274 * @param {int} id: 双击行的主键值. 275 * @event 276 */ 277 this.dblclick = function(id){ 278 }; 279 280 /** 281 * 内部方法, 行双击时调用. 282 */ 283 this._dblclick = function(id){ 284 self.dblclick(id); 285 }; 286 287 /** 288 * 获取当前可显示的数据数. 289 * @returns {int} 290 */ 291 this.rowCount = function(){ 292 var n = 0; 293 for(var i in self._display_rows){ 294 n ++; 295 } 296 return n; 297 }; 298 299 /** 300 * 更新统计数据. 301 */ 302 this._update_meta = function(){ 303 if(!self.display.count){ 304 return; 305 } 306 var marked_count = 0; 307 marked_count = $(self.container).find('.datagrid td.marker input[value!=""]:checked').length; 308 $(self.container).find('.datagrid_meta span.marked_count').html(marked_count); 309 $(self.container).find('.datagrid_meta span.row_count').html(self.rowCount()); 310 } 311 312 /** 313 * 内部方法. 绑定事件, 设置外观. 314 */ 315 this._after_render = function(){ 316 $(self.container).find('table.datagrid>tbody>tr.tv_row').each(function(i, tr){ 317 var cb = tr.getElementsByTagName('input')[0]; 318 319 var clz = i%2==0? 'odd' : 'even'; 320 $(tr).removeClass('odd even'); 321 $(tr).addClass(clz); 322 323 // 标记已选的行 324 if(cb.checked){ 325 $(tr).addClass('marked'); 326 }else{ 327 $(tr).removeClass('marked'); 328 } 329 cb.onclick = function(){ 330 cb.checked = !cb.checked; 331 }; 332 tr.onclick = function(){ 333 if(!cb.checked){ 334 if(!self.display.multiple){ 335 self.unselectAll(); 336 } 337 } 338 cb.checked = !cb.checked; 339 if(cb.checked){ 340 $(tr).addClass('marked'); 341 }else{ 342 $(tr).removeClass('marked'); 343 } 344 self._update_meta(); 345 }; 346 tr.onmouseover = function(){ 347 $(tr).addClass('hover'); 348 }; 349 tr.onmouseout = function(){ 350 $(tr).removeClass('hover'); 351 }; 352 tr.ondblclick = function(){ 353 self._dblclick(cb.value); 354 }; 355 }); 356 357 self._update_meta(); 358 359 $(self.container).find('.datagrid_meta .title').css('display', self.display.title? '':'none'); 360 $(self.container).find('.datagrid_meta .count').css('display', self.display.count? '':'none'); 361 $(self.container).find('.datagrid_meta .filter').css('display', self.display.filter? '':'none'); 362 $(self.container).find('#' + self.pager.id).css('display', self.display.pager? '':'none'); 363 $(self.container).find('.datagrid_div .datagrid th.marker,.datagrid_div .datagrid td.marker') 364 .css('display', self.display.marker? '':'none'); 365 }; 366 367 /** 368 * 内部方法, 渲染视图框架. 369 */ 370 this._render_framework = function(){ 371 var str = ''; 372 str += '<div class="TableView">\n'; 373 str += '<div class="datagrid_meta">\n'; 374 str += '<span class="title">' + this.title + '</span>'; 375 str += '<span class="count">(<span class="marked_count">0</span>/<span class="row_count">0</span>)</span>'; 376 str += ' <span class="filter"><label>模糊过滤</label>'; 377 str += '<input type="text" value="' + this._filter_text + '"' 378 + ' onkeyup="document.getElementById(\'' + this.id + '\').view.filter(this.value)" />'; 379 str += '</span>\n'; 380 str += '</div>\n'; 381 382 str += '<div class="datagrid_div">\n'; 383 str += '</div><!-- /.datagrid_div -->\n'; 384 385 var pager_id = self.id + '_pager__'; 386 str += '<div id="' + pager_id + '" class="pager"></div>\n'; 387 388 // debug 389 var debug_div_id = self.id + '_debug'; 390 str += '<div id="' + debug_div_id + '"></div>\n'; 391 392 str += '</div><!-- /.TableView -->\n'; 393 394 var div = document.getElementById(self.id); 395 div.view = self; 396 self.container = div; 397 self.container.innerHTML = str; 398 399 // debug 400 self._debug = $('#' + debug_div_id); 401 402 // 捕获异常, 可以不需要PagerView工作 403 try{ 404 self.pager = new PagerView(pager_id); 405 self.pager.onclick = function(index){ 406 self.render(); 407 }; 408 409 self.sort = new SortView(); 410 self.sort.onclick = function(field, order){ 411 self.sort.sort(self._display_rows); 412 self.render(); 413 }; 414 }catch(e){ 415 } 416 }; 417 418 self._render_framework(); 419 420 // DEBUG 421 function debug(str){ 422 if(self.display.debug){ 423 self._debug.css('border', '2px solid #f00'); 424 self._debug.append(str + '<br/>'); 425 } 426 } 427 428 /** 429 * 渲染控件. 430 */ 431 this.render = function(){ 432 var str = ''; 433 str += '<table class="datagrid"><tbody>\n'; 434 str += '<tr>\n'; 435 str += '<th class="marker" width="10">'; 436 if(self.display.multiple){ 437 str += '<input type="checkbox" value="" onclick="document.getElementById(\'' + this.id + '\').view._toggleSelect()" />'; 438 } 439 str += '</th>\n'; 440 for(var k in this.header){ 441 str += '<th field="' + k + '">' + self.header[k] + '</th>\n'; 442 } 443 str += "</tr>\n"; 444 445 if(self.display.sort){ 446 self.sort.sort(self._display_rows); 447 } 448 if(self.display.pager){ 449 self.pager.itemCount = self._display_rows.length; 450 var rows = self.pager.page(self._display_rows); 451 }else{ 452 var rows = self._display_rows; 453 } 454 for(var i in rows){ 455 var row = rows[i]; 456 var rid = row[self.dataKey]; 457 str += '<tr class="tv_row">\n'; 458 str += '<td class="marker" width="10">'; 459 str += '<input type="checkbox" value="' + rid + '" />'; 460 str += '</td>\n'; 461 for(var k in self.header){ 462 str += '<td>' + row[k] + '</td>\n'; 463 } 464 str += '</tr>\n'; 465 } 466 str += "</tbody></table>\n"; 467 $(self.container).find('.datagrid_meta .title').html(this.title); 468 $(self.container).find('.datagrid_div').html(str); 469 470 self._after_render(); 471 472 if(self.display.pager){ 473 self.pager.render(); 474 } 475 if(self.display.sort){ 476 if(true){ 477 var fields = {}; 478 for(var k in self.header){ 479 fields[k] = [null, null]; 480 } 481 self.sort.fields = fields; 482 } 483 var elements = {}; 484 $(self.container).find('table.datagrid th[field]').each(function(i, th){ 485 var k = $(th).attr('field'); 486 if(k != undefined){ 487 elements[k] = th; 488 } 489 }); 490 self.sort.render(elements); 491 } 492 493 self._rendered = true; 494 }; 495 496 /** 497 * 设置所有行的选择标记. 如果设置了分页, 则只对当前页有效. 498 */ 499 this.selectAll = function(){ 500 $(self.container).find('th.marker input').attr('checked', 'checked'); 501 $(self.container).find('td.marker input').attr('checked', 'checked'); 502 self._after_render(); 503 }; 504 505 /** 506 * 取消所有行的选择标记. 如果设置了分页, 则只对当前页有效. 507 */ 508 this.unselectAll = function(){ 509 $(self.container).find('th.marker input').attr('checked', ''); 510 $(self.container).find('td.marker input').attr('checked', ''); 511 self._after_render(); 512 }; 513 514 /** 515 * 返回所有的记录的列表. 516 * @returns {Array[Object]} 517 */ 518 this.getDataSource = function(){ 519 return self.rows; 520 } 521 522 /** 523 * 获取所有标记为选择的行对应的记录的列表. 524 * @returns {Array[Object]} 525 */ 526 this.getSelected = function(){ 527 var items = []; 528 $(self.container).find('.datagrid td.marker input[value!=""]:checked').each(function(i, cb){ 529 if(cb.checked){ 530 // 注意, 不要作为hash使用, 否则会导致使用者判断选中个数时错误. 531 var row = array_get(self.rows, self.dataKey, cb.value); 532 items.push(row); 533 } 534 }); 535 536 return items; 537 }; 538 539 /** 540 * 获取所有已选择的数据对象键值的列表. 541 * @returns {Array[Key]} 542 */ 543 this.getSelectedKeys = function(){ 544 var keys = []; 545 var rows = self.getSelected(); 546 for(var i in rows){ 547 keys.push(rows[i][self.dataKey]); 548 } 549 return keys; 550 }; 551 552 /** 553 * 进行模糊过滤. 554 * @param {String} text: Regex字符串. 555 */ 556 this.filter = function(text){ 557 self._filter_text = text; 558 self._display_rows = []; 559 560 var regex = new RegExp(text.replace('\\', '\\\\'), 'i'); 561 for(var key in self.rows){ 562 var row = self.rows[key]; 563 if(text == ''){ 564 self._display_rows.push(row); 565 }else{ 566 // 只对看到的进行过滤 567 for(var k in self.header){ 568 var find = String(row[k]).replace(/<[^>]*>/g, ''); 569 if(regex.test(find)){ 570 self._display_rows.push(row); 571 break; 572 } 573 } 574 } 575 } 576 577 if(self.display.pager){ 578 self.pager.index = 1; 579 } 580 self.render(); 581 }; 582 583 /** 584 * 清空所有行. 585 */ 586 this.clear = function(){ 587 self.rows = []; 588 self._display_rows = []; 589 590 if(self.display.pager){ 591 self.pager.index = 1; 592 } 593 self.render(); 594 }; 595 } 596