Subversion Repositories Integrator Subversion

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 espaco 1
/* =========================================================
2
 * bootstrap-gtreetable v2.2.1-alpha
3
 * https://github.com/gilek/bootstrap-gtreetable
4
 * =========================================================
5
 * Copyright 2014 Maciej Kłak
6
 * Licensed under MIT (https://github.com/gilek/bootstrap-gtreetable/blob/master/LICENSE)
7
 * ========================================================= */
8
 
9
(function ($) {
10
    // GTREETABLE CLASSES DEFINITION
11
    // =============================    
12
    function GTreeTable(element, options) {
13
        this.options = options;
14
        this.$tree = $(element);
15
        this.language = this.options.languages[this.options.language] === undefined ?
16
            this.options.languages['en-US'] :
17
            $.extend({}, this.options.languages['en-US'], this.options.languages[this.options.language]);
18
        this._isNodeDragging = false;
19
        this._lastId = 0;
20
 
21
        this.actions = [];
22
        if (this.options.defaultActions !== null) {
23
            this.actions = this.options.defaultActions;
24
        }
25
 
26
        if (this.options.actions !== undefined) {
27
            this.actions.push.apply(this.actions, this.options.actions);
28
        }        
29
 
30
        if (this.options.cache > 0) {
31
            this.cacheManager = new GTreeTableCache(this);
32
        }
33
 
34
        var lang = this.language;
35
        this.template = this.options.template !== undefined ? this.options.template :
36
           '<table class="table gtreetable">' +
37
           '<tr class="' + this.options.classes.node + ' ' + this.options.classes.collapsed + '">' +
38
           '<td>' +
39
           '<span>${draggableIcon}${indent}${ecIcon}${selectedIcon}${typeIcon}${name}</span>' +
40
           '<span class="hide ' + this.options.classes.action + '">${input}${saveButton} ${cancelButton}</span>' +
41
           '<div class="btn-group pull-right ' + this.options.classes.buttons + '">${actionsButton}${actionsList}</div>' +
42
           '</td>' +
43
           '</tr>' +
44
           '</table>';            
45
 
46
      this.templateParts = this.options.templateParts !== undefined ? this.options.templateParts :
47
            {
48
                draggableIcon: this.options.draggable === true ? '<span class="' + this.options.classes.handleIcon + '">&zwnj;</span><span class="' + this.options.classes.draggablePointer + '">&zwnj;</span>'  : '',
49
                indent: '<span class="' + this.options.classes.indent + '">&zwnj;</span>',
50
                ecIcon: '<span class="' + this.options.classes.ceIcon + ' icon"></span>',
51
                selectedIcon: '<span class="' + this.options.classes.selectedIcon + ' icon"></span>',
52
                typeIcon: '<span class="' + this.options.classes.typeIcon + '"></span>',
53
                name: '<span class="' + this.options.classes.name + '"></span>',
54
                input: '<input type="text" name="name" value="" style="width: ' + this.options.inputWidth + '" class="form-control" />',
55
                saveButton: '<button type="button" class="btn btn-sm btn-primary ' + this.options.classes.saveButton + '">' + lang.save + '</button>',
56
                cancelButton: '<button type="button" class="btn btn-sm ' + this.options.classes.cancelButton + '">' + lang.cancel + '</button>',
57
                actionsButton: '<button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">' + lang.action + ' <span class="caret"></span></button>',
58
                actionsList: ''
59
            };
60
 
61
        if (this.actions.length > 0) {
62
            var templateActionsList = '<ul class="dropdown-menu" role="menu">' +
63
            '<li role="presentation" class="dropdown-header">' + lang.action + '</li>';
64
 
65
            $.each(this.actions, function (index, action) {
66
                if (action.divider === true) {
67
                    templateActionsList += '<li class="divider"></li>';
68
                }
69
                else {
70
                    var matches = action.name.match(/\$\{([\w\W]+)\}/),
71
                        name = matches !== null && matches[1] !== undefined && lang.actions[matches[1]] !== undefined ? lang.actions[matches[1]] : action.name;
72
                    templateActionsList += '<li role="presentation"><a href="#notarget" class="node-action-' + index + '" tabindex="-1">' + name + '</a></li>';
73
                }
74
            });        
75
 
76
            templateActionsList += '</ul>';
77
            this.templateParts.actionsList = templateActionsList;
78
        }
79
 
80
        var template = this.template;
81
 
82
        $.each(this.templateParts, function(index, value) {
83
            template = template.replace('${'+index+'}', value);
84
        });
85
 
86
        this.options.template = template;
87
 
88
 
89
        if (this.$tree.find('tbody').length === 0) {
90
            this.$tree.append('<tbody></tbody>');
91
        }
92
 
93
        if (!this.options.readonly) {
94
            this.$tree.addClass('gtreetable-fullAccess');
95
        }
96
 
97
        this.$nodeTemplate = $(this.options.templateSelector !== undefined ?
98
            this.options.templateSelector :
99
            this.options.template).find('.' + this.options.classes.node);
100
 
101
        if (this.options.draggable === true) {
102
            this.isNodeDragging(false);
103
        }
104
        this.init();
105
    }
106
 
107
    GTreeTable.prototype = {
108
 
109
        getNode: function ($node) {
110
            return $node.data('bs.gtreetable.gtreetablenode');
111
        },
112
 
113
        getNodeById: function (id) {
114
            return this.getNode(this.$tree.find('.' + this.options.classes.node + "[data-id='" + id + "']"));
115
        },
116
 
117
        getSelectedNodes: function () {
118
            var selectedNodes = [],
119
                that = this;
120
            $.each(this.$tree.find('.' + this.options.classes.selected), function () {
121
                selectedNodes.push(that.getNode($(this)));
122
            });
123
 
124
            return selectedNodes;
125
        },
126
 
127
        getSourceNodes: function (nodeId, force) {
128
            var that = this,
129
                oNode = this.getNodeById(nodeId),
130
                cached = (nodeId > 0 && this.options.cache > 0);
131
 
132
            if (cached && force !== true) {
133
                var data = this.cacheManager.get(oNode);
134
                if (data !== undefined) {
135
                    var temp = {};
136
                    temp[that.options.nodesWrapper] = data;
137
                    return temp;
138
 
139
                }
140
            }
141
 
142
            var sourceOptions = this.options.source(nodeId);
143
            var defaultSourceOptions = {
144
                beforeSend: function () {
145
                    if (nodeId > 0) {
146
                        oNode.isLoading(true);
147
                    }
148
                },
149
                success: function (result) {
150
                    if (result[that.options.nodesWrapper] !== undefined) {
151
                        data = result[that.options.nodesWrapper];
152
                        for (var x = 0; x < data.length; x += 1) {
153
                            data[x].parent = nodeId;
154
                        }
155
 
156
                        if (typeof that.options.sort === "function") {
157
                            data.sort(that.options.sort);
158
                        }
159
 
160
                        if (cached) {
161
                            that.cacheManager.set(oNode, data);
162
                        }
163
                    }
164
                },
165
                error: function (XMLHttpRequest) {
166
                    alert(XMLHttpRequest.status + ': ' + XMLHttpRequest.responseText);
167
                },
168
                complete: function () {
169
                    if (nodeId > 0) {
170
                        oNode.isLoading(false);
171
                    }
172
                }
173
            };
174
 
175
            return $.ajax($.extend({}, defaultSourceOptions, sourceOptions));
176
        },
177
 
178
        init: function () {
179
            var that = this;
180
 
181
            this.getSourceNodes(0).done(function (result) {
182
                var data = result[that.options.nodesWrapper];
183
                for(var x in data) {
184
                    var oNewNode = new GTreeTableNode(data[x], that);
185
                    oNewNode.insertIntegral(oNewNode);
186
                }
187
            });
188
        },
189
 
190
        isNodeDragging: function(action) {
191
            if (action === undefined) {
192
                return this._isNodeDragging;
193
            } else if (action === true) {
194
                this._isNodeDragging = true;
195
                this.$tree.disableSelection();
196
            } else {
197
                this._isNodeDragging = false;
198
                this.$tree.enableSelection();
199
            }
200
        },
201
 
202
        generateNewId: function() {
203
            this._lastId += 1;
204
            return 'g' + this._lastId;
205
        }
206
    };
207
 
208
    function GTreeTableNode(data, gtreetable) {
209
        this.manager = gtreetable;
210
 
211
        this.level = parseInt(data.level);
212
        this.parent = data.parent;
213
        this.name = data.name;
214
        this.type = data.type;
215
        this.id = data.id;
216
 
217
        this.insertPosition = undefined;
218
        this.movePosition = undefined;
219
        this.relatedNodeId = undefined;
220
        this._isExpanded = false;
221
        this._isLoading = false;
222
        this._isSaved = data.id === undefined ? false : true;
223
        this._isSelected = false;
224
        this._isHovered = false;
225
        this._isEditable = false;
226
 
227
        this.init();
228
    }
229
 
230
    GTreeTableNode.prototype = {
231
 
232
        getPath: function () {
233
            var oNode = this,
234
                path = [oNode.name],
235
                parent = oNode.parent;
236
 
237
            oNode.$node.prevAll('.' + this.manager.options.classes.node).each(function () {
238
                var currentNode = oNode.manager.getNode($(this));
239
                if (currentNode.id === parent) {
240
                    parent = currentNode.parent;
241
                    path[path.length] = currentNode.name;
242
                }
243
            });
244
            return path;
245
        },        
246
 
247
        getParents: function () {
248
            var parents = [],
249
            parentId = this.parent;
250
 
251
            while (true) {
252
                if (parentId === 0) {
253
                    break;
254
                }
255
 
256
                var oNode = this.manager.getNodeById(parentId);
257
                parents.push(oNode);
258
                parentId = oNode.parent;
259
 
260
            }
261
            return parents;        
262
        },  
263
 
264
        //TODO
265
        // potrzebne tylko w przypadku cache
266
        getIP: function() {
267
            var oNode = this,
268
                ip = '0';
269
 
270
            var parents = oNode.getParents();
271
            parents.reverse();
272
            $.each(parents, function() {
273
                ip += '.'+this.id;
274
            });
275
 
276
            ip += '.'+oNode.id;
277
 
278
            return ip;
279
        },
280
 
281
        getSiblings: function () {
282
            var oNode = this,
283
                siblings = [],
284
                findPath = '.' + oNode.manager.options.classes.node + "[data-parent='" + oNode.parent + "']",
285
                prev = oNode.$node.prevAll(findPath);
286
 
287
            for (var i = prev.length-1; i >= 0; --i) {
288
                siblings.push(oNode.manager.getNode($(prev[i])));
289
            }              
290
 
291
            siblings.push(oNode);    
292
 
293
            oNode.$node
294
                 .nextAll(findPath)
295
                 .each(function () {
296
                     siblings.push(oNode.manager.getNode($(this)));
297
                 });  
298
 
299
            return siblings;        
300
        },
301
 
302
        getDescendants: function (options) {
303
            var oParentNode = this,
304
                settings = $.extend({},{
305
                    depth: 1,
306
                    includeNotSaved: false,
307
                    index: undefined
308
                },options),
309
                findPath = '.' + oParentNode.manager.options.classes.node,
310
                depth = settings.depth !== -1 || isNaN(settings.depth) ? settings.depth : Infinity,
311
                descendants = [];
312
 
313
            if ((settings.includeNotSaved === false)) {
314
                findPath += '.' + oParentNode.manager.options.classes.saved;
315
            }
316
 
317
            if (depth > 1) {
318
                oParentNode.$node.nextAll(findPath).each(function () {
319
                    var oCurrentNode = oParentNode.manager.getNode($(this));
320
                    if ( (oCurrentNode.level <= oParentNode.level) || (oCurrentNode.level === oParentNode.level && oCurrentNode.parent === oParentNode.parent) ) {
321
                        if (!(settings.includeNotSaved === true && !oCurrentNode.isSaved())) {
322
                            return false;
323
                        }
324
                    }
325
                    descendants.push(oCurrentNode);
326
                });
327
            } else {  
328
                oParentNode.$node
329
                    .nextAll(findPath + "[data-parent='" + oParentNode.id + "'][data-level='" + (oParentNode.level + 1) + "']")
330
                    .each(function () {
331
                        descendants.push(oParentNode.manager.getNode($(this)));
332
                    });
333
            }
334
 
335
            if (!isNaN(settings.index)) {
336
                var index = settings.index >= 0  ? settings.index - 1 : descendants.length + settings.index;
337
                return descendants[index];
338
            }
339
            return descendants;
340
        },        
341
 
342
        getMovePosition: function() {
343
            return this.movePosition;
344
        },
345
 
346
        setMovePosition: function(position, pointerOffset) {
347
            this.$node.removeClass(this.manager.options.classes.draggableContainer);
348
            if (position !== undefined) {
349
                this.$node.addClass(this.manager.options.classes.draggableContainer);
350
                this.movePosition = position;
351
                this.$pointer.css('top', pointerOffset.top + 'px');
352
                this.$pointer.css('left', pointerOffset.left + 'px');
353
            }
354
        },
355
 
356
        getId: function () {
357
            return this.id;
358
        },        
359
 
360
        getName: function () {
361
            return this.isEditable() ? this.$input.val() : this.name;
362
        },
363
 
364
        getParent: function () {
365
            return this.parent;
366
        },    
367
 
368
        getInsertPosition: function () {
369
            return this.insertPosition;
370
        },          
371
 
372
        getRelatedNodeId: function () {
373
            return this.relatedNodeId;
374
        },          
375
 
376
        init: function () {
377
            this.$node = this.manager.$nodeTemplate.clone(false);    
378
            this.$name = this.$node.find('.' + this.manager.options.classes.name);
379
            this.$ceIcon = this.$node.find('.' + this.manager.options.classes.ceIcon);
380
            this.$typeIcon = this.$node.find('.' + this.manager.options.classes.typeIcon);
381
            this.$icon = this.$node.find('.' + this.manager.options.classes.icon);
382
            this.$action = this.$node.find('.' + this.manager.options.classes.action);
383
            this.$indent = this.$node.find('.' + this.manager.options.classes.indent);
384
            this.$saveButton = this.$node.find('.' + this.manager.options.classes.saveButton);
385
            this.$cancelButton = this.$node.find('.' + this.manager.options.classes.cancelButton);      
386
            this.$input = this.$node.find('input');      
387
            this.$pointer = this.$node.find('.' + this.manager.options.classes.draggablePointer);
388
 
389
            this.render();
390
            this.attachEvents();
391
 
392
            this.$node.data('bs.gtreetable.gtreetablenode', this);
393
        },
394
 
395
        render: function() {
396
            this.$name.html(this.name);
397
            if (this.id !== undefined) {
398
                this.$node.attr('data-id', this.id);
399
                this.isSaved(true);
400
 
401
                if (this.manager.options.draggable === true) {
402
                    this.$node.addClass(this.manager.options.classes.draggable);
403
                }
404
            }
405
            this.$node.attr('data-parent', this.parent);
406
            this.$node.attr('data-level', this.level);
407
 
408
            this.$indent.css('marginLeft', ((parseInt(this.level) - this.manager.options.rootLevel) * this.manager.options.nodeIndent) + 'px').html('&zwnj;');
409
 
410
            if (this.type !== undefined && this.manager.options.types && this.manager.options.types[this.type] !== undefined) {
411
                this.$typeIcon.addClass(this.manager.options.types[this.type]).show();
412
            }
413
        },
414
 
415
        attachEvents: function () {
416
            var that = this,
417
                selectLimit = parseInt(this.manager.options.selectLimit);
418
 
419
            this.$node.mouseover(function () {
420
                if (!(that.manager.options.draggable === true && that.manager.isNodeDragging() === true)) {
421
                    that.$node.addClass(that.manager.options.classes.hovered);
422
                    that.isHovered(true);
423
                }
424
            });
425
 
426
            this.$node.mouseleave(function () {
427
                that.$node.removeClass(that.manager.options.classes.hovered);
428
                that.isHovered(false);
429
            });
430
 
431
 
432
            if (isNaN(selectLimit) === false && (selectLimit > 0 || selectLimit === -1) ) {
433
                this.$name.click(function (e) {
434
                    if (that.isSelected()) {
435
                        if ($.isFunction(that.manager.options.onUnselect)) {
436
                            that.manager.options.onUnselect(that);
437
                        }
438
                        that.isSelected(false);
439
                    } else {
440
                        var selectedNodes = that.manager.getSelectedNodes();
441
                        if (selectLimit === 1 && selectedNodes.length === 1) {
442
                            selectedNodes[0].isSelected(false);
443
                            selectedNodes = [];
444
                        } else if (selectedNodes.length === selectLimit) {
445
                            if ($.isFunction(that.manager.options.onSelectOverflow)) {
446
                                that.options.onSelectOverflow(that);
447
                            }
448
                            e.preventDefault();
449
                        }
450
 
451
                        if (selectedNodes.length < selectLimit || selectLimit === -1) {
452
                            that.isSelected(true);                            
453
                        }
454
 
455
                        if ($.isFunction(that.manager.options.onSelect)) {
456
                            that.manager.options.onSelect(that);
457
                        }
458
                    }
459
                });                
460
            } else {
461
                this.$name.click(function (e) { that.$ceIcon.click(); });
462
            }
463
 
464
 
465
            this.$ceIcon.click(function (e) {
466
                if (!that.isExpanded()) {
467
                    that.expand({
468
                        isAltPressed: e.altKey
469
                    });
470
                } else {
471
                    that.collapse();
472
                }
473
            });
474
            if (that.manager.options.dragCanExpand === true) {
475
                this.$ceIcon.mouseover(function (e) {
476
                    if (that.manager.options.draggable === true && that.manager.isNodeDragging() === true) {
477
                        if (!that.isExpanded()) {
478
                            that.expand();
479
                        }
480
                    }
481
                });
482
            }
483
 
484
            $.each(this.manager.actions, function (index, action) {
485
                that.$node.find('.' + that.manager.options.classes.action + '-' + index).click(function (event) {
486
                    action.event(that, that.manager);
487
                });
488
            });
489
 
490
            this.$saveButton.click(function () {
491
                that.save();
492
            });
493
 
494
            this.$cancelButton.click(function () {
495
                that.saveCancel();
496
            });
497
 
498
            if (that.manager.options.draggable === true) {
499
                var getMoveData = function (ui, $droppable) {
500
                    var draggableOffsetTop = ui.offset.top - $droppable.offset().top,
501
                        containerOffsetTop = $droppable.offset().top,
502
                        containerHeight = $droppable.outerHeight(),
503
                        containerWorkspace = containerHeight - Math.round(ui.helper.outerHeight() / 2),
504
                        movePosition,
505
                        pointerOffset = {left: that.manager.$tree.offset().left + 5 };
506
 
507
                    if (draggableOffsetTop  <= (containerWorkspace * 0.3)) {
508
                        movePosition = 'before';
509
                        pointerOffset.top = (containerOffsetTop + 3);
510
                    } else if (draggableOffsetTop  <= (containerWorkspace * 0.7)) {
511
                        movePosition = 'lastChild';
512
                        pointerOffset.top = containerOffsetTop + (containerWorkspace / 2);
513
                    } else {
514
                        movePosition = 'after';
515
                        pointerOffset.top = containerOffsetTop + containerWorkspace;
516
                    }                    
517
                    pointerOffset.top += 2;
518
                    return {
519
                        position: movePosition,
520
                        pointerOffset: pointerOffset
521
                    };
522
                };
523
 
524
                this.$node
525
                    .draggable( {
526
                        scroll:true,
527
                        refreshPositions: that.manager.options.dragCanExpand,
528
                        helper: function (e) {
529
                            var oName = that.manager.getNode($(this));
530
                            return '<mark class="' + that.manager.options.classes.draggableHelper + '">' + oName.name + '</mark>';
531
                        },
532
                        cursorAt: {top:0, left: 0 },
533
                        handle: '.'+ that.manager.options.classes.handleIcon,
534
                        start: function (e) {
535
                            if (!$.browser.webkit) {
536
                                $(this).data("bs.gtreetable.gtreetablenode.scrollTop", $(window).scrollTop());
537
                            }
538
                        },
539
                        stop: function (e) {
540
                            that.manager.isNodeDragging(false);
541
                        },
542
                        drag: function (e, ui) {
543
                            if (!$.browser.webkit) {
544
                                var strollTop =  $(window).scrollTop(),
545
                                    delta = ($(this).data("bs.gtreetable.gtreetablenode.scrollTop") - strollTop);
546
 
547
                                ui.position.top -= strollTop + delta;
548
                                $(this).data("bs.gtreetable.gtreetablenode.startingScrollTop", strollTop);
549
                            }
550
                            var $droppable = $(this).data("bs.gtreetable.gtreetablenode.currentDroppable");
551
                            if ($droppable) {
552
                                var data = getMoveData(ui, $droppable);
553
                                that.manager.getNode($droppable).setMovePosition(data.position, data.pointerOffset);
554
                            }                            
555
                        }
556
                    })
557
                    .droppable({
558
                        accept: '.' + that.manager.options.classes.node,
559
                        over: function(event, ui) {
560
                            var $this = $(this),
561
                                data = getMoveData(ui, $this);
562
                            that.manager.getNode($this).setMovePosition(data.position, data.pointerOffset);
563
                            ui.draggable.data("bs.gtreetable.gtreetablenode.currentDroppable", $this);
564
                        },
565
                        out: function(event, ui) {
566
                            ui.draggable.removeData("bs.gtreetable.gtreetablenode.currentDroppable");
567
                            that.manager.getNode($(this)).setMovePosition();
568
                        },
569
                        drop: function(event, ui) {
570
                            var $this = $(this),
571
                                oNode = that.manager.getNode($this),
572
                                movePosition = oNode.getMovePosition();
573
                            ui.draggable.removeData("bs.gtreetable.gtreetablenode.currentDroppable");
574
                            oNode.setMovePosition();
575
                            that.manager.getNode(ui.draggable).move(oNode, movePosition);
576
                        }
577
                    });
578
            }            
579
        },
580
 
581
        makeEditable: function () {
582
            this.showForm(true);  
583
        },
584
 
585
        save: function () {
586
            var oNode = this;
587
            if ($.isFunction(oNode.manager.options.onSave)) {
588
                $.when($.ajax(oNode.manager.options.onSave(oNode))).done(function (data) {
589
                    oNode._save(data);
590
                });
591
            } else {
592
                oNode._save({
593
                    name: oNode.getName(),
594
                    id: oNode.manager.generateNewId()
595
                });
596
            }
597
        },
598
 
599
        _save: function(data) {
600
            var oNode = this;
601
            oNode.id = data.id;
602
            oNode.name = data.name;                                      
603
 
604
            if ($.isFunction(oNode.manager.options.sort)) {
605
                oNode.sort();
606
            }
607
 
608
            if (this.manager.options.cache > 0) {
609
                this.manager.cacheManager.synchronize(oNode.isSaved() ? 'edit' : 'add', oNode);
610
            }
611
 
612
            oNode.render();
613
            oNode.showForm(false);
614
            oNode.isHovered(false);
615
        },
616
 
617
        saveCancel: function () {
618
            this.showForm(false);
619
            if (!this.isSaved()) {
620
                this._remove();
621
            }
622
        },
623
 
624
        expand: function (options) {
625
            var oNode = this,
626
                    prevNode = oNode,
627
                settings = $.extend({}, {
628
                isAltPressed: false,
629
                onAfterFill: function (oNode, data) {
630
                    oNode.isExpanded(true);
631
                    if (data.length === 0) {
632
                        if (oNode.manager.options.showExpandIconOnEmpty === true) {
633
                            oNode.isExpanded(false);
634
                        } else {
635
                            oNode.showCeIcon(false);
636
                        }    
637
                    }
638
                }
639
            },options);
640
 
641
            $.when(this.manager.getSourceNodes(oNode.id, settings.isAltPressed)).done(function (result) {
642
                var data = result[oNode.manager.options.nodesWrapper];
643
                for(var x in data) {
644
                    var newNode = new GTreeTableNode(data[x], oNode.manager);
645
                    oNode.insertIntegral(newNode, prevNode);
646
                    prevNode = newNode;
647
                }
648
 
649
                if (settings && typeof $.isFunction(settings.onAfterFill)) {
650
                    settings.onAfterFill(oNode, data);
651
                }
652
            });            
653
        },
654
 
655
        collapse: function () {
656
            this.isExpanded(false);
657
 
658
            $.each(this.getDescendants({ depth: -1, includeNotSaved: true }), function () {
659
                this.$node.remove();
660
            });
661
        },        
662
 
663
        _canAdd: function(oNewNode) {
664
            var data = { result: !(oNewNode.parent === 0 && this.manager.options.manyroots === false) };
665
            if (!data.result) {
666
                data.message = this.manager.language.messages.onNewRootNotAllowed;
667
            }
668
            return data;
669
        },
670
 
671
        add: function (position, type) {
672
            var oTriggerNode = this,
673
                childPosition = (position === 'lastChild' || position === 'firstChild'),
674
                oNewNode = new GTreeTableNode({
675
                    level: oTriggerNode.level + (childPosition ? 1 : 0),
676
                    parent: oTriggerNode.level === this.manager.options.rootLevel && !childPosition ? 0 : (childPosition ? oTriggerNode.id : oTriggerNode.parent),
677
                    type: type
678
                },this.manager),
679
                canAddData = this._canAdd(oNewNode);
680
 
681
 
682
            if (!canAddData.result) {
683
                alert(canAddData.message);
684
                return false;
685
            }
686
 
687
            function ins() {
688
                if (childPosition) {
689
                    oTriggerNode.isExpanded(true);
690
                    oTriggerNode.showCeIcon(true);
691
                }
692
                oNewNode.insert(position, oTriggerNode);  
693
                oNewNode.insertPosition = position;
694
                oNewNode.relatedNodeId = oTriggerNode.id;                
695
                oNewNode.showForm(true);
696
            }
697
 
698
            if ( childPosition && !oTriggerNode.isExpanded() ) {
699
                oTriggerNode.expand({
700
                    onAfterFill: function () {
701
                        ins();
702
                    }
703
                });
704
            } else {
705
                ins();
706
            }
707
        },
708
 
709
        insert: function (position, oRelatedNode) {
710
            var oNode = this,
711
                oLastChild,
712
                oContext;
713
 
714
            if (position === 'before') {
715
                oRelatedNode.$node.before(oNode.$node);
716
            } else if (position === 'after') {
717
                oContext = oRelatedNode;
718
                if (oRelatedNode.isExpanded()) {
719
                    oLastChild = oRelatedNode.getDescendants({ depth: 1, index: -1, includeNotSaved: true });
720
                    oContext = oLastChild === undefined ? oContext : oLastChild;
721
                }
722
                oContext.$node.after(oNode.$node);
723
            } else if (position === 'firstChild') {
724
                this.manager.getNodeById(oRelatedNode.id).$node.after(oNode.$node);
725
            } else if (position === 'lastChild') {
726
                oLastChild = oRelatedNode.getDescendants({ depth: 1, index: -1, includeNotSaved: true });
727
                oContext = oLastChild === undefined ? oRelatedNode : oLastChild;
728
                oContext.$node.after(oNode.$node);
729
            } else {
730
                throw "Wrong position.";
731
            }
732
        },        
733
 
734
        insertIntegral: function (oNewNode, oNode) {
735
            if (oNode === undefined) {
736
                this.manager.$tree.append(oNewNode.$node);
737
            } else {
738
                oNode.$node.after(oNewNode.$node);
739
            }
740
        },          
741
 
742
        remove: function () {
743
            var oNode = this;
744
 
745
            if (oNode.isSaved() && $.isFunction(oNode.manager.options.onDelete)) {
746
                $.when($.ajax(oNode.manager.options.onDelete(oNode))).done(function () {
747
                    oNode._remove();
748
                });
749
            } else {
750
                this._remove();
751
            }
752
        },
753
 
754
        _remove: function () {            
755
            if (this.isExpanded() === true) {
756
                this.collapse();
757
            }
758
            this.$node.remove();            
759
 
760
            if (this.parent > 0) {
761
                var oParent = this.manager.getNodeById(this.parent);
762
                if (oParent.getDescendants({ depth: 1, includeNotSaved: true }).length === 0) {
763
                    oParent.collapse();
764
                }
765
            }
766
 
767
            if (this.manager.options.cache > 0) {
768
                this.manager.cacheManager.synchronize('delete', this);
769
            }
770
        },    
771
 
772
        _canMove: function(oDestination, position) {
773
            var oNode = this,
774
                data = { result: true };
775
            if (oDestination.parent === 0 && this.manager.options.manyroots === false && position !== 'lastChild') {
776
                data.result = false;
777
                data.message = this.manager.language.messages.onMoveAsRoot;
778
            } else {              
779
                $.each(oDestination.getParents(), function () {
780
                    if (this.id === oNode.id) {
781
                        data.result = false;
782
                        data.message = this.manager.language.messages.onMoveInDescendant;  
783
                        return false;
784
                    }
785
                });          
786
            }
787
            return data;
788
        },
789
 
790
        move: function(oDestination, position) {            
791
            var oNode = this,
792
                moveData = this._canMove(oDestination, position);
793
 
794
            if (moveData.result === false) {
795
                alert(moveData.message);
796
                return false;
797
            }
798
 
799
            if ($.isFunction(oNode.manager.options.onMove)) {
800
                $.when($.ajax(oNode.manager.options.onMove(oNode, oDestination, position))).done(function (data) {
801
                    oNode._move(oDestination, position);
802
                });
803
            } else {
804
                oNode._move(oDestination, position);
805
            }
806
        },
807
 
808
        _move: function(oDestination, position) {
809
            var oNode = this,
810
                oNodeDescendants = oNode.getDescendants({ depth: -1, includeNotSaved: true }),
811
                oOldNode = $.extend({}, oNode),
812
                oldIP = oNode.getIP(),
813
                delta = oDestination.level - oNode.level;
814
 
815
            oNode.parent = position === 'lastChild' ? oDestination.id : oDestination.parent;
816
            oNode.level = oDestination.level;
817
 
818
            if (position === 'lastChild' && !oDestination.isExpanded()) {
819
                oNode.$node.remove();
820
                $.each(oNodeDescendants, function () {
821
                    this.$node.remove();
822
                });                
823
            } else {
824
 
825
                if (position === 'lastChild') {
826
                    oNode.level += 1;
827
                    oDestination.showCeIcon(true);
828
                }
829
                oNode.render();
830
                oNode.insert(position, oDestination);
831
 
832
                if (oNodeDescendants.length > 0) {
833
                    var prevNode = oNode.$node;
834
                    if (position === 'lastChild') {
835
                        delta += 1;
836
                    }
837
                    $.each(oNodeDescendants, function() {
838
                        var oNode = this;
839
                        oNode.level += delta;
840
                        oNode.render();
841
                        prevNode.after(oNode.$node);
842
                        prevNode = oNode.$node;
843
                    });                
844
                }                        
845
            }
846
 
847
            // sprawdza, czy nie byl przeniesiony ostatni element
848
            // oOldSourceParent !== undefined => parent = 0
849
            var oOldNodeParent = oNode.manager.getNodeById(oOldNode.parent);
850
            if (oOldNodeParent !== undefined && oOldNodeParent.getDescendants({depth: 1, includeNotSaved: true}).length === 0) {
851
                oOldNodeParent.isExpanded(false);
852
            }
853
 
854
            if ($.isFunction(oNode.manager.options.sort)) {
855
                oNode.sort();
856
            }    
857
 
858
            if (this.manager.options.cache > 0) {
859
                this.manager.cacheManager.synchronize('move', oNode, { 'oOldNode': oOldNode, 'oldIP': oldIP });
860
            }            
861
        },        
862
 
863
        sort: function() {
864
            var oNode = this,
865
                oSiblings = oNode.getSiblings();
866
 
867
            // nie ma rodzenstwa = sortowanie nie jest potrzebne
868
            if (oSiblings.length > 0) {
869
                var oDescendants = !oNode.isExpanded() ? [] : oNode.getDescendants({ depth: -1, includeNotSaved: true }),
870
                    oRelated;
871
 
872
                $.each(oSiblings, function () {
873
                    if (oNode.manager.options.sort(oNode, this) === -1) {
874
                        oRelated = this;
875
                        return false;
876
                    }
877
                });
878
 
879
                if (oRelated === undefined) {
880
                    oRelated = oSiblings[oSiblings.length-1];
881
                    if (oRelated.isExpanded()) {
882
                        oRelated = oNode.manager.getNodeById(oNode.parent).getDescendants({ depth: -1, index: -1, includeNotSaved: true });
883
                    }
884
                    oRelated.$node.after(oNode.$node);
885
                } else {
886
                    oRelated.$node.before(oNode.$node);
887
                }
888
 
889
                var prevNode = oNode.$node;
890
                $.each(oDescendants, function() {
891
                    var oCurrentNode = this;
892
                    prevNode.after(oCurrentNode.$node);
893
                    prevNode = oCurrentNode.$node;
894
                });                        
895
            }
896
        },        
897
 
898
        isLoading: function (action) {
899
            if (action === undefined) {
900
                return this._isLoading;
901
            } else if (action) {
902
                this.$name.addClass(this.manager.options.classes.loading);
903
                this._isLoading = true;
904
            } else {
905
                this.$name.removeClass(this.manager.options.classes.loading);
906
                this._isLoading = false;
907
            }
908
        },
909
 
910
        isSaved: function (action) {
911
            if (action === undefined) {
912
                return this._isSaved;
913
            } else if (action) {
914
                this.$node.addClass(this.manager.options.classes.saved);
915
                this._isSaved = true;
916
            } else {
917
                this.$node.removeClass(this.manager.options.classes.saved);
918
                this._isSaved = false;
919
            }
920
        },        
921
 
922
        isSelected: function (action) {
923
            if (action === undefined) {
924
                return this._isSelected;
925
            } else if (action) {
926
                this.$node.addClass(this.manager.options.classes.selected);
927
                this._isSelected = true;
928
            } else {
929
                this.$node.removeClass(this.manager.options.classes.selected);
930
                this._isSelected = false;
931
            }            
932
        },
933
 
934
        isExpanded: function (action) {
935
            if (action === undefined) {
936
                return this._isExpanded;
937
            } else if (action) {
938
                this.$node.addClass(this.manager.options.classes.expanded).removeClass(this.manager.options.classes.collapsed);
939
                this._isExpanded = true;
940
            } else {
941
                this.$node.addClass(this.manager.options.classes.collapsed).removeClass(this.manager.options.classes.expanded);
942
                this._isExpanded = false;
943
            }            
944
        },    
945
 
946
        isHovered: function (action) {
947
            if (action === undefined) {
948
                return this._isHovered;
949
            } else if (action) {
950
                this.$node.addClass(this.manager.options.classes.hovered);
951
                this._isHovered = true;
952
            } else {
953
                this.$node.removeClass(this.manager.options.classes.hovered);
954
                this.$node.find('.btn-group').removeClass('open');
955
                this._isHovered = false;
956
            }            
957
        },
958
 
959
        isEditable: function (action) {
960
            if (action === undefined) {
961
                return this._isEditable;
962
            } else {
963
                this._isEditable = action;
964
            }  
965
        },        
966
 
967
        showCeIcon: function (action) {
968
            this.$ceIcon.css('visibility', action ? 'visible' : 'hidden');
969
        },
970
 
971
        showForm: function (action) {
972
            if (action === true) {
973
                this.isEditable(true);
974
                this.$input.val(this.name);
975
                this.$name.addClass('hide');
976
                this.$action.removeClass('hide');
977
                //TODO nie dziala zawsze
978
                this.$input.focus();
979
            } else {
980
                this.isEditable(false);
981
                this.$name.removeClass('hide');
982
                this.$action.addClass('hide');
983
            }
984
        }
985
    };
986
 
987
   function GTreeTableCache(manager) {
988
        this._cached = {};
989
        this.manager = manager;
990
    }
991
 
992
    GTreeTableCache.prototype = {  
993
        _getIP: function (param) {
994
            return typeof param === "string" ? param : param.getIP();
995
        },
996
 
997
        get: function(param) {
998
            return this._cached[this._getIP(param)];
999
        },
1000
 
1001
        set: function (param, data) {
1002
            this._cached[this._getIP(param)] = data;
1003
        },
1004
 
1005
        remove: function (param) {
1006
            this._cached[this._getIP(param)] = undefined;
1007
        },
1008
 
1009
        synchronize: function (method, oNode, params) {
1010
            if (oNode.parent > 0) {
1011
                switch (method) {
1012
                    case 'add':
1013
                        this._synchronizeAdd(oNode);
1014
                        break;
1015
 
1016
                    case 'edit':
1017
                        this._synchronizeEdit(oNode);
1018
                        break;
1019
 
1020
                    case 'delete':
1021
                        this._synchronizeDelete(oNode);
1022
                        break;
1023
 
1024
                    case 'move':
1025
                        this._synchronizeMove(oNode, params);
1026
                        break;
1027
 
1028
                    default:
1029
                        throw "Wrong method.";
1030
                }
1031
            }            
1032
        },
1033
 
1034
        _synchronizeAdd: function (oNode) {
1035
            var oParentNode = this.manager.getNodeById(oNode.parent);
1036
 
1037
            if (this.manager.options.cache > 1) {
1038
                var data = this.get(oParentNode);
1039
                if (data !== undefined) {
1040
                    data.push({
1041
                        id: oNode.id,
1042
                        name: oNode.getName(),
1043
                        level: oNode.level,
1044
                        type: oNode.type,
1045
                        parent: oNode.parent
1046
                    });
1047
                    this.set(oParentNode, this.isSortDefined() ? this.sort(data) : data);
1048
                }
1049
            } else {
1050
                this.remove(oParentNode);
1051
            }
1052
        },
1053
 
1054
        _synchronizeEdit: function (oNode) {
1055
            var oParentNode = this.manager.getNodeById(oNode.parent);
1056
 
1057
            if (this.manager.options.cache > 1) {
1058
                var data = this.get(oParentNode);
1059
                $.each(data, function () {
1060
                    if (this.id === oNode.id) {
1061
                        this.name = oNode.getName();
1062
                        return false;
1063
                    }
1064
                });
1065
                this.set(oParentNode, this.isSortDefined() ? this.sort(data) : data);
1066
            } else {
1067
                this.remove(oParentNode);
1068
            }            
1069
        },
1070
 
1071
        _synchronizeDelete: function(oNode) {            
1072
            var oParentNode = this.manager.getNodeById(oNode.parent);
1073
 
1074
            if (this.manager.options.cache > 1) {
1075
                var data = this.get(oParentNode),
1076
                    position;
1077
 
1078
                // pobieranie pozycji
1079
                $.each(data, function(index) {
1080
                    if (this.id === oNode.id) {
1081
                        position = index;
1082
                        return false;
1083
                    }
1084
                });
1085
                if (position !== undefined) {
1086
                    data.splice(position, 1);
1087
                    this.set(oParentNode, data);
1088
                }
1089
            } else {
1090
                this.remove(oParentNode);
1091
            }                      
1092
        },
1093
 
1094
        _synchronizeMove: function(oNode, params) {
1095
            var that = this,
1096
                newIP = oNode.getIP(),
1097
                delta =  oNode.level - params.oOldNode.level;
1098
 
1099
            $.each(this._cached, function (index) {
1100
                if (index === params.oldIP || index.indexOf(params.oldIP+'.') === 0) {
1101
 
1102
                    if (that.manager.options.cache > 1) {
1103
                        var newData = [],
1104
                            newIndex = index !== params.oldIP ? newIP + index.substr(params.oldIP.length) : newIP;
1105
 
1106
                        $(that.get(index)).each(function () {
1107
                            this.level += delta;
1108
                            newData.push(this);
1109
                        });      
1110
                        that.set(newIndex, newData);
1111
                    }
1112
 
1113
                    that.remove(index);
1114
 
1115
                }
1116
            });
1117
            this.synchronize('delete', params.oOldNode);  
1118
            this.synchronize('add', oNode);            
1119
        },
1120
 
1121
        isSortDefined: function () {
1122
            return $.isFunction(this.manager.options.sort);
1123
        },
1124
 
1125
        sort: function (data) {
1126
            return data.sort(this.manager.options.sort);
1127
        }
1128
    };    
1129
 
1130
    // OVERLAYINPUT PLUGIN DEFINITION
1131
    // ==============================
1132
 
1133
    function Plugin(option, _relatedTarget) {
1134
        var retval = null;
1135
 
1136
        this.each(function () {
1137
            var $this = $(this),
1138
                data = $this.data('bs.gtreetable'),
1139
                options = $.extend({}, $.fn.gtreetable.defaults, $this.data(), typeof option === 'object' && option);
1140
 
1141
            if (!data) {
1142
                data = new GTreeTable(this, options);
1143
                $this.data('bs.gtreetable', data);
1144
            }
1145
 
1146
            if (typeof option === 'string') {
1147
                retval = data[option](_relatedTarget);
1148
            }
1149
        });
1150
 
1151
        if (!retval) {
1152
            retval = this;
1153
        }
1154
 
1155
        return retval;
1156
    }
1157
 
1158
    var old = $.fn.gtreetable;
1159
 
1160
    $.fn.gtreetable = Plugin;
1161
    $.fn.gtreetable.Constructor = GTreeTable;
1162
 
1163
    $.fn.gtreetable.defaults = {
1164
        nodesWrapper: 'nodes',
1165
        nodeIndent: 16,
1166
        language: 'en',
1167
        inputWidth: '60%',
1168
        cache: 2,
1169
        readonly: false,
1170
        selectLimit: 1,
1171
        rootLevel: 0,        
1172
        manyroots: false,
1173
        draggable: false,
1174
        dragCanExpand: false,
1175
        showExpandIconOnEmpty: false,        
1176
        languages: {
1177
            'en-US': {
1178
                save: 'Save',
1179
                cancel: 'Cancel',
1180
                action: 'Action',
1181
                actions: {
1182
                    createBefore: 'Create before',
1183
                    createAfter: 'Create after',
1184
                    createFirstChild: 'Create first child',
1185
                    createLastChild: 'Create last child',
1186
                    update: 'Update',
1187
                    'delete': 'Delete'
1188
                },
1189
                messages: {
1190
                    onDelete: 'Are you sure?',
1191
                    onNewRootNotAllowed: 'Adding the now node as root is not allowed.',
1192
                    onMoveInDescendant: 'The target node should not be descendant.',
1193
                    onMoveAsRoot: 'The target node should not be root.'
1194
                }                
1195
            }
1196
        },
1197
        defaultActions: [
1198
            {
1199
                name: '${createBefore}',
1200
                event: function (oNode, oManager) {
1201
                    oNode.add('before', 'default');
1202
                }
1203
            },
1204
            {
1205
                name: '${createAfter}',
1206
                event: function (oNode, oManager) {
1207
                    oNode.add('after', 'default');
1208
                }
1209
            },
1210
            {
1211
                name: '${createFirstChild}',
1212
                event: function (oNode, oManager) {
1213
                    oNode.add('firstChild', 'default');
1214
                }
1215
            },
1216
            {
1217
                name: '${createLastChild}',
1218
                event: function (oNode, oManager) {
1219
                    oNode.add('lastChild', 'default');
1220
                }
1221
            },
1222
            {
1223
                divider: true
1224
            },
1225
            {
1226
                name: '${update}',
1227
                event: function (oNode, oManager) {
1228
                    oNode.makeEditable();
1229
                }
1230
            },
1231
            {
1232
                name: '${delete}',
1233
                event: function (oNode, oManager) {
1234
                    if (confirm(oManager.language.messages.onDelete)) {
1235
                        oNode.remove();
1236
                    }
1237
                }
1238
            }
1239
        ],
1240
        classes: {
1241
            node: 'node',
1242
            loading: 'node-loading',
1243
            selected: 'node-selected',
1244
            hovered: 'node-hovered',
1245
            expanded: 'node-expanded',
1246
            collapsed : 'node-collapsed',
1247
            draggable : 'node-draggable',
1248
            draggableHelper : 'node-draggable-helper',
1249
            draggablePointer : 'node-draggable-pointer',
1250
            draggableContainer : 'node-draggable-container',
1251
            saved: 'node-saved',
1252
            name: 'node-name',
1253
            icon: 'node-icon',            
1254
            selectedIcon: 'node-icon-selected',
1255
            ceIcon: 'node-icon-ce',
1256
            typeIcon: 'node-icon-type',
1257
            handleIcon : 'node-icon-handle',
1258
            action: 'node-action',
1259
            indent: 'node-indent',
1260
            saveButton: 'node-save',
1261
            cancelButton: 'node-cancel',
1262
            buttons: 'node-buttons'            
1263
        }
1264
    };
1265
 
1266
    // OVERLAYINPUT NO CONFLICT
1267
    // ========================
1268
 
1269
    $.fn.gtreetable.noConflict = function () {
1270
        $.fn.gtreetable = old;
1271
        return this;
1272
    };
1273
 
1274
}(jQuery));