zhuguifei
2026-03-10 58402bd5e762361363a0f7d7907153c77dbb819f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
/*
    数据表格
    source: 除delete之外的所有
    view:   当前数据视图,用于配合table,tree显示
    
1.source    是完整的数据表格
2.view      是过滤,排序后的数据表格
3.idHash    保存的是source的id引用?
 
是不是加一个原则:
    1.source    是完整的数据, 可以增删改
    2.view      只是数据视图,只能排序,过滤,但是不能进行增删改操作
    
    就像sql2000数据库的逻辑一样
*/
/**
    @name Edo.data.DataTable
    @class 
    @typeName datatable
    @description 表格数据源
    @extend Edo.core.Component
    @example 
*/ 
Edo.data.DataTable = function(data){
    Edo.data.DataTable.superclass.constructor.call(this);
    
    this.source = this.view = [];
    this.modified = {};
    this.removed = {};      
    this.idHash = {};
    
    //if(data) 
    this.load(data);
    
    //this._setConn(new Edo.data.PagingConnection());
/**
    @name Edo.data.DataTable#datachange
    @event 
    @description 数据改变事件
    @property {String} action 数据改变动作(add,remove,update,move,clear,reset,resetfield,beforeload,load,refresh,filter,collapse,expand,sort)
 */
}
Edo.data.DataTable.extend(Edo.core.Component, {
    componentMode: 'data',
    
    dataTable: true,
    
/**
    @name Edo.data.DataTable#source
    @property 
    @type Array
    @description 源Array数组对象
 */    
 /**
    @name Edo.data.DataTable#view
    @property 
    @type Array
    @description 数据视图(如过滤/排序/折叠后的数据视图)
 */    
    _setData: function(data){
        this.load(data);
    },
    
    refresh: function(view, action, event){                //刷新数据视图
        if(view){
            this.length = 0;
            this.view = view;
        }
        this.fire(action || 'refresh', event);                 //数据组件,只需要监听refresh事件即可!
    },
    /**
        @name Edo.data.DataTable#load
        @function
        @description 加载数据
        @param {Array} data 数组形式的数据
    */      
    load: function(data, view){
        if(!data) data = [];
        var e = {data: data};
        if(this.fire('beforeload', e) !== false){   //这是属于"破坏"性的操作,如果有必要,要保存原来的操作结果,请使用beforeload事件
            
            this._doLoad(data);
            
            this.view = this.source = data;       //更新原始数据                    
            this.modified = {};
            this.removed = {};
            //this.idHash = {};
            
            if(view) this.view = view;
            
            this.changed = false;   //修改changed标记(表示数据是没有经过任何数据操作:增,删,改)
            
            this.canFire = true;
            
            this.fire('load', e);
        }
    },  
    /**
        @name Edo.data.DataTable#reload
        @function
        @description 重加载数据, 去除新增/删除/修改的数据标记        
    */  
    reload: function(){
        this.load(this.source, this.view);
    },
    /**
        @name Edo.data.DataTable#add
        @function
        @description 增加数据
        @param {Object} record 单个对象
    */      
    add: function(record){           
        this.insert(this.view.length, record);
    },
    /**
        @name Edo.data.DataTable#addRange
        @function
        @description 增加数据
        @param {Array} records 多个对象
    */  
    addRange: function(records){           
        this.insertRange(this.view.length, records);
    },
    /**
        @name Edo.data.DataTable#insert
        @function
        @description 增加数据
        @param {Number} index 
        @param {Object} record 单个对象  
    */
    insert: function(index, record){
        this.insertRange(index, [record]);
    },
    /**
        @name Edo.data.DataTable#insertRange
        @function
        @description 增加多个数据
        @param {Number} index
        @param {Array} records 数组对象        
    */
    insertRange: function(index, records){
        if(!records || !(records instanceof Array)) return;  
        
        var d = this.source, v = this.view;
        
        for(var i = 0, len = records.length; i < len; i++){
            var r = records[i];            
            v.insert(index, r);
            
            if(d !== v) d.insert(index, r);         //在源数据上也新增
            
            this._doAdd(r);
        }
        this.fire('add', {
            records: records, 
            index: index
        });
        this.changed = true;
    },
    
    /**
        @name Edo.data.DataTable#remove
        @function
        @description 删除数据
        @param {Object} record 数据对象
    */      
    remove: function(record){                
        this.removeRange([record]);
    },
    /**
        @name Edo.data.DataTable#removeAt
        @function
        @description 根据索引删除数据
        @param {Number} index
    */     
    removeAt: function(index){
        this.remove(this.getAt(index));
    },
    /**
        @name Edo.data.DataTable#removeRange
        @function
        @description 删除数据
        @param {Array} records 数据对象数组
    */
    removeRange: function(records){
        if(!records || !(records instanceof Array)) return;  
        records = records.clone();
        
        for(var i=0,l=records.length; i<l; i++){
            var record = records[i];
            var index = this.view.indexOf(record);
            if(index > -1){
                this.view.removeAt(index);
                this.source.remove(record);             //从源数据上也删除
//                delete this.idHash[record.__id];
//                delete this.modified[record.__id];
                
                this._doRemove(record);                
            }
        }
        
        this.fire('remove', {records: records});
        this.changed = true;
    },
    /**
        @name Edo.data.DataTable#updateRecord
        @function
        @description 批量更新数据对象属性值
        @param {Object} record 数据对象
        @param {Object} properties {key: value, ..}形式的对象
    */        
    updateRecord: function(record, o){
        var canFire = this.canFire;
        this.beginChange();
        for(var p in o){
            this.update(record, p, o[p]);
        }
        this.canFire = canFire;
        this.fire('update',{
            record: record
        });
        this.changed = true;
    },
    /**
        @name Edo.data.DataTable#update
        @function
        @description 更新数据对象属性值
        @param {Object} record 数据对象
        @param {String} field 属性名
        @param {Object} value 属性值
    */  
    update: function(record, field, value){
        var old = Edo.getValue(record, field);
        //var old = record[field];
        //alert(old+":"+value);
        var type = typeof(old);
        if(type == 'object'){
            if(old === value) return false;
        }
        else if(String(old) == String(value)) return false;
        
        //record[field] = value;
        Edo.setValue(record, field, value);
        
        //to data
        this._doUpdate(record, field, old);
        
        this.fire('update',{
            record: record,
            field: field,
            value: value
        });
        this.changed = true;
        return true;
    },
    /**
        @name Edo.data.DataTable#move
        @function
        @description 移动数据对象
        @param {Object} record 数据对象
        @param {Number} index
    */       
    move: function(records, index){//index:数字, 行对象       
     
        var target = Edo.isInt(index) ? this.getAt(index) : index;
        
        if(!Edo.isArray(records)) records = [records];
        
        for(var j=0,l=records.length; j<l; j++){
            var record = records[j];
            
            this.view.remove(record);
            
            if(target){
                var index = this.indexOf(target);
                this.view.insert(index, record);
            }else{
                this.view.add(record);
            }
        }
        
        //alert(this.indexOf(target));       
        this.fire('move',{
            records: records,
            index: this.indexOf(target)
        });
        
        this.changed = true;
        
    },
    /**
        @name Edo.data.DataTable#clear
        @function
        @description 清楚数据
    */       
    clear: function(){
        this.source = this.view = [];
        this.modified = {};
        this.removed = {};
        this.fire('clear');
        this.changed = true;
    },    
    /**
        @name Edo.data.DataTable#reset
        @function
        @description 还原行数据对象,一个被修改过的行对象, 换到到修改前
        @param {Object} record 数据对象        
    */     
    reset: function(record){
        var r = this.modified[record.__id];
        if(r){
            Edo.apply(record, r);
            
            delete this.modified[record.__id];
            delete record.status;
            
            this.fire('reset',{
                record: record
            });
        }
    },    
    /**
        @name Edo.data.DataTable#resetField
        @function
        @description 还原行数据对象的某个属性
        @param {Object} record 数据对象        
        @param {String} field 属性名        
    */     
    resetField: function(record, field){
        var r = this.modified[record.__id];
        if(r){
            var v = r[field];
            if(typeof v !== 'undefined'){
                delete r[field];
                record[field] = v;
                
                this.fire('resetfield',{
                    record: record,
                    field: field,
                    value: v
                });
            }
        }
    }, 
    /**
        @name Edo.data.DataTable#sort
        @function
        @description 排序数据
        @param {Function} sortFn 排序算法函数        
    */   
    sort: function(sortFn){
//        function sortFn(pre, now){
//            if(fn(pre, now) == true) return 1;
//            else return -1;
//        }
        //可以自己实现一个优化排序算法
        this.view.sortByFn(sortFn);
        if(this.view !== this.source){
            this.source.sortByFn(sortFn);    
        }
        this.fire('sort');
    },    
    /**
        @name Edo.data.DataTable#sortField
        @function
        @description 针对某一个field行属性进行升降排序
        @param {String} field 属性名
        @param {String} direction 排序方向.ASC或DESC
    */
    sortField : function(field, direction){
        direction = direction || 'ASC';
        var fn;
        if(direction.toUpperCase() == 'ASC'){
            fn = function(r1, r2){
                var v1 = r1[f], v2 = r2[f];
                return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
            };
        }else{
            fn = function(r1, r2){
                var v1 = r1[f], v2 = r2[f];
                return v1 < v2 ? 1 : (v1 > v2 ? -1 : 0);
            };
        }
        this.sort(fn);
    },
    /**
        @name Edo.data.DataTable#filter
        @function
        @description 过滤数据
        @param {Function} fn 过滤算法函数
        @param {Object} scope 过滤函数中的this指向对象
    */
    filter: function(fn, scope){
        var view = this.view = [];
        var data = this.source;
        for (var i = 0, j = data.length; i < j; i++){
            var record = data[i];
            if (fn.call(scope, record, i, data) !== false) view[view.length] = record;
        }
        this.fire('filter');
    },
    /**
        @name Edo.data.DataTable#clearFilter
        @function
        @description 清除过滤, 还原数据视图
    */
    clearFilter : function(){
        this.filterFn = null;
        if(this.isFiltered()){
            this.view = this.source;         
            this.fire('filter');
        }
    },
    /**
        @name Edo.data.DataTable#isFiltered
        @function
        @description 判断数据视图是否被过滤过
        @return Boolean
    */
    isFiltered: function(){
        return this.view.length != this.source.length;
    },
    //查找:传递一个object,找出属性符合最多的对象的Index.
    //这其实是个很复杂的概念:1)默认是从当前数据视图查找;2)如果传递source对象,就从source对象查找(并且如果是删除的数据,不会查找出来)
    /**
        @name Edo.data.DataTable#findIndex
        @function
        @description 查找一个符合特征的对象
        @param {Object} attributes {key:value}形式的特性对象
        @return {Number} 
    */
    findIndex: function(attribute, array){
        if(array === true) array = this.source;
        array = array || this.view;
        for(var i=0,l=array.length; i<l; i++){
            var o = array[i];
            if(o === attribute) return i;
            
            var all = true;     //全部匹配才行
            for(var p in attribute){
                if(attribute[p] != o[p]) {
                    all = false;
                    break;
                }
            }
            if(all) return i;
        }
        return -1;
    },
    /**
        @name Edo.data.DataTable#find
        @function
        @description 查找一个符合特征的对象
        @param {Object} attributes {key:value}形式的特性对象
        @return {Object} 
    */
    find: function(attribute, array){
        if(array !== false) array = this.source;
        array = array || this.view;
        return array[this.findIndex(attribute, array)];
    },       
    /**
        @name Edo.data.DataTable#getById
        @function
        @description 根据id查找对象(从源数据)
        @param {String} id row.__id
        @return {Object} 
    */
    getById: function(id){
        return this.idHash[id];
    },
    /**
        @name Edo.data.DataTable#getViewById
        @function
        @description 根据id查找对象(从数据视图)
        @param {String} id row.__id
        @return {Object} 
    */
    getViewById: function(id){
        return this.viewHash[id];
    },
    /**
        @name Edo.data.DataTable#getAt
        @function
        @description 根据index查找对象(从数据视图)
        @param {Number} index
        @return {Object} 
    */
    getAt: function(index){
        return this.view[index];
    },
    /**
        @name Edo.data.DataTable#indexOf
        @function
        @description 根据对象查找对象的索引号
        @param {Object} record
        @return {Number} 
    */
    indexOf: function(record){
        return this.view.indexOf(record);
    },
    /**
        @name Edo.data.DataTable#indexOfId 
        @function
        @description 根据id查找对象的索引号
        @param {String} id
        @return {Number} 
    */
    indexOfId : function(id){
        var r = this.idHash[id];
        return r ? this.indexOf(r) : -1;
    },
    /**
        @name Edo.data.DataTable#getCount 
        @function
        @description 数据视图行对象数目     
        @return {Number} 
    */
    getCount: function(){
        return this.view.length;
    },
    /**
        @name Edo.data.DataTable#isEmpty 
        @function
        @description 是否是空数据     
        @return {Boolean} 
    */
    isEmpty: function(){
        return this.view.length == 0;
    },
    /**
        @name Edo.data.DataTable#each 
        @function
        @description 遍历数据视图     
        @param {Function} fn
        @param {Object} scope
    */
    each : function(fn, scope){
        this.view.each(fn, scope);
    },
    fire: function(action, event){
        if(this.canFire){                    
            event = Edo.apply({
                type: 'datachange',
                action: action,
                source: this
            }, event);
            this._onFire(event);
            
            if(['beforeload', 'load', 'refresh', 'filter', 'collapse', 'expand', 'sort'].indexOf(action) == -1){
                this.changed = true;
            }
            
            return this.fireEvent('datachange', event);
        }
    },    
    /**
        @name Edo.data.DataTable#isModify 
        @function
        @description 判断一个行数据是否被修改过     
        @param {Object} record
        @return {Boolean}
    */
    isModify: function(record){
        var ro = this.modified[record.__id];
        if(!ro) return false;
        return true;
    },   
    /**
        @name Edo.data.DataTable#isFieldModify 
        @function
        @description 判断一个行数据的某个属性是否被修改过     
        @param {Object} record
        @param {String} field
        @return {Boolean}
    */ 
    isFieldModify: function(record, field){
        var ro = this.modified[record.__id];
        if(!ro) return false;
        if(typeof(ro[field]) !== 'undefined') return true;
        return false;
    },        
    /**
        @name Edo.data.DataTable#beginChange 
        @function
        @description 开始改变数据(这时候调用add,update,remove等方法是不会激发datachange事件的)        
    */ 
    beginChange: function(){
        this.canFire = false;
    },    
    /**
        @name Edo.data.DataTable#endChange 
        @function
        @description 结束改变数据(激发datachange事件)
    */ 
    endChange: function(action, event){
        this.canFire = true;
        this.refresh(null, action, event);        
    },
    /**
        @name Edo.data.DataTable#getDeleted 
        @function
        @description 获得删除的数据       
        @return {Array}
    */ 
    getDeleted: function(){
        var rs = [];
        var os = this.removed;
        for(var id in os){
            var o = os[id];
            if(o.__status == 'remove'){
                rs.add(os[id]);
            }
        }
        return rs;
    },
    /**
        @name Edo.data.DataTable#getDeleted 
        @function
        @description 获得修改的数据
        @param {String} idField 行记录的唯一值属性,比如id, uid等
        @return {Array}
    */ 
    getUpdated: function(idField){   
        idField = idField ||  this.idField;    
        var rs = [];
        var os = this.modified;        
        for(var id in os){
            var o = os[id];
            var r = this.getById(id);
            o[idField] = r[idField];
            rs.add(o);
            
            for(var p in o){
                o[p] = r[p];
            }
        }
        return rs;
    },
    /**
        @name Edo.data.DataTable#getAdded 
        @function
        @description 获得新增的数据       
        @return {Array}
    */
    getAdded: function(){
        var rs = [];   
        for(var i=0,l=this.source.length; i<l; i++){
            var o = this.source[i];
            if(o.__status == 'add'){
                rs.add(o);
            }
        }
        return rs;
    },
    _doLoad: function(data){
        var idHash = this.idHash = {};    //load的时候,清空原有的idHash
        var id = Edo.data.DataTable.id;
        for(var i=0,l=data.length; i<l; i++){
            var r = data[i];
            if(!r.__id || idHash[r.__id]) r.__id = id++;        //千万不能动...
            //r.__id = id++;
            idHash[r.__id] = r;
            delete r.__status;
            //r.__status = 'normal';
        }
        Edo.data.DataTable.id = id;
    },
    _doAdd: function(record){
        if(record.__id){
            var r = this.idHash[record.__id];
            if(r){
                if(r !== record){
                    record.__id = null;
                }
            }
        }
        if(!record.__id) {
            record.__id = Edo.data.DataTable.id++;
        }
        this.idHash[record.__id] = record;
        if(!record.__status) record.__status = 'add';
        else delete record.__status;
    },
    _doRemove: function(record){
        if(record.__status != 'add'){
            record.__status = 'remove';
        }
        this.removed[record.__id] = record;
        delete this.idHash[record.__id];
        delete this.modified[record.__id];
        //delete record.__id;
    },
    idField: 'id',
    _doUpdate: function(record, field, value){
        if(record.__status != 'add'){
            record.__status = 'update';
            var erow = this.modified[record.__id];
            if(!erow){
                erow = this.modified[record.__id] = {};
            }
            if(typeof erow[field] === 'undefined') erow[field] = value;   //只保存一次,初始值
        }
    },
    _onFire: function(e){
        if(e.action == 'update') return;      
        var viewHash = this.viewHash = {};         //viewHash是可视的数据视图快速索引  
        var view = this.view;
        for(var i=0,l=view.length; i<l; i++){
            var r = view[i];
            viewHash[r.__id] = r;
            r.__index = i;
        }
    },
    
    getRecord: function(record){
        var type = Edo.type(record);
        if(type == 'number'){
            record = this.getAt(record);
        }else if(type == 'string'){
            record = this.getById(record);
        }
        return record;
    },
    
    //数据指针:选中状态逻辑(单选)    
    selected: null,
    getSelectedIndex: function(){
        return this.indexOf(this.selected);
    },
    getSelected: function(){
        if(!this.selected || this.indexOf(this.selected) == -1) {
            this.selected = null;
            return null;
        }            
        return this.selected;
    },
    isSelected: function(record){
        record = this.getRecord(record);
        if(record == this.selected) return true;
        return false;
    },
    select: function(record){
        record = this.getRecord(record);        
        if(!record || this.isSelected(record)) return false;
        if(this.fireEvent('beforeselectionchange',{
            type: 'beforeselectionchange',
            source: this,
            selected: record
        }) === false) return false;
        this.selected = record;
        this.fireEvent('selectionchange', {
            type: 'selectionchange',
            source: this,
            selected: this.selected
        });
    },
    deselect: function(record){
        record = this.getRecord(record);
        if(!record || !this.isSelected(record)) return false;
        if(this.fireEvent('beforeselectionchange',{
            type: 'beforeselectionchange',
            source: this,
            selected: this.selected,
            deselected: record
        }) === false) return false;
        this.selected = null;        
        this.fireEvent('selectionchange', {
            type: 'selectionchange',
            source: this,
            selected: this.selected
        });
    },
    clearSelect: function(){
        this.deselect(this.selected);
    },
    firstSelect: function(){
        this.select(0);
    },
    prevSelect: function(){
        this.select(this.getSelectedIndex() - 1);
    },
    nextSelect: function(){
        this.select(this.getSelectedIndex() + 1);
    },
    lastSelect: function(){
        this.select(this.getCount()-1);
    }
});
 
Edo.data.DataTable.id = 1000;
 
Edo.data.DataTable.regType('datatable');
 
 
Edo.data.cloneData = function(o){
    var c = null;
    if(o instanceof Array){
        c = o.clone();
    }else{
        c = Edo.apply({}, o);
    }
    delete c.__id;    
    delete c.__index;
    delete c.__status;
    
    delete c.__pid;
    delete c.__preid;
    delete c.__nextid;
    delete c.__hasChildren
    
    return c;
}
 
/*
    一些思考:
    
    1.索引, 新增, 删除等, 应该是在source进行操作.
    2.view的index是不准的, 应该通过record来锚记和定位.
    3.是否默认的数据操作是针对source的???       也不太好
    
*/