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