dynatree-rails 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -41,7 +41,16 @@ Can be used to turn association select to a tree select, like this:
41
41
 
42
42
 
43
43
  #checkboxes
44
+ -# with simple_form
44
45
  = f.association :categories, as: :check_boxes, collection: Category.all
46
+
47
+ -# with formtastic
48
+ = f.input :categories, as: :check_boxes, collection: Category.all
49
+
50
+ -# without any
51
+ - Category.each do |c|
52
+ = check_box_tag "item[category_ids][]", c.id, @item.category_ids.include?(c.id)
53
+
45
54
  #tree.controls.input{style: 'width: 220px;'}
46
55
 
47
56
  :javascript
@@ -87,5 +96,5 @@ Dynatree is
87
96
 
88
97
  This code is:
89
98
 
90
- Copyright (c) 2008-2011, GlebTV
99
+ Copyright (c) 2012, GlebTV
91
100
  Dual licensed under the MIT or GPL Version 2 licenses.
@@ -1,5 +1,5 @@
1
1
  module Dynatree
2
2
  module Rails
3
- VERSION = "0.0.4"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
@@ -0,0 +1,3312 @@
1
+ /*************************************************************************
2
+ jquery.dynatree.js
3
+ Dynamic tree view control, with support for lazy loading of branches.
4
+
5
+ Copyright (c) 2008-2011, Martin Wendt (http://wwWendt.de)
6
+ Dual licensed under the MIT or GPL Version 2 licenses.
7
+ http://code.google.com/p/dynatree/wiki/LicenseInfo
8
+
9
+ A current version and some documentation is available at
10
+ http://dynatree.googlecode.com/
11
+
12
+ $Version: 1.2.1_rc3$
13
+ $Revision: 585, 2012-01-10 00:07:39$
14
+
15
+ @depends: jquery.js
16
+ @depends: jquery.ui.core.js
17
+ @depends: jquery.cookie.js
18
+ *************************************************************************/
19
+
20
+ // Note: We currently allow eval() to parse the 'data' attribtes, when initializing from HTML.
21
+ /*jslint laxbreak: true, browser: true, evil: true, indent: 0, white: false, onevar: false */
22
+
23
+ /*************************************************************************
24
+ * Debug functions
25
+ */
26
+
27
+ var _canLog = true;
28
+
29
+ function _log(mode, msg) {
30
+ /**
31
+ * Usage: logMsg("%o was toggled", this);
32
+ */
33
+ if( !_canLog ){
34
+ return;
35
+ }
36
+ // Remove first argument
37
+ var args = Array.prototype.slice.apply(arguments, [1]);
38
+ // Prepend timestamp
39
+ var dt = new Date();
40
+ var tag = dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds();
41
+ args[0] = tag + " - " + args[0];
42
+
43
+ try {
44
+ switch( mode ) {
45
+ case "info":
46
+ window.console.info.apply(window.console, args);
47
+ break;
48
+ case "warn":
49
+ window.console.warn.apply(window.console, args);
50
+ break;
51
+ default:
52
+ window.console.log.apply(window.console, args);
53
+ break;
54
+ }
55
+ } catch(e) {
56
+ if( !window.console ){
57
+ _canLog = false; // Permanently disable, when logging is not supported by the browser
58
+ }
59
+ }
60
+ }
61
+
62
+ function logMsg(msg) {
63
+ Array.prototype.unshift.apply(arguments, ["debug"]);
64
+ _log.apply(this, arguments);
65
+ }
66
+
67
+
68
+ // Forward declaration
69
+ var getDynaTreePersistData = null;
70
+
71
+
72
+
73
+ /*************************************************************************
74
+ * Constants
75
+ */
76
+ var DTNodeStatus_Error = -1;
77
+ var DTNodeStatus_Loading = 1;
78
+ var DTNodeStatus_Ok = 0;
79
+
80
+
81
+ // Start of local namespace
82
+ (function($) {
83
+
84
+ /*************************************************************************
85
+ * Common tool functions.
86
+ */
87
+
88
+ var Class = {
89
+ create: function() {
90
+ return function() {
91
+ this.initialize.apply(this, arguments);
92
+ };
93
+ }
94
+ };
95
+
96
+ // Tool function to get dtnode from the event target:
97
+ function getDtNodeFromElement(el) {
98
+ alert("getDtNodeFromElement is deprecated");
99
+ return $.ui.dynatree.getNode(el);
100
+ /*
101
+ var iMax = 5;
102
+ while( el && iMax-- ) {
103
+ if(el.dtnode) { return el.dtnode; }
104
+ el = el.parentNode;
105
+ }
106
+ return null;
107
+ */
108
+ }
109
+
110
+ function noop() {
111
+ }
112
+
113
+ /*************************************************************************
114
+ * Class DynaTreeNode
115
+ */
116
+ var DynaTreeNode = Class.create();
117
+
118
+ DynaTreeNode.prototype = {
119
+ initialize: function(parent, tree, data) {
120
+ /**
121
+ * @constructor
122
+ */
123
+ this.parent = parent;
124
+ this.tree = tree;
125
+ if ( typeof data === "string" ){
126
+ data = { title: data };
127
+ }
128
+ if( data.key === undefined ){
129
+ data.key = "_" + tree._nodeCount++;
130
+ }
131
+ this.data = $.extend({}, $.ui.dynatree.nodedatadefaults, data);
132
+ this.li = null; // not yet created
133
+ this.span = null; // not yet created
134
+ this.ul = null; // not yet created
135
+ this.childList = null; // no subnodes yet
136
+ this._isLoading = false; // Lazy content is being loaded
137
+ this.hasSubSel = false;
138
+ this.bExpanded = false;
139
+ this.bSelected = false;
140
+
141
+ },
142
+
143
+ toString: function() {
144
+ return "DynaTreeNode<" + this.data.key + ">: '" + this.data.title + "'";
145
+ },
146
+
147
+ toDict: function(recursive, callback) {
148
+ var dict = $.extend({}, this.data);
149
+ dict.activate = ( this.tree.activeNode === this );
150
+ dict.focus = ( this.tree.focusNode === this );
151
+ dict.expand = this.bExpanded;
152
+ dict.select = this.bSelected;
153
+ if( callback ){
154
+ callback(dict);
155
+ }
156
+ if( recursive && this.childList ) {
157
+ dict.children = [];
158
+ for(var i=0, l=this.childList.length; i<l; i++ ){
159
+ dict.children.push(this.childList[i].toDict(true, callback));
160
+ }
161
+ } else {
162
+ delete dict.children;
163
+ }
164
+ return dict;
165
+ },
166
+
167
+ fromDict: function(dict) {
168
+ /**
169
+ * Update node data. If dict contains 'children', then also replace
170
+ * the hole sub tree.
171
+ */
172
+ var children = dict.children;
173
+ if(children === undefined){
174
+ this.data = $.extend(this.data, dict);
175
+ this.render();
176
+ return;
177
+ }
178
+ dict = $.extend({}, dict);
179
+ dict.children = undefined;
180
+ this.data = $.extend(this.data, dict);
181
+ this.removeChildren();
182
+ this.addChild(children);
183
+ },
184
+
185
+ _getInnerHtml: function() {
186
+ var tree = this.tree,
187
+ opts = tree.options,
188
+ cache = tree.cache,
189
+ level = this.getLevel(),
190
+ data = this.data,
191
+ res = "";
192
+ // connector (expanded, expandable or simple)
193
+ if( level < opts.minExpandLevel ) {
194
+ if(level > 1){
195
+ res += cache.tagConnector;
196
+ }
197
+ // .. else (i.e. for root level) skip expander/connector altogether
198
+ } else if( this.hasChildren() !== false ) {
199
+ res += cache.tagExpander;
200
+ } else {
201
+ res += cache.tagConnector;
202
+ }
203
+ // Checkbox mode
204
+ if( opts.checkbox && data.hideCheckbox !== true && !data.isStatusNode ) {
205
+ res += cache.tagCheckbox;
206
+ }
207
+ // folder or doctype icon
208
+ if ( data.icon ) {
209
+ res += "<img src='" + opts.imagePath + data.icon + "' alt='' />";
210
+ } else if ( data.icon === false ) {
211
+ // icon == false means 'no icon'
212
+ noop(); // keep JSLint happy
213
+ } else {
214
+ // icon == null means 'default icon'
215
+ res += cache.tagNodeIcon;
216
+ }
217
+ // node title
218
+ var nodeTitle = "";
219
+ if ( opts.onCustomRender ){
220
+ nodeTitle = opts.onCustomRender.call(tree, this) || "";
221
+ }
222
+ if(!nodeTitle){
223
+ var tooltip = data.tooltip ? ' title="' + data.tooltip.replace(/\"/g, '&quot;') + '"' : '',
224
+ href = data.href || "#";
225
+ if( opts.noLink || data.noLink ) {
226
+ nodeTitle = '<span style="display:inline-block;" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</span>';
227
+ // this.tree.logDebug("nodeTitle: " + nodeTitle);
228
+ } else {
229
+ nodeTitle = '<a href="' + href + '" class="' + opts.classNames.title + '"' + tooltip + '>' + data.title + '</a>';
230
+ }
231
+ }
232
+ res += nodeTitle;
233
+ return res;
234
+ },
235
+
236
+
237
+ _fixOrder: function() {
238
+ /**
239
+ * Make sure, that <li> order matches childList order.
240
+ */
241
+ var cl = this.childList;
242
+ if( !cl || !this.ul ){
243
+ return;
244
+ }
245
+ var childLI = this.ul.firstChild;
246
+ for(var i=0, l=cl.length-1; i<l; i++) {
247
+ var childNode1 = cl[i];
248
+ var childNode2 = childLI.dtnode;
249
+ if( childNode1 !== childNode2 ) {
250
+ this.tree.logDebug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
251
+ this.ul.insertBefore(childNode1.li, childNode2.li);
252
+ } else {
253
+ childLI = childLI.nextSibling;
254
+ }
255
+ }
256
+ },
257
+
258
+
259
+ render: function(useEffects, includeInvisible) {
260
+ /**
261
+ * Create <li><span>..</span> .. </li> tags for this node.
262
+ *
263
+ * <li id='KEY' dtnode=NODE> // This div contains the node's span and list of child div's.
264
+ * <span class='title'>S S S A</span> // Span contains graphic spans and title <a> tag
265
+ * <ul> // only present, when node has children
266
+ * <li id='KEY' dtnode=NODE>child1</li>
267
+ * <li id='KEY' dtnode=NODE>child2</li>
268
+ * </ul>
269
+ * </li>
270
+ */
271
+ // this.tree.logDebug("%s.render(%s)", this, useEffects);
272
+ // ---
273
+ var tree = this.tree,
274
+ parent = this.parent,
275
+ data = this.data,
276
+ opts = tree.options,
277
+ cn = opts.classNames,
278
+ isLastSib = this.isLastSibling(),
279
+ firstTime = false;
280
+
281
+ if( !parent && !this.ul ) {
282
+ // Root node has only a <ul>
283
+ this.li = this.span = null;
284
+ this.ul = document.createElement("ul");
285
+ if( opts.minExpandLevel > 1 ){
286
+ this.ul.className = cn.container + " " + cn.noConnector;
287
+ }else{
288
+ this.ul.className = cn.container;
289
+ }
290
+ } else if( parent ) {
291
+ // Create <li><span /> </li>
292
+ if( ! this.li ) {
293
+ firstTime = true;
294
+ this.li = document.createElement("li");
295
+ this.li.dtnode = this;
296
+ if( data.key && opts.generateIds ){
297
+ this.li.id = opts.idPrefix + data.key;
298
+ }
299
+ this.span = document.createElement("span");
300
+ this.span.className = cn.title;
301
+ this.li.appendChild(this.span);
302
+
303
+ if( !parent.ul ) {
304
+ // This is the parent's first child: create UL tag
305
+ // (Hidden, because it will be
306
+ parent.ul = document.createElement("ul");
307
+ parent.ul.style.display = "none";
308
+ parent.li.appendChild(parent.ul);
309
+ // if( opts.minExpandLevel > this.getLevel() ){
310
+ // parent.ul.className = cn.noConnector;
311
+ // }
312
+ }
313
+ // set node connector images, links and text
314
+ // this.span.innerHTML = this._getInnerHtml();
315
+
316
+ parent.ul.appendChild(this.li);
317
+ }
318
+ // set node connector images, links and text
319
+ this.span.innerHTML = this._getInnerHtml();
320
+ // Set classes for current status
321
+ var cnList = [];
322
+ cnList.push(cn.node);
323
+ if( data.isFolder ){
324
+ cnList.push(cn.folder);
325
+ }
326
+ if( this.bExpanded ){
327
+ cnList.push(cn.expanded);
328
+ }
329
+ if( this.hasChildren() !== false ){
330
+ cnList.push(cn.hasChildren);
331
+ }
332
+ if( data.isLazy && this.childList === null ){
333
+ cnList.push(cn.lazy);
334
+ }
335
+ if( isLastSib ){
336
+ cnList.push(cn.lastsib);
337
+ }
338
+ if( this.bSelected ){
339
+ cnList.push(cn.selected);
340
+ }
341
+ if( this.hasSubSel ){
342
+ cnList.push(cn.partsel);
343
+ }
344
+ if( tree.activeNode === this ){
345
+ cnList.push(cn.active);
346
+ }
347
+ if( data.addClass ){
348
+ cnList.push(data.addClass);
349
+ }
350
+ // IE6 doesn't correctly evaluate multiple class names,
351
+ // so we create combined class names that can be used in the CSS
352
+ cnList.push(cn.combinedExpanderPrefix
353
+ + (this.bExpanded ? "e" : "c")
354
+ + (data.isLazy && this.childList === null ? "d" : "")
355
+ + (isLastSib ? "l" : "")
356
+ );
357
+ cnList.push(cn.combinedIconPrefix
358
+ + (this.bExpanded ? "e" : "c")
359
+ + (data.isFolder ? "f" : "")
360
+ );
361
+ this.span.className = cnList.join(" ");
362
+
363
+ // TODO: we should not set this in the <span> tag also, if we set it here:
364
+ this.li.className = isLastSib ? cn.lastsib : "";
365
+
366
+ // Allow tweaking, binding, after node was created for the first time
367
+ if(firstTime && opts.onCreate){
368
+ opts.onCreate.call(tree, this, this.span);
369
+ }
370
+ // Hide children, if node is collapsed
371
+ // this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none";
372
+ // Allow tweaking after node state was rendered
373
+ if(opts.onRender){
374
+ opts.onRender.call(tree, this, this.span);
375
+ }
376
+ }
377
+ // Visit child nodes
378
+ if( (this.bExpanded || includeInvisible === true) && this.childList ) {
379
+ for(var i=0, l=this.childList.length; i<l; i++) {
380
+ this.childList[i].render(false, includeInvisible);
381
+ }
382
+ // Make sure the tag order matches the child array
383
+ this._fixOrder();
384
+ }
385
+ // Hide children, if node is collapsed
386
+ if( this.ul ) {
387
+ var isHidden = (this.ul.style.display === "none");
388
+ var isExpanded = !!this.bExpanded;
389
+ // logMsg("isHidden:%s", isHidden);
390
+ if( useEffects && opts.fx && (isHidden === isExpanded) ) {
391
+ var duration = opts.fx.duration || 200;
392
+ $(this.ul).animate(opts.fx, duration);
393
+ } else {
394
+ this.ul.style.display = ( this.bExpanded || !parent ) ? "" : "none";
395
+ }
396
+ }
397
+ },
398
+ /** Return '/id1/id2/id3'. */
399
+ getKeyPath: function(excludeSelf) {
400
+ var path = [];
401
+ this.visitParents(function(node){
402
+ if(node.parent){
403
+ path.unshift(node.data.key);
404
+ }
405
+ }, !excludeSelf);
406
+ return "/" + path.join(this.tree.options.keyPathSeparator);
407
+ },
408
+
409
+ getParent: function() {
410
+ return this.parent;
411
+ },
412
+
413
+ getChildren: function() {
414
+ if(this.hasChildren() === undefined){
415
+ return undefined; // Lazy node: unloaded, currently loading, or load error
416
+ }
417
+ return this.childList;
418
+ },
419
+
420
+ /** Check if node has children (returns undefined, if not sure). */
421
+ hasChildren: function() {
422
+ if(this.data.isLazy){
423
+ if(this.childList === null || this.childList === undefined){
424
+ // Not yet loaded
425
+ return undefined;
426
+ }else if(this.childList.length === 0){
427
+ // Loaded, but response was empty
428
+ return false;
429
+ }else if(this.childList.length === 1 && this.childList[0].isStatusNode()){
430
+ // Currently loading or load error
431
+ return undefined;
432
+ }
433
+ return true;
434
+ }
435
+ return !!this.childList;
436
+ },
437
+
438
+ isFirstSibling: function() {
439
+ var p = this.parent;
440
+ return !p || p.childList[0] === this;
441
+ },
442
+
443
+ isLastSibling: function() {
444
+ var p = this.parent;
445
+ return !p || p.childList[p.childList.length-1] === this;
446
+ },
447
+
448
+ isLoading: function() {
449
+ return !!this._isLoading;
450
+ },
451
+
452
+ getPrevSibling: function() {
453
+ if( !this.parent ){
454
+ return null;
455
+ }
456
+ var ac = this.parent.childList;
457
+ for(var i=1, l=ac.length; i<l; i++){ // start with 1, so prev(first) = null
458
+ if( ac[i] === this ){
459
+ return ac[i-1];
460
+ }
461
+ }
462
+ return null;
463
+ },
464
+
465
+ getNextSibling: function() {
466
+ if( !this.parent ){
467
+ return null;
468
+ }
469
+ var ac = this.parent.childList;
470
+ for(var i=0, l=ac.length-1; i<l; i++){ // up to length-2, so next(last) = null
471
+ if( ac[i] === this ){
472
+ return ac[i+1];
473
+ }
474
+ }
475
+ return null;
476
+ },
477
+
478
+ isStatusNode: function() {
479
+ return (this.data.isStatusNode === true);
480
+ },
481
+
482
+ isChildOf: function(otherNode) {
483
+ return (this.parent && this.parent === otherNode);
484
+ },
485
+
486
+ isDescendantOf: function(otherNode) {
487
+ if(!otherNode){
488
+ return false;
489
+ }
490
+ var p = this.parent;
491
+ while( p ) {
492
+ if( p === otherNode ){
493
+ return true;
494
+ }
495
+ p = p.parent;
496
+ }
497
+ return false;
498
+ },
499
+
500
+ countChildren: function() {
501
+ var cl = this.childList;
502
+ if( !cl ){
503
+ return 0;
504
+ }
505
+ var n = cl.length;
506
+ for(var i=0, l=n; i<l; i++){
507
+ var child = cl[i];
508
+ n += child.countChildren();
509
+ }
510
+ return n;
511
+ },
512
+
513
+ /**Sort child list by title.
514
+ * cmd: optional compare function.
515
+ * deep: optional: pass true to sort all descendant nodes.
516
+ */
517
+ sortChildren: function(cmp, deep) {
518
+ var cl = this.childList;
519
+ if( !cl ){
520
+ return;
521
+ }
522
+ cmp = cmp || function(a, b) {
523
+ // return a.data.title === b.data.title ? 0 : a.data.title > b.data.title ? 1 : -1;
524
+ var x = a.data.title.toLowerCase(),
525
+ y = b.data.title.toLowerCase();
526
+ return x === y ? 0 : x > y ? 1 : -1;
527
+ };
528
+ cl.sort(cmp);
529
+ if( deep ){
530
+ for(var i=0, l=cl.length; i<l; i++){
531
+ if( cl[i].childList ){
532
+ cl[i].sortChildren(cmp, "$norender$");
533
+ }
534
+ }
535
+ }
536
+ if( deep !== "$norender$" ){
537
+ this.render();
538
+ }
539
+ },
540
+
541
+ _setStatusNode: function(data) {
542
+ // Create, modify or remove the status child node (pass 'null', to remove it).
543
+ var firstChild = ( this.childList ? this.childList[0] : null );
544
+ if( !data ) {
545
+ if ( firstChild && firstChild.isStatusNode()) {
546
+ try{
547
+ // I've seen exceptions here with loadKeyPath...
548
+ if(this.ul){
549
+ this.ul.removeChild(firstChild.li);
550
+ firstChild.li = null; // avoid leaks (issue 215)
551
+ }
552
+ }catch(e){}
553
+ if( this.childList.length === 1 ){
554
+ this.childList = [];
555
+ }else{
556
+ this.childList.shift();
557
+ }
558
+ }
559
+ } else if ( firstChild ) {
560
+ data.isStatusNode = true;
561
+ data.key = "_statusNode";
562
+ firstChild.data = data;
563
+ firstChild.render();
564
+ } else {
565
+ data.isStatusNode = true;
566
+ data.key = "_statusNode";
567
+ firstChild = this.addChild(data);
568
+ }
569
+ },
570
+
571
+ setLazyNodeStatus: function(lts, opts) {
572
+ var tooltip = (opts && opts.tooltip) ? opts.tooltip : null,
573
+ info = (opts && opts.info) ? " (" + opts.info + ")" : "";
574
+ switch( lts ) {
575
+ case DTNodeStatus_Ok:
576
+ this._setStatusNode(null);
577
+ $(this.span).removeClass(this.tree.options.classNames.nodeLoading);
578
+ this._isLoading = false;
579
+ // this.render();
580
+ if( this.tree.options.autoFocus ) {
581
+ if( this === this.tree.tnRoot && this.childList && this.childList.length > 0) {
582
+ // special case: using ajaxInit
583
+ this.childList[0].focus();
584
+ } else {
585
+ this.focus();
586
+ }
587
+ }
588
+ break;
589
+ case DTNodeStatus_Loading:
590
+ this._isLoading = true;
591
+ $(this.span).addClass(this.tree.options.classNames.nodeLoading);
592
+ // The root is hidden, so we set a temporary status child
593
+ if(!this.parent){
594
+ this._setStatusNode({
595
+ title: this.tree.options.strings.loading + info,
596
+ tooltip: tooltip,
597
+ addClass: this.tree.options.classNames.nodeWait
598
+ });
599
+ }
600
+ break;
601
+ case DTNodeStatus_Error:
602
+ this._isLoading = false;
603
+ // $(this.span).addClass(this.tree.options.classNames.nodeError);
604
+ this._setStatusNode({
605
+ title: this.tree.options.strings.loadError + info,
606
+ tooltip: tooltip,
607
+ addClass: this.tree.options.classNames.nodeError
608
+ });
609
+ break;
610
+ default:
611
+ throw "Bad LazyNodeStatus: '" + lts + "'.";
612
+ }
613
+ },
614
+
615
+ _parentList: function(includeRoot, includeSelf) {
616
+ var l = [];
617
+ var dtn = includeSelf ? this : this.parent;
618
+ while( dtn ) {
619
+ if( includeRoot || dtn.parent ){
620
+ l.unshift(dtn);
621
+ }
622
+ dtn = dtn.parent;
623
+ }
624
+ return l;
625
+ },
626
+ getLevel: function() {
627
+ /**
628
+ * Return node depth. 0: System root node, 1: visible top-level node.
629
+ */
630
+ var level = 0;
631
+ var dtn = this.parent;
632
+ while( dtn ) {
633
+ level++;
634
+ dtn = dtn.parent;
635
+ }
636
+ return level;
637
+ },
638
+
639
+ _getTypeForOuterNodeEvent: function(event) {
640
+ /** Return the inner node span (title, checkbox or expander) if
641
+ * event.target points to the outer span.
642
+ * This function should fix issue #93:
643
+ * FF2 ignores empty spans, when generating events (returning the parent instead).
644
+ */
645
+ var cns = this.tree.options.classNames;
646
+ var target = event.target;
647
+ // Only process clicks on an outer node span (probably due to a FF2 event handling bug)
648
+ if( target.className.indexOf(cns.node) < 0 ) {
649
+ return null;
650
+ }
651
+ // Event coordinates, relative to outer node span:
652
+ var eventX = event.pageX - target.offsetLeft;
653
+ var eventY = event.pageY - target.offsetTop;
654
+
655
+ for(var i=0, l=target.childNodes.length; i<l; i++) {
656
+ var cn = target.childNodes[i];
657
+ var x = cn.offsetLeft - target.offsetLeft;
658
+ var y = cn.offsetTop - target.offsetTop;
659
+ var nx = cn.clientWidth, ny = cn.clientHeight;
660
+ // alert (cn.className + ": " + x + ", " + y + ", s:" + nx + ", " + ny);
661
+ if( eventX >= x && eventX <= (x+nx) && eventY >= y && eventY <= (y+ny) ) {
662
+ // alert("HIT "+ cn.className);
663
+ if( cn.className==cns.title ){
664
+ return "title";
665
+ }else if( cn.className==cns.expander ){
666
+ return "expander";
667
+ }else if( cn.className==cns.checkbox ){
668
+ return "checkbox";
669
+ }else if( cn.className==cns.nodeIcon ){
670
+ return "icon";
671
+ }
672
+ }
673
+ }
674
+ return "prefix";
675
+ },
676
+
677
+ getEventTargetType: function(event) {
678
+ // Return the part of a node, that a click event occured on.
679
+ // Note: there is no check, if the event was fired on THIS node.
680
+ var tcn = event && event.target ? event.target.className : "",
681
+ cns = this.tree.options.classNames;
682
+
683
+ if( tcn === cns.title ){
684
+ return "title";
685
+ }else if( tcn === cns.expander ){
686
+ return "expander";
687
+ }else if( tcn === cns.checkbox ){
688
+ return "checkbox";
689
+ }else if( tcn === cns.nodeIcon ){
690
+ return "icon";
691
+ }else if( tcn === cns.empty || tcn === cns.vline || tcn === cns.connector ){
692
+ return "prefix";
693
+ }else if( tcn.indexOf(cns.node) >= 0 ){
694
+ // FIX issue #93
695
+ return this._getTypeForOuterNodeEvent(event);
696
+ }
697
+ return null;
698
+ },
699
+
700
+ isVisible: function() {
701
+ // Return true, if all parents are expanded.
702
+ var parents = this._parentList(true, false);
703
+ for(var i=0, l=parents.length; i<l; i++){
704
+ if( ! parents[i].bExpanded ){ return false; }
705
+ }
706
+ return true;
707
+ },
708
+
709
+ makeVisible: function() {
710
+ // Make sure, all parents are expanded
711
+ var parents = this._parentList(true, false);
712
+ for(var i=0, l=parents.length; i<l; i++){
713
+ parents[i]._expand(true);
714
+ }
715
+ },
716
+
717
+ focus: function() {
718
+ // TODO: check, if we already have focus
719
+ // this.tree.logDebug("dtnode.focus(): %o", this);
720
+ this.makeVisible();
721
+ try {
722
+ $(this.span).find(">a").focus();
723
+ } catch(e) { }
724
+ },
725
+
726
+ isFocused: function() {
727
+ return (this.tree.tnFocused === this);
728
+ },
729
+
730
+ _activate: function(flag, fireEvents) {
731
+ // (De)Activate - but not focus - this node.
732
+ this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o", flag, fireEvents, this);
733
+ var opts = this.tree.options;
734
+ if( this.data.isStatusNode ){
735
+ return;
736
+ }
737
+ if ( fireEvents && opts.onQueryActivate && opts.onQueryActivate.call(this.tree, flag, this) === false ){
738
+ return; // Callback returned false
739
+ }
740
+ if( flag ) {
741
+ // Activate
742
+ if( this.tree.activeNode ) {
743
+ if( this.tree.activeNode === this ){
744
+ return;
745
+ }
746
+ this.tree.activeNode.deactivate();
747
+ }
748
+ if( opts.activeVisible ){
749
+ this.makeVisible();
750
+ }
751
+ this.tree.activeNode = this;
752
+ if( opts.persist ){
753
+ $.cookie(opts.cookieId+"-active", this.data.key, opts.cookie);
754
+ }
755
+ this.tree.persistence.activeKey = this.data.key;
756
+ $(this.span).addClass(opts.classNames.active);
757
+ if ( fireEvents && opts.onActivate ){
758
+ opts.onActivate.call(this.tree, this);
759
+ }
760
+ } else {
761
+ // Deactivate
762
+ if( this.tree.activeNode === this ) {
763
+ if ( opts.onQueryActivate && opts.onQueryActivate.call(this.tree, false, this) === false ){
764
+ return; // Callback returned false
765
+ }
766
+ $(this.span).removeClass(opts.classNames.active);
767
+ if( opts.persist ) {
768
+ // Note: we don't pass null, but ''. So the cookie is not deleted.
769
+ // If we pass null, we also have to pass a COPY of opts, because $cookie will override opts.expires (issue 84)
770
+ $.cookie(opts.cookieId+"-active", "", opts.cookie);
771
+ }
772
+ this.tree.persistence.activeKey = null;
773
+ this.tree.activeNode = null;
774
+ if ( fireEvents && opts.onDeactivate ){
775
+ opts.onDeactivate.call(this.tree, this);
776
+ }
777
+ }
778
+ }
779
+ },
780
+
781
+ activate: function() {
782
+ // Select - but not focus - this node.
783
+ // this.tree.logDebug("dtnode.activate(): %o", this);
784
+ this._activate(true, true);
785
+ },
786
+
787
+ activateSilently: function() {
788
+ this._activate(true, false);
789
+ },
790
+
791
+ deactivate: function() {
792
+ // this.tree.logDebug("dtnode.deactivate(): %o", this);
793
+ this._activate(false, true);
794
+ },
795
+
796
+ isActive: function() {
797
+ return (this.tree.activeNode === this);
798
+ },
799
+
800
+ _userActivate: function() {
801
+ // Handle user click / [space] / [enter], according to clickFolderMode.
802
+ var activate = true;
803
+ var expand = false;
804
+ if ( this.data.isFolder ) {
805
+ switch( this.tree.options.clickFolderMode ) {
806
+ case 2:
807
+ activate = false;
808
+ expand = true;
809
+ break;
810
+ case 3:
811
+ activate = expand = true;
812
+ break;
813
+ }
814
+ }
815
+ if( this.parent === null ) {
816
+ expand = false;
817
+ }
818
+ if( expand ) {
819
+ this.toggleExpand();
820
+ this.focus();
821
+ }
822
+ if( activate ) {
823
+ this.activate();
824
+ }
825
+ },
826
+
827
+ _setSubSel: function(hasSubSel) {
828
+ if( hasSubSel ) {
829
+ this.hasSubSel = true;
830
+ $(this.span).addClass(this.tree.options.classNames.partsel);
831
+ } else {
832
+ this.hasSubSel = false;
833
+ $(this.span).removeClass(this.tree.options.classNames.partsel);
834
+ }
835
+ },
836
+ /**
837
+ * Fix selection and partsel status, of parent nodes, according to current status of
838
+ * end nodes.
839
+ */
840
+ _updatePartSelectionState: function() {
841
+ // alert("_updatePartSelectionState " + this);
842
+ // this.tree.logDebug("_updatePartSelectionState() - %o", this);
843
+ var sel;
844
+ // Return `true` or `false` for end nodes and remove part-sel flag
845
+ if( ! this.hasChildren() ){
846
+ sel = (this.bSelected && !this.data.unselectable && !this.data.isStatusNode);
847
+ this._setSubSel(false);
848
+ return sel;
849
+ }
850
+ // Return `true`, `false`, or `undefined` for parent nodes
851
+ var i, l,
852
+ cl = this.childList,
853
+ allSelected = true,
854
+ allDeselected = true;
855
+ for(i=0, l=cl.length; i<l; i++) {
856
+ var n = cl[i],
857
+ s = n._updatePartSelectionState();
858
+ if( s !== false){
859
+ allDeselected = false;
860
+ }
861
+ if( s !== true){
862
+ allSelected = false;
863
+ }
864
+ }
865
+ if( allSelected ){
866
+ sel = true;
867
+ } else if ( allDeselected ){
868
+ sel = false;
869
+ } else {
870
+ sel = undefined;
871
+ }
872
+ this._setSubSel(sel === undefined);
873
+ this.bSelected = (sel === true);
874
+ return sel;
875
+ },
876
+
877
+ /**
878
+ * Fix selection status, after this node was (de)selected in multi-hier mode.
879
+ * This includes (de)selecting all children.
880
+ */
881
+ _fixSelectionState: function() {
882
+ // alert("_fixSelectionState " + this);
883
+ // this.tree.logDebug("_fixSelectionState(%s) - %o", this.bSelected, this);
884
+ var p, i, l;
885
+ if( this.bSelected ) {
886
+ // Select all children
887
+ this.visit(function(node){
888
+ node.parent._setSubSel(true);
889
+ if(!node.data.unselectable){
890
+ node._select(true, false, false);
891
+ }
892
+ });
893
+ // Select parents, if all children are selected
894
+ p = this.parent;
895
+ while( p ) {
896
+ p._setSubSel(true);
897
+ var allChildsSelected = true;
898
+ for(i=0, l=p.childList.length; i<l; i++) {
899
+ var n = p.childList[i];
900
+ if( !n.bSelected && !n.data.isStatusNode && !n.data.unselectable) {
901
+ allChildsSelected = false;
902
+ break;
903
+ }
904
+ }
905
+ if( allChildsSelected ){
906
+ p._select(true, false, false);
907
+ }
908
+ p = p.parent;
909
+ }
910
+ } else {
911
+ // Deselect all children
912
+ this._setSubSel(false);
913
+ this.visit(function(node){
914
+ node._setSubSel(false);
915
+ node._select(false, false, false);
916
+ });
917
+ // Deselect parents, and recalc hasSubSel
918
+ p = this.parent;
919
+ while( p ) {
920
+ p._select(false, false, false);
921
+ var isPartSel = false;
922
+ for(i=0, l=p.childList.length; i<l; i++) {
923
+ if( p.childList[i].bSelected || p.childList[i].hasSubSel ) {
924
+ isPartSel = true;
925
+ break;
926
+ }
927
+ }
928
+ p._setSubSel(isPartSel);
929
+ p = p.parent;
930
+ }
931
+ }
932
+ },
933
+
934
+ _select: function(sel, fireEvents, deep) {
935
+ // Select - but not focus - this node.
936
+ // this.tree.logDebug("dtnode._select(%o) - %o", sel, this);
937
+ var opts = this.tree.options;
938
+ if( this.data.isStatusNode ){
939
+ return;
940
+ }
941
+ //
942
+ if( this.bSelected === sel ) {
943
+ // this.tree.logDebug("dtnode._select(%o) IGNORED - %o", sel, this);
944
+ return;
945
+ }
946
+ // Allow event listener to abort selection
947
+ if ( fireEvents && opts.onQuerySelect && opts.onQuerySelect.call(this.tree, sel, this) === false ){
948
+ return; // Callback returned false
949
+ }
950
+ // Force single-selection
951
+ if( opts.selectMode==1 && sel ) {
952
+ this.tree.visit(function(node){
953
+ if( node.bSelected ) {
954
+ // Deselect; assuming that in selectMode:1 there's max. one other selected node
955
+ node._select(false, false, false);
956
+ return false;
957
+ }
958
+ });
959
+ }
960
+
961
+ this.bSelected = sel;
962
+ // this.tree._changeNodeList("select", this, sel);
963
+
964
+ if( sel ) {
965
+ if( opts.persist ){
966
+ this.tree.persistence.addSelect(this.data.key);
967
+ }
968
+ $(this.span).addClass(opts.classNames.selected);
969
+
970
+ if( deep && opts.selectMode === 3 ){
971
+ this._fixSelectionState();
972
+ }
973
+ if ( fireEvents && opts.onSelect ){
974
+ opts.onSelect.call(this.tree, true, this);
975
+ }
976
+ } else {
977
+ if( opts.persist ){
978
+ this.tree.persistence.clearSelect(this.data.key);
979
+ }
980
+ $(this.span).removeClass(opts.classNames.selected);
981
+
982
+ if( deep && opts.selectMode === 3 ){
983
+ this._fixSelectionState();
984
+ }
985
+ if ( fireEvents && opts.onSelect ){
986
+ opts.onSelect.call(this.tree, false, this);
987
+ }
988
+ }
989
+ },
990
+
991
+ select: function(sel) {
992
+ // Select - but not focus - this node.
993
+ // this.tree.logDebug("dtnode.select(%o) - %o", sel, this);
994
+ if( this.data.unselectable ){
995
+ return this.bSelected;
996
+ }
997
+ return this._select(sel!==false, true, true);
998
+ },
999
+
1000
+ toggleSelect: function() {
1001
+ // this.tree.logDebug("dtnode.toggleSelect() - %o", this);
1002
+ return this.select(!this.bSelected);
1003
+ },
1004
+
1005
+ isSelected: function() {
1006
+ return this.bSelected;
1007
+ },
1008
+
1009
+ isLazy: function() {
1010
+ return !!this.data.isLazy;
1011
+ },
1012
+
1013
+ _loadContent: function() {
1014
+ try {
1015
+ var opts = this.tree.options;
1016
+ this.tree.logDebug("_loadContent: start - %o", this);
1017
+ this.setLazyNodeStatus(DTNodeStatus_Loading);
1018
+ if( true === opts.onLazyRead.call(this.tree, this) ) {
1019
+ // If function returns 'true', we assume that the loading is done:
1020
+ this.setLazyNodeStatus(DTNodeStatus_Ok);
1021
+ // Otherwise (i.e. if the loading was started as an asynchronous process)
1022
+ // the onLazyRead(dtnode) handler is expected to call dtnode.setLazyNodeStatus(DTNodeStatus_Ok/_Error) when done.
1023
+ this.tree.logDebug("_loadContent: succeeded - %o", this);
1024
+ }
1025
+ } catch(e) {
1026
+ this.tree.logWarning("_loadContent: failed - %o", e);
1027
+ this.setLazyNodeStatus(DTNodeStatus_Error, {tooltip: ""+e});
1028
+ }
1029
+ },
1030
+
1031
+ _expand: function(bExpand, forceSync) {
1032
+ if( this.bExpanded === bExpand ) {
1033
+ this.tree.logDebug("dtnode._expand(%o) IGNORED - %o", bExpand, this);
1034
+ return;
1035
+ }
1036
+ this.tree.logDebug("dtnode._expand(%o) - %o", bExpand, this);
1037
+ var opts = this.tree.options;
1038
+ if( !bExpand && this.getLevel() < opts.minExpandLevel ) {
1039
+ this.tree.logDebug("dtnode._expand(%o) prevented collapse - %o", bExpand, this);
1040
+ return;
1041
+ }
1042
+ if ( opts.onQueryExpand && opts.onQueryExpand.call(this.tree, bExpand, this) === false ){
1043
+ return; // Callback returned false
1044
+ }
1045
+ this.bExpanded = bExpand;
1046
+
1047
+ // Persist expand state
1048
+ if( opts.persist ) {
1049
+ if( bExpand ){
1050
+ this.tree.persistence.addExpand(this.data.key);
1051
+ }else{
1052
+ this.tree.persistence.clearExpand(this.data.key);
1053
+ }
1054
+ }
1055
+ // Do not apply animations in init phase, or before lazy-loading
1056
+ var allowEffects = !(this.data.isLazy && this.childList === null)
1057
+ && !this._isLoading
1058
+ && !forceSync;
1059
+ this.render(allowEffects);
1060
+
1061
+ // Auto-collapse mode: collapse all siblings
1062
+ if( this.bExpanded && this.parent && opts.autoCollapse ) {
1063
+ var parents = this._parentList(false, true);
1064
+ for(var i=0, l=parents.length; i<l; i++){
1065
+ parents[i].collapseSiblings();
1066
+ }
1067
+ }
1068
+ // If the currently active node is now hidden, deactivate it
1069
+ if( opts.activeVisible && this.tree.activeNode && ! this.tree.activeNode.isVisible() ) {
1070
+ this.tree.activeNode.deactivate();
1071
+ }
1072
+ // Expanding a lazy node: set 'loading...' and call callback
1073
+ if( bExpand && this.data.isLazy && this.childList === null && !this._isLoading ) {
1074
+ this._loadContent();
1075
+ return;
1076
+ }
1077
+ if ( opts.onExpand ){
1078
+ opts.onExpand.call(this.tree, bExpand, this);
1079
+ }
1080
+ },
1081
+
1082
+ isExpanded: function() {
1083
+ return this.bExpanded;
1084
+ },
1085
+
1086
+ expand: function(flag) {
1087
+ flag = (flag !== false);
1088
+ if( !this.childList && !this.data.isLazy && flag ){
1089
+ return; // Prevent expanding empty nodes
1090
+ } else if( this.parent === null && !flag ){
1091
+ return; // Prevent collapsing the root
1092
+ }
1093
+ this._expand(flag);
1094
+ },
1095
+
1096
+ scheduleAction: function(mode, ms) {
1097
+ /** Schedule activity for delayed execution (cancel any pending request).
1098
+ * scheduleAction('cancel') will cancel the request.
1099
+ */
1100
+ if( this.tree.timer ) {
1101
+ clearTimeout(this.tree.timer);
1102
+ this.tree.logDebug("clearTimeout(%o)", this.tree.timer);
1103
+ }
1104
+ var self = this; // required for closures
1105
+ switch (mode) {
1106
+ case "cancel":
1107
+ // Simply made sure that timer was cleared
1108
+ break;
1109
+ case "expand":
1110
+ this.tree.timer = setTimeout(function(){
1111
+ self.tree.logDebug("setTimeout: trigger expand");
1112
+ self.expand(true);
1113
+ }, ms);
1114
+ break;
1115
+ case "activate":
1116
+ this.tree.timer = setTimeout(function(){
1117
+ self.tree.logDebug("setTimeout: trigger activate");
1118
+ self.activate();
1119
+ }, ms);
1120
+ break;
1121
+ default:
1122
+ throw "Invalid mode " + mode;
1123
+ }
1124
+ this.tree.logDebug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
1125
+ },
1126
+
1127
+ toggleExpand: function() {
1128
+ this.expand(!this.bExpanded);
1129
+ },
1130
+
1131
+ collapseSiblings: function() {
1132
+ if( this.parent === null ){
1133
+ return;
1134
+ }
1135
+ var ac = this.parent.childList;
1136
+ for (var i=0, l=ac.length; i<l; i++) {
1137
+ if ( ac[i] !== this && ac[i].bExpanded ){
1138
+ ac[i]._expand(false);
1139
+ }
1140
+ }
1141
+ },
1142
+
1143
+ _onClick: function(event) {
1144
+ // this.tree.logDebug("dtnode.onClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
1145
+ var targetType = this.getEventTargetType(event);
1146
+ if( targetType === "expander" ) {
1147
+ // Clicking the expander icon always expands/collapses
1148
+ this.toggleExpand();
1149
+ this.focus(); // issue 95
1150
+ } else if( targetType === "checkbox" ) {
1151
+ // Clicking the checkbox always (de)selects
1152
+ this.toggleSelect();
1153
+ this.focus(); // issue 95
1154
+ } else {
1155
+ this._userActivate();
1156
+ var aTag = this.span.getElementsByTagName("a");
1157
+ if(aTag[0]){
1158
+ // issue 154
1159
+ // TODO: check if still required on IE 9:
1160
+ // Chrome and Safari don't focus the a-tag on click,
1161
+ // but calling focus() seem to have problems on IE:
1162
+ // http://code.google.com/p/dynatree/issues/detail?id=154
1163
+ if(!$.browser.msie){
1164
+ aTag[0].focus();
1165
+ }
1166
+ }else{
1167
+ // 'noLink' option was set
1168
+ return true;
1169
+ }
1170
+ }
1171
+ // Make sure that clicks stop, otherwise <a href='#'> jumps to the top
1172
+ event.preventDefault();
1173
+ },
1174
+
1175
+ _onDblClick: function(event) {
1176
+ // this.tree.logDebug("dtnode.onDblClick(" + event.type + "): dtnode:" + this + ", button:" + event.button + ", which: " + event.which);
1177
+ },
1178
+
1179
+ _onKeydown: function(event) {
1180
+ // this.tree.logDebug("dtnode.onKeydown(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
1181
+ var handled = true,
1182
+ sib;
1183
+ // alert("keyDown" + event.which);
1184
+
1185
+ switch( event.which ) {
1186
+ // charCodes:
1187
+ // case 43: // '+'
1188
+ case 107: // '+'
1189
+ case 187: // '+' @ Chrome, Safari
1190
+ if( !this.bExpanded ){ this.toggleExpand(); }
1191
+ break;
1192
+ // case 45: // '-'
1193
+ case 109: // '-'
1194
+ case 189: // '+' @ Chrome, Safari
1195
+ if( this.bExpanded ){ this.toggleExpand(); }
1196
+ break;
1197
+ //~ case 42: // '*'
1198
+ //~ break;
1199
+ //~ case 47: // '/'
1200
+ //~ break;
1201
+ // case 13: // <enter>
1202
+ // <enter> on a focused <a> tag seems to generate a click-event.
1203
+ // this._userActivate();
1204
+ // break;
1205
+ case 32: // <space>
1206
+ this._userActivate();
1207
+ break;
1208
+ case 8: // <backspace>
1209
+ if( this.parent ){
1210
+ this.parent.focus();
1211
+ }
1212
+ break;
1213
+ case 37: // <left>
1214
+ if( this.bExpanded ) {
1215
+ this.toggleExpand();
1216
+ this.focus();
1217
+ // } else if( this.parent && (this.tree.options.rootVisible || this.parent.parent) ) {
1218
+ } else if( this.parent && this.parent.parent ) {
1219
+ this.parent.focus();
1220
+ }
1221
+ break;
1222
+ case 39: // <right>
1223
+ if( !this.bExpanded && (this.childList || this.data.isLazy) ) {
1224
+ this.toggleExpand();
1225
+ this.focus();
1226
+ } else if( this.childList ) {
1227
+ this.childList[0].focus();
1228
+ }
1229
+ break;
1230
+ case 38: // <up>
1231
+ sib = this.getPrevSibling();
1232
+ while( sib && sib.bExpanded && sib.childList ){
1233
+ sib = sib.childList[sib.childList.length-1];
1234
+ }
1235
+ // if( !sib && this.parent && (this.tree.options.rootVisible || this.parent.parent) )
1236
+ if( !sib && this.parent && this.parent.parent ){
1237
+ sib = this.parent;
1238
+ }
1239
+ if( sib ){
1240
+ sib.focus();
1241
+ }
1242
+ break;
1243
+ case 40: // <down>
1244
+ if( this.bExpanded && this.childList ) {
1245
+ sib = this.childList[0];
1246
+ } else {
1247
+ var parents = this._parentList(false, true);
1248
+ for(var i=parents.length-1; i>=0; i--) {
1249
+ sib = parents[i].getNextSibling();
1250
+ if( sib ){ break; }
1251
+ }
1252
+ }
1253
+ if( sib ){
1254
+ sib.focus();
1255
+ }
1256
+ break;
1257
+ default:
1258
+ handled = false;
1259
+ }
1260
+ // Return false, if handled, to prevent default processing
1261
+ // return !handled;
1262
+ if(handled){
1263
+ event.preventDefault();
1264
+ }
1265
+ },
1266
+
1267
+ _onKeypress: function(event) {
1268
+ // onKeypress is only hooked to allow user callbacks.
1269
+ // We don't process it, because IE and Safari don't fire keypress for cursor keys.
1270
+ // this.tree.logDebug("dtnode.onKeypress(" + event.type + "): dtnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
1271
+ },
1272
+
1273
+ _onFocus: function(event) {
1274
+ // Handles blur and focus events.
1275
+ // this.tree.logDebug("dtnode.onFocus(%o): %o", event, this);
1276
+ var opts = this.tree.options;
1277
+ if ( event.type == "blur" || event.type == "focusout" ) {
1278
+ if ( opts.onBlur ){
1279
+ opts.onBlur.call(this.tree, this);
1280
+ }
1281
+ if( this.tree.tnFocused ){
1282
+ $(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
1283
+ }
1284
+ this.tree.tnFocused = null;
1285
+ if( opts.persist ){
1286
+ $.cookie(opts.cookieId+"-focus", "", opts.cookie);
1287
+ }
1288
+ } else if ( event.type=="focus" || event.type=="focusin") {
1289
+ // Fix: sometimes the blur event is not generated
1290
+ if( this.tree.tnFocused && this.tree.tnFocused !== this ) {
1291
+ this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o", this.tree.tnFocused);
1292
+ $(this.tree.tnFocused.span).removeClass(opts.classNames.focused);
1293
+ }
1294
+ this.tree.tnFocused = this;
1295
+ if ( opts.onFocus ){
1296
+ opts.onFocus.call(this.tree, this);
1297
+ }
1298
+ $(this.tree.tnFocused.span).addClass(opts.classNames.focused);
1299
+ if( opts.persist ){
1300
+ $.cookie(opts.cookieId+"-focus", this.data.key, opts.cookie);
1301
+ }
1302
+ }
1303
+ // TODO: return anything?
1304
+ // return false;
1305
+ },
1306
+
1307
+ visit: function(fn, includeSelf) {
1308
+ // Call fn(node) for all child nodes. Stop iteration, if fn() returns false.
1309
+ var res = true;
1310
+ if( includeSelf === true ) {
1311
+ res = fn(this);
1312
+ if( res === false || res == "skip" ){
1313
+ return res;
1314
+ }
1315
+ }
1316
+ if(this.childList){
1317
+ for(var i=0, l=this.childList.length; i<l; i++){
1318
+ res = this.childList[i].visit(fn, true);
1319
+ if( res === false ){
1320
+ break;
1321
+ }
1322
+ }
1323
+ }
1324
+ return res;
1325
+ },
1326
+
1327
+ visitParents: function(fn, includeSelf) {
1328
+ // Visit parent nodes (bottom up)
1329
+ if(includeSelf && fn(this) === false){
1330
+ return false;
1331
+ }
1332
+ var p = this.parent;
1333
+ while( p ) {
1334
+ if(fn(p) === false){
1335
+ return false;
1336
+ }
1337
+ p = p.parent;
1338
+ }
1339
+ return true;
1340
+ },
1341
+
1342
+ remove: function() {
1343
+ // Remove this node
1344
+ // this.tree.logDebug ("%s.remove()", this);
1345
+ if ( this === this.tree.root ){
1346
+ throw "Cannot remove system root";
1347
+ }
1348
+ return this.parent.removeChild(this);
1349
+ },
1350
+
1351
+ removeChild: function(tn) {
1352
+ // Remove tn from list of direct children.
1353
+ var ac = this.childList;
1354
+ if( ac.length == 1 ) {
1355
+ if( tn !== ac[0] ){
1356
+ throw "removeChild: invalid child";
1357
+ }
1358
+ return this.removeChildren();
1359
+ }
1360
+ if( tn === this.tree.activeNode ){
1361
+ tn.deactivate();
1362
+ }
1363
+ if( this.tree.options.persist ) {
1364
+ if( tn.bSelected ){
1365
+ this.tree.persistence.clearSelect(tn.data.key);
1366
+ }
1367
+ if ( tn.bExpanded ){
1368
+ this.tree.persistence.clearExpand(tn.data.key);
1369
+ }
1370
+ }
1371
+ tn.removeChildren(true);
1372
+ // this.div.removeChild(tn.div);
1373
+ this.ul.removeChild(tn.li);
1374
+ for(var i=0, l=ac.length; i<l; i++) {
1375
+ if( ac[i] === tn ) {
1376
+ this.childList.splice(i, 1);
1377
+ // delete tn; // JSLint complained
1378
+ break;
1379
+ }
1380
+ }
1381
+ },
1382
+
1383
+ removeChildren: function(isRecursiveCall, retainPersistence) {
1384
+ // Remove all child nodes (more efficiently than recursive remove())
1385
+ this.tree.logDebug("%s.removeChildren(%o)", this, isRecursiveCall);
1386
+ var tree = this.tree;
1387
+ var ac = this.childList;
1388
+ if( ac ) {
1389
+ for(var i=0, l=ac.length; i<l; i++) {
1390
+ var tn = ac[i];
1391
+ if ( tn === tree.activeNode && !retainPersistence ){
1392
+ tn.deactivate();
1393
+ }
1394
+ if( this.tree.options.persist && !retainPersistence ) {
1395
+ if( tn.bSelected ){
1396
+ this.tree.persistence.clearSelect(tn.data.key);
1397
+ }
1398
+ if ( tn.bExpanded ){
1399
+ this.tree.persistence.clearExpand(tn.data.key);
1400
+ }
1401
+ }
1402
+ tn.removeChildren(true, retainPersistence);
1403
+ if(this.ul){
1404
+ // this.ul.removeChild(tn.li);
1405
+ $("li", $(this.ul)).remove(); // issue 231
1406
+ }
1407
+ // delete tn; JSLint complained
1408
+ }
1409
+ // Set to 'null' which is interpreted as 'not yet loaded' for lazy
1410
+ // nodes
1411
+ this.childList = null;
1412
+ }
1413
+ if( ! isRecursiveCall ) {
1414
+ // this._expand(false);
1415
+ // this.isRead = false;
1416
+ this._isLoading = false;
1417
+ this.render();
1418
+ }
1419
+ },
1420
+
1421
+ setTitle: function(title) {
1422
+ this.fromDict({title: title});
1423
+ },
1424
+
1425
+ reload: function(force) {
1426
+ throw "Use reloadChildren() instead";
1427
+ },
1428
+
1429
+ reloadChildren: function(callback) {
1430
+ // Reload lazy content (expansion state is maintained).
1431
+ if( this.parent === null ){
1432
+ throw "Use tree.reload() instead";
1433
+ }else if( ! this.data.isLazy ){
1434
+ throw "node.reloadChildren() requires lazy nodes.";
1435
+ }
1436
+ // appendAjax triggers 'nodeLoaded' event.
1437
+ // We listen to this, if a callback was passed to reloadChildren
1438
+ if(callback){
1439
+ var self = this;
1440
+ var eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id")
1441
+ + "." + this.data.key;
1442
+ this.tree.$tree.bind(eventType, function(e, node, isOk){
1443
+ self.tree.$tree.unbind(eventType);
1444
+ self.tree.logDebug("loaded %o, %o, %o", e, node, isOk);
1445
+ if(node !== self){
1446
+ throw "got invalid load event";
1447
+ }
1448
+ callback.call(self.tree, node, isOk);
1449
+ });
1450
+ }
1451
+ // The expansion state is maintained
1452
+ this.removeChildren();
1453
+ this._loadContent();
1454
+ // if( this.bExpanded ) {
1455
+ // // Remove children first, to prevent effects being applied
1456
+ // this.removeChildren();
1457
+ // // then force re-expand to trigger lazy loading
1458
+ //// this.expand(false);
1459
+ //// this.expand(true);
1460
+ // this._loadContent();
1461
+ // } else {
1462
+ // this.removeChildren();
1463
+ // this._loadContent();
1464
+ // }
1465
+ },
1466
+
1467
+ /**
1468
+ * Make sure the node with a given key path is available in the tree.
1469
+ */
1470
+ _loadKeyPath: function(keyPath, callback) {
1471
+ var tree = this.tree;
1472
+ tree.logDebug("%s._loadKeyPath(%s)", this, keyPath);
1473
+ if(keyPath === ""){
1474
+ throw "Key path must not be empty";
1475
+ }
1476
+ var segList = keyPath.split(tree.options.keyPathSeparator);
1477
+ if(segList[0] === ""){
1478
+ throw "Key path must be relative (don't start with '/')";
1479
+ }
1480
+ var seg = segList.shift();
1481
+
1482
+ for(var i=0, l=this.childList.length; i < l; i++){
1483
+ var child = this.childList[i];
1484
+ if( child.data.key === seg ){
1485
+ if(segList.length === 0) {
1486
+ // Found the end node
1487
+ callback.call(tree, child, "ok");
1488
+
1489
+ }else if(child.data.isLazy && (child.childList === null || child.childList === undefined)){
1490
+ tree.logDebug("%s._loadKeyPath(%s) -> reloading %s...", this, keyPath, child);
1491
+ var self = this;
1492
+ child.reloadChildren(function(node, isOk){
1493
+ // After loading, look for direct child with that key
1494
+ if(isOk){
1495
+ tree.logDebug("%s._loadKeyPath(%s) -> reloaded %s.", node, keyPath, node);
1496
+ callback.call(tree, child, "loaded");
1497
+ node._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback);
1498
+ }else{
1499
+ tree.logWarning("%s._loadKeyPath(%s) -> reloadChildren() failed.", self, keyPath);
1500
+ callback.call(tree, child, "error");
1501
+ }
1502
+ }); // Note: this line gives a JSLint warning (Don't make functions within a loop)
1503
+ // we can ignore it, since it will only be exectuted once, the the loop is ended
1504
+ // See also http://stackoverflow.com/questions/3037598/how-to-get-around-the-jslint-error-dont-make-functions-within-a-loop
1505
+ } else {
1506
+ callback.call(tree, child, "loaded");
1507
+ // Look for direct child with that key
1508
+ child._loadKeyPath(segList.join(tree.options.keyPathSeparator), callback);
1509
+ }
1510
+ return;
1511
+ }
1512
+ }
1513
+ // Could not find key
1514
+ tree.logWarning("Node not found: " + seg);
1515
+ return;
1516
+ },
1517
+
1518
+ resetLazy: function() {
1519
+ // Discard lazy content.
1520
+ if( this.parent === null ){
1521
+ throw "Use tree.reload() instead";
1522
+ }else if( ! this.data.isLazy ){
1523
+ throw "node.resetLazy() requires lazy nodes.";
1524
+ }
1525
+ this.expand(false);
1526
+ this.removeChildren();
1527
+ },
1528
+
1529
+ _addChildNode: function(dtnode, beforeNode) {
1530
+ /**
1531
+ * Internal function to add one single DynatreeNode as a child.
1532
+ *
1533
+ */
1534
+ var tree = this.tree,
1535
+ opts = tree.options,
1536
+ pers = tree.persistence;
1537
+
1538
+ // tree.logDebug("%s._addChildNode(%o)", this, dtnode);
1539
+
1540
+ // --- Update and fix dtnode attributes if necessary
1541
+ dtnode.parent = this;
1542
+ // if( beforeNode && (beforeNode.parent !== this || beforeNode === dtnode ) )
1543
+ // throw "<beforeNode> must be another child of <this>";
1544
+
1545
+ // --- Add dtnode as a child
1546
+ if ( this.childList === null ) {
1547
+ this.childList = [];
1548
+ } else if( ! beforeNode ) {
1549
+ // Fix 'lastsib'
1550
+ if(this.childList.length > 0) {
1551
+ $(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib);
1552
+ }
1553
+ }
1554
+ if( beforeNode ) {
1555
+ var iBefore = $.inArray(beforeNode, this.childList);
1556
+ if( iBefore < 0 ){
1557
+ throw "<beforeNode> must be a child of <this>";
1558
+ }
1559
+ this.childList.splice(iBefore, 0, dtnode);
1560
+ } else {
1561
+ // Append node
1562
+ this.childList.push(dtnode);
1563
+ }
1564
+
1565
+ // --- Handle persistence
1566
+ // Initial status is read from cookies, if persistence is active and
1567
+ // cookies are already present.
1568
+ // Otherwise the status is read from the data attributes and then persisted.
1569
+ var isInitializing = tree.isInitializing();
1570
+ if( opts.persist && pers.cookiesFound && isInitializing ) {
1571
+ // Init status from cookies
1572
+ // tree.logDebug("init from cookie, pa=%o, dk=%o", pers.activeKey, dtnode.data.key);
1573
+ if( pers.activeKey === dtnode.data.key ){
1574
+ tree.activeNode = dtnode;
1575
+ }
1576
+ if( pers.focusedKey === dtnode.data.key ){
1577
+ tree.focusNode = dtnode;
1578
+ }
1579
+ dtnode.bExpanded = ($.inArray(dtnode.data.key, pers.expandedKeyList) >= 0);
1580
+ dtnode.bSelected = ($.inArray(dtnode.data.key, pers.selectedKeyList) >= 0);
1581
+ // tree.logDebug(" key=%o, bSelected=%o", dtnode.data.key, dtnode.bSelected);
1582
+ } else {
1583
+ // Init status from data (Note: we write the cookies after the init phase)
1584
+ // tree.logDebug("init from data");
1585
+ if( dtnode.data.activate ) {
1586
+ tree.activeNode = dtnode;
1587
+ if( opts.persist ){
1588
+ pers.activeKey = dtnode.data.key;
1589
+ }
1590
+ }
1591
+ if( dtnode.data.focus ) {
1592
+ tree.focusNode = dtnode;
1593
+ if( opts.persist ){
1594
+ pers.focusedKey = dtnode.data.key;
1595
+ }
1596
+ }
1597
+ dtnode.bExpanded = ( dtnode.data.expand === true ); // Collapsed by default
1598
+ if( dtnode.bExpanded && opts.persist ){
1599
+ pers.addExpand(dtnode.data.key);
1600
+ }
1601
+ dtnode.bSelected = ( dtnode.data.select === true ); // Deselected by default
1602
+ /*
1603
+ Doesn't work, cause pers.selectedKeyList may be null
1604
+ if( dtnode.bSelected && opts.selectMode==1
1605
+ && pers.selectedKeyList && pers.selectedKeyList.length>0 ) {
1606
+ tree.logWarning("Ignored multi-selection in single-mode for %o", dtnode);
1607
+ dtnode.bSelected = false; // Fixing bad input data (multi selection for mode:1)
1608
+ }
1609
+ */
1610
+ if( dtnode.bSelected && opts.persist ){
1611
+ pers.addSelect(dtnode.data.key);
1612
+ }
1613
+ }
1614
+
1615
+ // Always expand, if it's below minExpandLevel
1616
+ // tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel());
1617
+ if ( opts.minExpandLevel >= dtnode.getLevel() ) {
1618
+ // tree.logDebug ("Force expand for %o", dtnode);
1619
+ this.bExpanded = true;
1620
+ }
1621
+
1622
+ // In multi-hier mode, update the parents selection state
1623
+ // issue #82: only if not initializing, because the children may not exist yet
1624
+ // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing )
1625
+ // dtnode._fixSelectionState();
1626
+
1627
+ // In multi-hier mode, update the parents selection state
1628
+ if( dtnode.bSelected && opts.selectMode==3 ) {
1629
+ var p = this;
1630
+ while( p ) {
1631
+ if( !p.hasSubSel ){
1632
+ p._setSubSel(true);
1633
+ }
1634
+ p = p.parent;
1635
+ }
1636
+ }
1637
+ // render this node and the new child
1638
+ if ( tree.bEnableUpdate ){
1639
+ this.render();
1640
+ }
1641
+ return dtnode;
1642
+ },
1643
+
1644
+ addChild: function(obj, beforeNode) {
1645
+ /**
1646
+ * Add a node object as child.
1647
+ *
1648
+ * This should be the only place, where a DynaTreeNode is constructed!
1649
+ * (Except for the root node creation in the tree constructor)
1650
+ *
1651
+ * @param obj A JS object (may be recursive) or an array of those.
1652
+ * @param {DynaTreeNode} beforeNode (optional) sibling node.
1653
+ *
1654
+ * Data format: array of node objects, with optional 'children' attributes.
1655
+ * [
1656
+ * { title: "t1", isFolder: true, ... }
1657
+ * { title: "t2", isFolder: true, ...,
1658
+ * children: [
1659
+ * {title: "t2.1", ..},
1660
+ * {..}
1661
+ * ]
1662
+ * }
1663
+ * ]
1664
+ * A simple object is also accepted instead of an array.
1665
+ *
1666
+ */
1667
+ // this.tree.logDebug("%s.addChild(%o, %o)", this, obj, beforeNode);
1668
+ if(typeof(obj) == "string"){
1669
+ throw "Invalid data type for " + obj;
1670
+ }else if( !obj || obj.length === 0 ){ // Passed null or undefined or empty array
1671
+ return;
1672
+ }else if( obj instanceof DynaTreeNode ){
1673
+ return this._addChildNode(obj, beforeNode);
1674
+ }
1675
+
1676
+ if( !obj.length ){ // Passed a single data object
1677
+ obj = [ obj ];
1678
+ }
1679
+ var prevFlag = this.tree.enableUpdate(false);
1680
+
1681
+ var tnFirst = null;
1682
+ for (var i=0, l=obj.length; i<l; i++) {
1683
+ var data = obj[i];
1684
+ var dtnode = this._addChildNode(new DynaTreeNode(this, this.tree, data), beforeNode);
1685
+ if( !tnFirst ){
1686
+ tnFirst = dtnode;
1687
+ }
1688
+ // Add child nodes recursively
1689
+ if( data.children ){
1690
+ dtnode.addChild(data.children, null);
1691
+ }
1692
+ }
1693
+ this.tree.enableUpdate(prevFlag);
1694
+ return tnFirst;
1695
+ },
1696
+
1697
+ append: function(obj) {
1698
+ this.tree.logWarning("node.append() is deprecated (use node.addChild() instead).");
1699
+ return this.addChild(obj, null);
1700
+ },
1701
+
1702
+ appendAjax: function(ajaxOptions) {
1703
+ var self = this;
1704
+ this.removeChildren(false, true);
1705
+ this.setLazyNodeStatus(DTNodeStatus_Loading);
1706
+ // Debug feature: force a delay, to simulate slow loading...
1707
+ if(ajaxOptions.debugLazyDelay){
1708
+ var ms = ajaxOptions.debugLazyDelay;
1709
+ ajaxOptions.debugLazyDelay = 0;
1710
+ this.tree.logInfo("appendAjax: waiting for debugLazyDelay " + ms);
1711
+ setTimeout(function(){self.appendAjax(ajaxOptions);}, ms);
1712
+ return;
1713
+ }
1714
+ // Ajax option inheritance: $.ajaxSetup < $.ui.dynatree.prototype.options.ajaxDefaults < tree.options.ajaxDefaults < ajaxOptions
1715
+ var orgSuccess = ajaxOptions.success,
1716
+ orgError = ajaxOptions.error,
1717
+ eventType = "nodeLoaded.dynatree." + this.tree.$tree.attr("id") + "." + this.data.key;
1718
+ var options = $.extend({}, this.tree.options.ajaxDefaults, ajaxOptions, {
1719
+ success: function(data, textStatus, jqXHR){
1720
+ // <this> is the request options
1721
+ // self.tree.logDebug("appendAjax().success");
1722
+ var prevPhase = self.tree.phase;
1723
+ self.tree.phase = "init";
1724
+ // postProcess is similar to the standard dataFilter hook,
1725
+ // but it is also called for JSONP
1726
+ if( options.postProcess ){
1727
+ data = options.postProcess.call(this, data, this.dataType);
1728
+ }
1729
+ // Process ASPX WebMethod JSON object inside "d" property
1730
+ // http://code.google.com/p/dynatree/issues/detail?id=202
1731
+ else if (data && data.hasOwnProperty("d")) {
1732
+ data = (typeof data.d) == "string" ? $.parseJSON(data.d) : response.d;
1733
+ }
1734
+ if(!$.isArray(data) || data.length !== 0){
1735
+ self.addChild(data, null);
1736
+ }
1737
+ self.tree.phase = "postInit";
1738
+ if( orgSuccess ){
1739
+ orgSuccess.call(options, self, data, textStatus);
1740
+ }
1741
+ self.tree.logDebug("trigger " + eventType);
1742
+ self.tree.$tree.trigger(eventType, [self, true]);
1743
+ self.tree.phase = prevPhase;
1744
+ // This should be the last command, so node._isLoading is true
1745
+ // while the callbacks run
1746
+ self.setLazyNodeStatus(DTNodeStatus_Ok);
1747
+ if($.isArray(data) && data.length === 0){
1748
+ // Set to [] which is interpreted as 'no children' for lazy
1749
+ // nodes
1750
+ self.childList = [];
1751
+ self.render();
1752
+ }
1753
+ },
1754
+ error: function(jqXHR, textStatus, errorThrown){
1755
+ // <this> is the request options
1756
+ self.tree.logWarning("appendAjax failed:", textStatus, ":\n", jqXHR, "\n", errorThrown);
1757
+ if( orgError ){
1758
+ orgError.call(options, self, jqXHR, textStatus, errorThrown);
1759
+ }
1760
+ self.tree.$tree.trigger(eventType, [self, false]);
1761
+ self.setLazyNodeStatus(DTNodeStatus_Error, {info: textStatus, tooltip: "" + errorThrown});
1762
+ }
1763
+ });
1764
+ $.ajax(options);
1765
+ },
1766
+
1767
+ move: function(targetNode, mode) {
1768
+ /**Move this node to targetNode.
1769
+ * mode 'child': append this node as last child of targetNode.
1770
+ * This is the default. To be compatble with the D'n'd
1771
+ * hitMode, we also accept 'over'.
1772
+ * mode 'before': add this node as sibling before targetNode.
1773
+ * mode 'after': add this node as sibling after targetNode.
1774
+ */
1775
+ var pos;
1776
+ if(this === targetNode){
1777
+ return;
1778
+ }
1779
+ if( !this.parent ){
1780
+ throw "Cannot move system root";
1781
+ }
1782
+ if(mode === undefined || mode == "over"){
1783
+ mode = "child";
1784
+ }
1785
+ var prevParent = this.parent;
1786
+ var targetParent = (mode === "child") ? targetNode : targetNode.parent;
1787
+ if( targetParent.isDescendantOf(this) ){
1788
+ throw "Cannot move a node to it's own descendant";
1789
+ }
1790
+ // Unlink this node from current parent
1791
+ if( this.parent.childList.length == 1 ) {
1792
+ this.parent.childList = null;
1793
+ this.parent.bExpanded = false;
1794
+ } else {
1795
+ pos = $.inArray(this, this.parent.childList);
1796
+ if( pos < 0 ){
1797
+ throw "Internal error";
1798
+ }
1799
+ this.parent.childList.splice(pos, 1);
1800
+ }
1801
+ // Remove from source DOM parent
1802
+ this.parent.ul.removeChild(this.li);
1803
+
1804
+ // Insert this node to target parent's child list
1805
+ this.parent = targetParent;
1806
+ if( targetParent.hasChildren() ) {
1807
+ switch(mode) {
1808
+ case "child":
1809
+ // Append to existing target children
1810
+ targetParent.childList.push(this);
1811
+ break;
1812
+ case "before":
1813
+ // Insert this node before target node
1814
+ pos = $.inArray(targetNode, targetParent.childList);
1815
+ if( pos < 0 ){
1816
+ throw "Internal error";
1817
+ }
1818
+ targetParent.childList.splice(pos, 0, this);
1819
+ break;
1820
+ case "after":
1821
+ // Insert this node after target node
1822
+ pos = $.inArray(targetNode, targetParent.childList);
1823
+ if( pos < 0 ){
1824
+ throw "Internal error";
1825
+ }
1826
+ targetParent.childList.splice(pos+1, 0, this);
1827
+ break;
1828
+ default:
1829
+ throw "Invalid mode " + mode;
1830
+ }
1831
+ } else {
1832
+ targetParent.childList = [ this ];
1833
+ }
1834
+ // Parent has no <ul> tag yet:
1835
+ if( !targetParent.ul ) {
1836
+ // This is the parent's first child: create UL tag
1837
+ // (Hidden, because it will be
1838
+ targetParent.ul = document.createElement("ul");
1839
+ targetParent.ul.style.display = "none";
1840
+ targetParent.li.appendChild(targetParent.ul);
1841
+ }
1842
+ // Add to target DOM parent
1843
+ targetParent.ul.appendChild(this.li);
1844
+
1845
+ if( this.tree !== targetNode.tree ) {
1846
+ // Fix node.tree for all source nodes
1847
+ this.visit(function(node){
1848
+ node.tree = targetNode.tree;
1849
+ }, null, true);
1850
+ throw "Not yet implemented.";
1851
+ }
1852
+ // TODO: fix selection state
1853
+ // TODO: fix active state
1854
+ if( !prevParent.isDescendantOf(targetParent)) {
1855
+ prevParent.render();
1856
+ }
1857
+ if( !targetParent.isDescendantOf(prevParent) ) {
1858
+ targetParent.render();
1859
+ }
1860
+ // this.tree.redraw();
1861
+ /*
1862
+ var tree = this.tree;
1863
+ var opts = tree.options;
1864
+ var pers = tree.persistence;
1865
+
1866
+
1867
+ // Always expand, if it's below minExpandLevel
1868
+ // tree.logDebug ("%s._addChildNode(%o), l=%o", this, dtnode, dtnode.getLevel());
1869
+ if ( opts.minExpandLevel >= dtnode.getLevel() ) {
1870
+ // tree.logDebug ("Force expand for %o", dtnode);
1871
+ this.bExpanded = true;
1872
+ }
1873
+
1874
+ // In multi-hier mode, update the parents selection state
1875
+ // issue #82: only if not initializing, because the children may not exist yet
1876
+ // if( !dtnode.data.isStatusNode && opts.selectMode==3 && !isInitializing )
1877
+ // dtnode._fixSelectionState();
1878
+
1879
+ // In multi-hier mode, update the parents selection state
1880
+ if( dtnode.bSelected && opts.selectMode==3 ) {
1881
+ var p = this;
1882
+ while( p ) {
1883
+ if( !p.hasSubSel )
1884
+ p._setSubSel(true);
1885
+ p = p.parent;
1886
+ }
1887
+ }
1888
+ // render this node and the new child
1889
+ if ( tree.bEnableUpdate )
1890
+ this.render();
1891
+
1892
+ return dtnode;
1893
+
1894
+ */
1895
+ },
1896
+
1897
+ // --- end of class
1898
+ lastentry: undefined
1899
+ };
1900
+
1901
+ /*************************************************************************
1902
+ * class DynaTreeStatus
1903
+ */
1904
+
1905
+ var DynaTreeStatus = Class.create();
1906
+
1907
+
1908
+ DynaTreeStatus._getTreePersistData = function(cookieId, cookieOpts) {
1909
+ // Static member: Return persistence information from cookies
1910
+ var ts = new DynaTreeStatus(cookieId, cookieOpts);
1911
+ ts.read();
1912
+ return ts.toDict();
1913
+ };
1914
+ // Make available in global scope
1915
+ getDynaTreePersistData = DynaTreeStatus._getTreePersistData; // TODO: deprecated
1916
+
1917
+
1918
+ DynaTreeStatus.prototype = {
1919
+ // Constructor
1920
+ initialize: function(cookieId, cookieOpts) {
1921
+ // this._log("DynaTreeStatus: initialize");
1922
+ if( cookieId === undefined ){
1923
+ cookieId = $.ui.dynatree.prototype.options.cookieId;
1924
+ }
1925
+ cookieOpts = $.extend({}, $.ui.dynatree.prototype.options.cookie, cookieOpts);
1926
+
1927
+ this.cookieId = cookieId;
1928
+ this.cookieOpts = cookieOpts;
1929
+ this.cookiesFound = undefined;
1930
+ this.activeKey = null;
1931
+ this.focusedKey = null;
1932
+ this.expandedKeyList = null;
1933
+ this.selectedKeyList = null;
1934
+ },
1935
+ // member functions
1936
+ _log: function(msg) {
1937
+ // this.logDebug("_changeNodeList(%o): nodeList:%o, idx:%o", mode, nodeList, idx);
1938
+ Array.prototype.unshift.apply(arguments, ["debug"]);
1939
+ _log.apply(this, arguments);
1940
+ },
1941
+ read: function() {
1942
+ // this._log("DynaTreeStatus: read");
1943
+ // Read or init cookies.
1944
+ this.cookiesFound = false;
1945
+
1946
+ var cookie = $.cookie(this.cookieId + "-active");
1947
+ this.activeKey = ( cookie === null ) ? "" : cookie;
1948
+ if( cookie !== null ){
1949
+ this.cookiesFound = true;
1950
+ }
1951
+ cookie = $.cookie(this.cookieId + "-focus");
1952
+ this.focusedKey = ( cookie === null ) ? "" : cookie;
1953
+ if( cookie !== null ){
1954
+ this.cookiesFound = true;
1955
+ }
1956
+ cookie = $.cookie(this.cookieId + "-expand");
1957
+ this.expandedKeyList = ( cookie === null ) ? [] : cookie.split(",");
1958
+ if( cookie !== null ){
1959
+ this.cookiesFound = true;
1960
+ }
1961
+ cookie = $.cookie(this.cookieId + "-select");
1962
+ this.selectedKeyList = ( cookie === null ) ? [] : cookie.split(",");
1963
+ if( cookie !== null ){
1964
+ this.cookiesFound = true;
1965
+ }
1966
+ },
1967
+ write: function() {
1968
+ // this._log("DynaTreeStatus: write");
1969
+ $.cookie(this.cookieId + "-active", ( this.activeKey === null ) ? "" : this.activeKey, this.cookieOpts);
1970
+ $.cookie(this.cookieId + "-focus", ( this.focusedKey === null ) ? "" : this.focusedKey, this.cookieOpts);
1971
+ $.cookie(this.cookieId + "-expand", ( this.expandedKeyList === null ) ? "" : this.expandedKeyList.join(","), this.cookieOpts);
1972
+ $.cookie(this.cookieId + "-select", ( this.selectedKeyList === null ) ? "" : this.selectedKeyList.join(","), this.cookieOpts);
1973
+ },
1974
+ addExpand: function(key) {
1975
+ // this._log("addExpand(%o)", key);
1976
+ if( $.inArray(key, this.expandedKeyList) < 0 ) {
1977
+ this.expandedKeyList.push(key);
1978
+ $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
1979
+ }
1980
+ },
1981
+ clearExpand: function(key) {
1982
+ // this._log("clearExpand(%o)", key);
1983
+ var idx = $.inArray(key, this.expandedKeyList);
1984
+ if( idx >= 0 ) {
1985
+ this.expandedKeyList.splice(idx, 1);
1986
+ $.cookie(this.cookieId + "-expand", this.expandedKeyList.join(","), this.cookieOpts);
1987
+ }
1988
+ },
1989
+ addSelect: function(key) {
1990
+ // this._log("addSelect(%o)", key);
1991
+ if( $.inArray(key, this.selectedKeyList) < 0 ) {
1992
+ this.selectedKeyList.push(key);
1993
+ $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
1994
+ }
1995
+ },
1996
+ clearSelect: function(key) {
1997
+ // this._log("clearSelect(%o)", key);
1998
+ var idx = $.inArray(key, this.selectedKeyList);
1999
+ if( idx >= 0 ) {
2000
+ this.selectedKeyList.splice(idx, 1);
2001
+ $.cookie(this.cookieId + "-select", this.selectedKeyList.join(","), this.cookieOpts);
2002
+ }
2003
+ },
2004
+ isReloading: function() {
2005
+ return this.cookiesFound === true;
2006
+ },
2007
+ toDict: function() {
2008
+ return {
2009
+ cookiesFound: this.cookiesFound,
2010
+ activeKey: this.activeKey,
2011
+ focusedKey: this.activeKey,
2012
+ expandedKeyList: this.expandedKeyList,
2013
+ selectedKeyList: this.selectedKeyList
2014
+ };
2015
+ },
2016
+ // --- end of class
2017
+ lastentry: undefined
2018
+ };
2019
+
2020
+
2021
+ /*************************************************************************
2022
+ * class DynaTree
2023
+ */
2024
+
2025
+ var DynaTree = Class.create();
2026
+
2027
+ // --- Static members ----------------------------------------------------------
2028
+
2029
+ DynaTree.version = "$Version: 1.2.1_rc3$";
2030
+
2031
+ /*
2032
+ DynaTree._initTree = function() {
2033
+ };
2034
+
2035
+ DynaTree._bind = function() {
2036
+ };
2037
+ */
2038
+ //--- Class members ------------------------------------------------------------
2039
+
2040
+ DynaTree.prototype = {
2041
+ // Constructor
2042
+ // initialize: function(divContainer, options) {
2043
+ initialize: function($widget) {
2044
+ // instance members
2045
+ this.phase = "init";
2046
+ this.$widget = $widget;
2047
+ this.options = $widget.options;
2048
+ this.$tree = $widget.element;
2049
+ this.timer = null;
2050
+ // find container element
2051
+ this.divTree = this.$tree.get(0);
2052
+
2053
+ // var parentPos = $(this.divTree).parent().offset();
2054
+ // this.parentTop = parentPos.top;
2055
+ // this.parentLeft = parentPos.left;
2056
+
2057
+ _initDragAndDrop(this);
2058
+ },
2059
+
2060
+ // member functions
2061
+
2062
+ _load: function(callback) {
2063
+ var $widget = this.$widget;
2064
+ var opts = this.options,
2065
+ self = this;
2066
+ this.bEnableUpdate = true;
2067
+ this._nodeCount = 1;
2068
+ this.activeNode = null;
2069
+ this.focusNode = null;
2070
+
2071
+ // Some deprecation warnings to help with migration
2072
+ if( opts.rootVisible !== undefined ){
2073
+ this.logWarning("Option 'rootVisible' is no longer supported.");
2074
+ }
2075
+ if( opts.minExpandLevel < 1 ) {
2076
+ this.logWarning("Option 'minExpandLevel' must be >= 1.");
2077
+ opts.minExpandLevel = 1;
2078
+ }
2079
+ // _log("warn", "jQuery.support.boxModel " + jQuery.support.boxModel);
2080
+
2081
+ // If a 'options.classNames' dictionary was passed, still use defaults
2082
+ // for undefined classes:
2083
+ if( opts.classNames !== $.ui.dynatree.prototype.options.classNames ) {
2084
+ opts.classNames = $.extend({}, $.ui.dynatree.prototype.options.classNames, opts.classNames);
2085
+ }
2086
+ if( opts.ajaxDefaults !== $.ui.dynatree.prototype.options.ajaxDefaults ) {
2087
+ opts.ajaxDefaults = $.extend({}, $.ui.dynatree.prototype.options.ajaxDefaults, opts.ajaxDefaults);
2088
+ }
2089
+ if( opts.dnd !== $.ui.dynatree.prototype.options.dnd ) {
2090
+ opts.dnd = $.extend({}, $.ui.dynatree.prototype.options.dnd, opts.dnd);
2091
+ }
2092
+ // Guess skin path, if not specified
2093
+ if(!opts.imagePath) {
2094
+ $("script").each( function () {
2095
+ var _rexDtLibName = /.*dynatree[^\/]*\.js$/i;
2096
+ if( this.src.search(_rexDtLibName) >= 0 ) {
2097
+ if( this.src.indexOf("/")>=0 ){ // issue #47
2098
+ opts.imagePath = this.src.slice(0, this.src.lastIndexOf("/")) + "/skin/";
2099
+ }else{
2100
+ opts.imagePath = "skin/";
2101
+ }
2102
+ self.logDebug("Guessing imagePath from '%s': '%s'", this.src, opts.imagePath);
2103
+ return false; // first match
2104
+ }
2105
+ });
2106
+ }
2107
+
2108
+ this.persistence = new DynaTreeStatus(opts.cookieId, opts.cookie);
2109
+ if( opts.persist ) {
2110
+ if( !$.cookie ){
2111
+ _log("warn", "Please include jquery.cookie.js to use persistence.");
2112
+ }
2113
+ this.persistence.read();
2114
+ }
2115
+ this.logDebug("DynaTree.persistence: %o", this.persistence.toDict());
2116
+
2117
+ // Cached tag strings
2118
+ this.cache = {
2119
+ tagEmpty: "<span class='" + opts.classNames.empty + "'></span>",
2120
+ tagVline: "<span class='" + opts.classNames.vline + "'></span>",
2121
+ tagExpander: "<span class='" + opts.classNames.expander + "'></span>",
2122
+ tagConnector: "<span class='" + opts.classNames.connector + "'></span>",
2123
+ tagNodeIcon: "<span class='" + opts.classNames.nodeIcon + "'></span>",
2124
+ tagCheckbox: "<span class='" + opts.classNames.checkbox + "'></span>",
2125
+ lastentry: undefined
2126
+ };
2127
+
2128
+ // Clear container, in case it contained some 'waiting' or 'error' text
2129
+ // for clients that don't support JS.
2130
+ // We don't do this however, if we try to load from an embedded UL element.
2131
+ if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId ){
2132
+ $(this.divTree).empty();
2133
+ }
2134
+ var $ulInitialize = this.$tree.find(">ul:first").hide();
2135
+
2136
+ // Create the root element
2137
+ this.tnRoot = new DynaTreeNode(null, this, {});
2138
+ this.tnRoot.bExpanded = true;
2139
+ this.tnRoot.render();
2140
+ this.divTree.appendChild(this.tnRoot.ul);
2141
+
2142
+ var root = this.tnRoot;
2143
+ var isReloading = ( opts.persist && this.persistence.isReloading() );
2144
+ var isLazy = false;
2145
+ var prevFlag = this.enableUpdate(false);
2146
+
2147
+ this.logDebug("Dynatree._load(): read tree structure...");
2148
+
2149
+ // Init tree structure
2150
+ if( opts.children ) {
2151
+ // Read structure from node array
2152
+ root.addChild(opts.children);
2153
+
2154
+ } else if( opts.initAjax && opts.initAjax.url ) {
2155
+ // Init tree from AJAX request
2156
+ isLazy = true;
2157
+ root.data.isLazy = true;
2158
+ this._reloadAjax(callback);
2159
+
2160
+ } else if( opts.initId ) {
2161
+ // Init tree from another UL element
2162
+ this._createFromTag(root, $("#"+opts.initId));
2163
+
2164
+ } else {
2165
+ // Init tree from the first UL element inside the container <div>
2166
+ // var $ul = this.$tree.find(">ul:first").hide();
2167
+ this._createFromTag(root, $ulInitialize);
2168
+ $ulInitialize.remove();
2169
+ }
2170
+
2171
+ this._checkConsistency();
2172
+ // Fix part-sel flags
2173
+ if(!isLazy && opts.selectMode == 3){
2174
+ root._updatePartSelectionState();
2175
+ }
2176
+ // Render html markup
2177
+ this.logDebug("Dynatree._load(): render nodes...");
2178
+ this.enableUpdate(prevFlag);
2179
+
2180
+ // bind event handlers
2181
+ this.logDebug("Dynatree._load(): bind events...");
2182
+ this.$widget.bind();
2183
+
2184
+ // --- Post-load processing
2185
+ this.logDebug("Dynatree._load(): postInit...");
2186
+ this.phase = "postInit";
2187
+
2188
+ // In persist mode, make sure that cookies are written, even if they are empty
2189
+ if( opts.persist ) {
2190
+ this.persistence.write();
2191
+ }
2192
+ // Set focus, if possible (this will also fire an event and write a cookie)
2193
+ if( this.focusNode && this.focusNode.isVisible() ) {
2194
+ this.logDebug("Focus on init: %o", this.focusNode);
2195
+ this.focusNode.focus();
2196
+ }
2197
+ if( !isLazy ) {
2198
+ if( opts.onPostInit ) {
2199
+ opts.onPostInit.call(this, isReloading, false);
2200
+ }
2201
+ if( callback ){
2202
+ callback.call(this, "ok");
2203
+ }
2204
+ }
2205
+ this.phase = "idle";
2206
+ },
2207
+
2208
+ _reloadAjax: function(callback) {
2209
+ // Reload
2210
+ var opts = this.options;
2211
+ if( ! opts.initAjax || ! opts.initAjax.url ){
2212
+ throw "tree.reload() requires 'initAjax' mode.";
2213
+ }
2214
+ var pers = this.persistence;
2215
+ var ajaxOpts = $.extend({}, opts.initAjax);
2216
+ // Append cookie info to the request
2217
+ // this.logDebug("reloadAjax: key=%o, an.key:%o", pers.activeKey, this.activeNode?this.activeNode.data.key:"?");
2218
+ if( ajaxOpts.addActiveKey ){
2219
+ ajaxOpts.data.activeKey = pers.activeKey;
2220
+ }
2221
+ if( ajaxOpts.addFocusedKey ){
2222
+ ajaxOpts.data.focusedKey = pers.focusedKey;
2223
+ }
2224
+ if( ajaxOpts.addExpandedKeyList ){
2225
+ ajaxOpts.data.expandedKeyList = pers.expandedKeyList.join(",");
2226
+ }
2227
+ if( ajaxOpts.addSelectedKeyList ){
2228
+ ajaxOpts.data.selectedKeyList = pers.selectedKeyList.join(",");
2229
+ }
2230
+ // Set up onPostInit callback to be called when Ajax returns
2231
+ if( ajaxOpts.success ){
2232
+ this.logWarning("initAjax: success callback is ignored; use onPostInit instead.");
2233
+ }
2234
+ if( ajaxOpts.error ){
2235
+ this.logWarning("initAjax: error callback is ignored; use onPostInit instead.");
2236
+ }
2237
+ var isReloading = pers.isReloading();
2238
+ ajaxOpts.success = function(dtnode, data, textStatus) {
2239
+ if(opts.selectMode == 3){
2240
+ dtnode.tree.tnRoot._updatePartSelectionState();
2241
+ }
2242
+ if(opts.onPostInit){
2243
+ opts.onPostInit.call(dtnode.tree, isReloading, false);
2244
+ }
2245
+ if(callback){
2246
+ callback.call(dtnode.tree, "ok");
2247
+ }
2248
+ };
2249
+ ajaxOpts.error = function(dtnode, XMLHttpRequest, textStatus, errorThrown) {
2250
+ if(opts.onPostInit){
2251
+ opts.onPostInit.call(dtnode.tree, isReloading, true, XMLHttpRequest, textStatus, errorThrown);
2252
+ }
2253
+ if(callback){
2254
+ callback.call(dtnode.tree, "error", XMLHttpRequest, textStatus, errorThrown);
2255
+ }
2256
+ };
2257
+ // }
2258
+ this.logDebug("Dynatree._init(): send Ajax request...");
2259
+ this.tnRoot.appendAjax(ajaxOpts);
2260
+ },
2261
+
2262
+ toString: function() {
2263
+ // return "DynaTree '" + this.options.title + "'";
2264
+ return "Dynatree '" + this.$tree.attr("id") + "'";
2265
+ },
2266
+
2267
+ toDict: function() {
2268
+ return this.tnRoot.toDict(true);
2269
+ },
2270
+
2271
+ serializeArray: function(stopOnParents) {
2272
+ // Return a JavaScript array of objects, ready to be encoded as a JSON
2273
+ // string for selected nodes
2274
+ var nodeList = this.getSelectedNodes(stopOnParents),
2275
+ name = this.$tree.attr("name") || this.$tree.attr("id"),
2276
+ arr = [];
2277
+ for(var i=0, l=nodeList.length; i<l; i++){
2278
+ arr.push({name: name, value: nodeList[i].data.key});
2279
+ }
2280
+ return arr;
2281
+ },
2282
+
2283
+ getPersistData: function() {
2284
+ return this.persistence.toDict();
2285
+ },
2286
+
2287
+ logDebug: function(msg) {
2288
+ if( this.options.debugLevel >= 2 ) {
2289
+ Array.prototype.unshift.apply(arguments, ["debug"]);
2290
+ _log.apply(this, arguments);
2291
+ }
2292
+ },
2293
+
2294
+ logInfo: function(msg) {
2295
+ if( this.options.debugLevel >= 1 ) {
2296
+ Array.prototype.unshift.apply(arguments, ["info"]);
2297
+ _log.apply(this, arguments);
2298
+ }
2299
+ },
2300
+
2301
+ logWarning: function(msg) {
2302
+ Array.prototype.unshift.apply(arguments, ["warn"]);
2303
+ _log.apply(this, arguments);
2304
+ },
2305
+
2306
+ isInitializing: function() {
2307
+ return ( this.phase=="init" || this.phase=="postInit" );
2308
+ },
2309
+ isReloading: function() {
2310
+ return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
2311
+ },
2312
+ isUserEvent: function() {
2313
+ return ( this.phase=="userEvent" );
2314
+ },
2315
+
2316
+ redraw: function() {
2317
+ // this.logDebug("dynatree.redraw()...");
2318
+ this.tnRoot.render(false, false);
2319
+ // this.logDebug("dynatree.redraw() done.");
2320
+ },
2321
+ renderInvisibleNodes: function() {
2322
+ this.tnRoot.render(false, true);
2323
+ },
2324
+ reload: function(callback) {
2325
+ this._load(callback);
2326
+ },
2327
+
2328
+ getRoot: function() {
2329
+ return this.tnRoot;
2330
+ },
2331
+
2332
+ enable: function() {
2333
+ this.$widget.enable();
2334
+ },
2335
+
2336
+ disable: function() {
2337
+ this.$widget.disable();
2338
+ },
2339
+
2340
+ getNodeByKey: function(key) {
2341
+ // Search the DOM by element ID (assuming this is faster than traversing all nodes).
2342
+ // $("#...") has problems, if the key contains '.', so we use getElementById()
2343
+ var el = document.getElementById(this.options.idPrefix + key);
2344
+ if( el ){
2345
+ return el.dtnode ? el.dtnode : null;
2346
+ }
2347
+ // Not found in the DOM, but still may be in an unrendered part of tree
2348
+ var match = null;
2349
+ this.visit(function(node){
2350
+ // window.console.log("%s", node);
2351
+ if(node.data.key == key) {
2352
+ match = node;
2353
+ return false;
2354
+ }
2355
+ }, true);
2356
+ return match;
2357
+ },
2358
+
2359
+ getActiveNode: function() {
2360
+ return this.activeNode;
2361
+ },
2362
+
2363
+ reactivate: function(setFocus) {
2364
+ // Re-fire onQueryActivate and onActivate events.
2365
+ var node = this.activeNode;
2366
+ // this.logDebug("reactivate %o", node);
2367
+ if( node ) {
2368
+ this.activeNode = null; // Force re-activating
2369
+ node.activate();
2370
+ if( setFocus ){
2371
+ node.focus();
2372
+ }
2373
+ }
2374
+ },
2375
+
2376
+ getSelectedNodes: function(stopOnParents) {
2377
+ var nodeList = [];
2378
+ this.tnRoot.visit(function(node){
2379
+ if( node.bSelected ) {
2380
+ nodeList.push(node);
2381
+ if( stopOnParents === true ){
2382
+ return "skip"; // stop processing this branch
2383
+ }
2384
+ }
2385
+ });
2386
+ return nodeList;
2387
+ },
2388
+
2389
+ activateKey: function(key) {
2390
+ var dtnode = (key === null) ? null : this.getNodeByKey(key);
2391
+ if( !dtnode ) {
2392
+ if( this.activeNode ){
2393
+ this.activeNode.deactivate();
2394
+ }
2395
+ this.activeNode = null;
2396
+ return null;
2397
+ }
2398
+ dtnode.focus();
2399
+ dtnode.activate();
2400
+ return dtnode;
2401
+ },
2402
+
2403
+ loadKeyPath: function(keyPath, callback) {
2404
+ var segList = keyPath.split(this.options.keyPathSeparator);
2405
+ // Remove leading '/'
2406
+ if(segList[0] === ""){
2407
+ segList.shift();
2408
+ }
2409
+ // Remove leading system root key
2410
+ if(segList[0] == this.tnRoot.data.key){
2411
+ this.logDebug("Removed leading root key.");
2412
+ segList.shift();
2413
+ }
2414
+ keyPath = segList.join(this.options.keyPathSeparator);
2415
+ return this.tnRoot._loadKeyPath(keyPath, callback);
2416
+ },
2417
+
2418
+ selectKey: function(key, select) {
2419
+ var dtnode = this.getNodeByKey(key);
2420
+ if( !dtnode ){
2421
+ return null;
2422
+ }
2423
+ dtnode.select(select);
2424
+ return dtnode;
2425
+ },
2426
+
2427
+ enableUpdate: function(bEnable) {
2428
+ if ( this.bEnableUpdate==bEnable ){
2429
+ return bEnable;
2430
+ }
2431
+ this.bEnableUpdate = bEnable;
2432
+ if ( bEnable ){
2433
+ this.redraw();
2434
+ }
2435
+ return !bEnable; // return previous value
2436
+ },
2437
+
2438
+ count: function() {
2439
+ return this.tnRoot.countChildren();
2440
+ },
2441
+
2442
+ visit: function(fn, includeRoot) {
2443
+ return this.tnRoot.visit(fn, includeRoot);
2444
+ },
2445
+
2446
+ _createFromTag: function(parentTreeNode, $ulParent) {
2447
+ // Convert a <UL>...</UL> list into children of the parent tree node.
2448
+ var self = this;
2449
+ /*
2450
+ TODO: better?
2451
+ this.$lis = $("li:has(a[href])", this.element);
2452
+ this.$tabs = this.$lis.map(function() { return $("a", this)[0]; });
2453
+ */
2454
+ $ulParent.find(">li").each(function() {
2455
+ var $li = $(this),
2456
+ $liSpan = $li.find(">span:first"),
2457
+ $liA = $li.find(">a:first"),
2458
+ title,
2459
+ href = null,
2460
+ target = null,
2461
+ tooltip;
2462
+ if( $liSpan.length ) {
2463
+ // If a <li><span> tag is specified, use it literally.
2464
+ title = $liSpan.html();
2465
+ } else if( $liA.length ) {
2466
+ title = $liA.html();
2467
+ href = $liA.attr("href");
2468
+ target = $liA.attr("target");
2469
+ tooltip = $liA.attr("title");
2470
+ } else {
2471
+ // If only a <li> tag is specified, use the trimmed string up to
2472
+ // the next child <ul> tag.
2473
+ title = $li.html();
2474
+ var iPos = title.search(/<ul/i);
2475
+ if( iPos >= 0 ){
2476
+ title = $.trim(title.substring(0, iPos));
2477
+ }else{
2478
+ title = $.trim(title);
2479
+ }
2480
+ // self.logDebug("%o", title);
2481
+ }
2482
+ // Parse node options from ID, title and class attributes
2483
+ var data = {
2484
+ title: title,
2485
+ tooltip: tooltip,
2486
+ isFolder: $li.hasClass("folder"),
2487
+ isLazy: $li.hasClass("lazy"),
2488
+ expand: $li.hasClass("expanded"),
2489
+ select: $li.hasClass("selected"),
2490
+ activate: $li.hasClass("active"),
2491
+ focus: $li.hasClass("focused"),
2492
+ noLink: $li.hasClass("noLink")
2493
+ };
2494
+ if( href ){
2495
+ data.href = href;
2496
+ data.target = target;
2497
+ }
2498
+ if( $li.attr("title") ){
2499
+ data.tooltip = $li.attr("title"); // overrides <a title='...'>
2500
+ }
2501
+ if( $li.attr("id") ){
2502
+ data.key = $li.attr("id");
2503
+ }
2504
+ // If a data attribute is present, evaluate as a JavaScript object
2505
+ if( $li.attr("data") ) {
2506
+ var dataAttr = $.trim($li.attr("data"));
2507
+ if( dataAttr ) {
2508
+ if( dataAttr.charAt(0) != "{" ){
2509
+ dataAttr = "{" + dataAttr + "}";
2510
+ }
2511
+ try {
2512
+ $.extend(data, eval("(" + dataAttr + ")"));
2513
+ } catch(e) {
2514
+ throw ("Error parsing node data: " + e + "\ndata:\n'" + dataAttr + "'");
2515
+ }
2516
+ }
2517
+ }
2518
+ var childNode = parentTreeNode.addChild(data);
2519
+ // Recursive reading of child nodes, if LI tag contains an UL tag
2520
+ var $ul = $li.find(">ul:first");
2521
+ if( $ul.length ) {
2522
+ self._createFromTag(childNode, $ul); // must use 'self', because 'this' is the each() context
2523
+ }
2524
+ });
2525
+ },
2526
+
2527
+ _checkConsistency: function() {
2528
+ // this.logDebug("tree._checkConsistency() NOT IMPLEMENTED - %o", this);
2529
+ },
2530
+
2531
+ _setDndStatus: function(sourceNode, targetNode, helper, hitMode, accept) {
2532
+ // hitMode: 'after', 'before', 'over', 'out', 'start', 'stop'
2533
+ var $source = sourceNode ? $(sourceNode.span) : null,
2534
+ $target = $(targetNode.span);
2535
+ if( !this.$dndMarker ) {
2536
+ this.$dndMarker = $("<div id='dynatree-drop-marker'></div>")
2537
+ .hide()
2538
+ .prependTo($(this.divTree).parent());
2539
+ // .prependTo("body");
2540
+ // logMsg("Creating marker: %o", this.$dndMarker);
2541
+ }
2542
+ /*
2543
+ if(hitMode === "start"){
2544
+ }
2545
+ if(hitMode === "stop"){
2546
+ // sourceNode.removeClass("dynatree-drop-target");
2547
+ }
2548
+ */
2549
+ // this.$dndMarker.attr("class", hitMode);
2550
+ if(hitMode === "after" || hitMode === "before" || hitMode === "over"){
2551
+ // $source && $source.addClass("dynatree-drag-source");
2552
+ var pos = $target.offset();
2553
+
2554
+ // $target.addClass("dynatree-drop-target");
2555
+
2556
+ switch(hitMode){
2557
+ case "before":
2558
+ this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-over");
2559
+ this.$dndMarker.addClass("dynatree-drop-before");
2560
+ pos.top -= 8;
2561
+ break;
2562
+ case "after":
2563
+ this.$dndMarker.removeClass("dynatree-drop-before dynatree-drop-over");
2564
+ this.$dndMarker.addClass("dynatree-drop-after");
2565
+ pos.top += 8;
2566
+ break;
2567
+ default:
2568
+ this.$dndMarker.removeClass("dynatree-drop-after dynatree-drop-before");
2569
+ this.$dndMarker.addClass("dynatree-drop-over");
2570
+ $target.addClass("dynatree-drop-target");
2571
+ pos.left += 8;
2572
+ }
2573
+ // logMsg("Creating marker: %o", this.$dndMarker);
2574
+ // logMsg(" $target.offset=%o", $target);
2575
+ // logMsg(" pos/$target.offset=%o", pos);
2576
+ // logMsg(" $target.position=%o", $target.position());
2577
+ // logMsg(" $target.offsetParent=%o, ot:%o", $target.offsetParent(), $target.offsetParent().offset());
2578
+ // logMsg(" $(this.divTree).offset=%o", $(this.divTree).offset());
2579
+ // logMsg(" $(this.divTree).parent=%o", $(this.divTree).parent());
2580
+ // var pos = $target.offset();
2581
+ // var parentPos = $target.offsetParent().offset();
2582
+ // var bodyPos = $target.offsetParent().offset();
2583
+
2584
+ this.$dndMarker //.offset({left: pos.left, top: pos.top})
2585
+ .css({
2586
+ "left": pos.left,
2587
+ "top": pos.top,
2588
+ "z-index": 1000
2589
+ })
2590
+ .show();
2591
+ // helper.addClass("dynatree-drop-hover");
2592
+ } else {
2593
+ // $source && $source.removeClass("dynatree-drag-source");
2594
+ $target.removeClass("dynatree-drop-target");
2595
+ this.$dndMarker.hide();
2596
+ // helper.removeClass("dynatree-drop-hover");
2597
+ }
2598
+ if(hitMode === "after"){
2599
+ $target.addClass("dynatree-drop-after");
2600
+ } else {
2601
+ $target.removeClass("dynatree-drop-after");
2602
+ }
2603
+ if(hitMode === "before"){
2604
+ $target.addClass("dynatree-drop-before");
2605
+ } else {
2606
+ $target.removeClass("dynatree-drop-before");
2607
+ }
2608
+ if(accept === true){
2609
+ if($source){
2610
+ $source.addClass("dynatree-drop-accept");
2611
+ }
2612
+ $target.addClass("dynatree-drop-accept");
2613
+ helper.addClass("dynatree-drop-accept");
2614
+ }else{
2615
+ if($source){
2616
+ $source.removeClass("dynatree-drop-accept");
2617
+ }
2618
+ $target.removeClass("dynatree-drop-accept");
2619
+ helper.removeClass("dynatree-drop-accept");
2620
+ }
2621
+ if(accept === false){
2622
+ if($source){
2623
+ $source.addClass("dynatree-drop-reject");
2624
+ }
2625
+ $target.addClass("dynatree-drop-reject");
2626
+ helper.addClass("dynatree-drop-reject");
2627
+ }else{
2628
+ if($source){
2629
+ $source.removeClass("dynatree-drop-reject");
2630
+ }
2631
+ $target.removeClass("dynatree-drop-reject");
2632
+ helper.removeClass("dynatree-drop-reject");
2633
+ }
2634
+ },
2635
+
2636
+ _onDragEvent: function(eventName, node, otherNode, event, ui, draggable) {
2637
+ /**
2638
+ * Handles drag'n'drop functionality.
2639
+ *
2640
+ * A standard jQuery drag-and-drop process may generate these calls:
2641
+ *
2642
+ * draggable helper():
2643
+ * _onDragEvent("helper", sourceNode, null, event, null, null);
2644
+ * start:
2645
+ * _onDragEvent("start", sourceNode, null, event, ui, draggable);
2646
+ * drag:
2647
+ * _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
2648
+ * _onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
2649
+ * _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
2650
+ * stop:
2651
+ * _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
2652
+ * _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
2653
+ * _onDragEvent("stop", sourceNode, null, event, ui, draggable);
2654
+ */
2655
+ // if(eventName !== "over"){
2656
+ // this.logDebug("tree._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this);
2657
+ // }
2658
+ var opts = this.options,
2659
+ dnd = this.options.dnd,
2660
+ res = null,
2661
+ nodeTag = $(node.span),
2662
+ hitMode;
2663
+
2664
+ switch (eventName) {
2665
+ case "helper":
2666
+ // Only event and node argument is available
2667
+ var $helper = $("<div class='dynatree-drag-helper'><span class='dynatree-drag-helper-img' /></div>")
2668
+ .append($(event.target).closest('a').clone());
2669
+ // issue 244: helper should be child of scrollParent
2670
+ $("ul.dynatree-container", node.tree.divTree).append($helper);
2671
+ // $(node.tree.divTree).append($helper);
2672
+ // Attach node reference to helper object
2673
+ $helper.data("dtSourceNode", node);
2674
+ // this.logDebug("helper=%o", $helper);
2675
+ // this.logDebug("helper.sourceNode=%o", $helper.data("dtSourceNode"));
2676
+ res = $helper;
2677
+ break;
2678
+ case "start":
2679
+ if(node.isStatusNode()) {
2680
+ res = false;
2681
+ } else if(dnd.onDragStart) {
2682
+ res = dnd.onDragStart(node);
2683
+ }
2684
+ if(res === false) {
2685
+ this.logDebug("tree.onDragStart() cancelled");
2686
+ //draggable._clear();
2687
+ // NOTE: the return value seems to be ignored (drag is not canceled, when false is returned)
2688
+ ui.helper.trigger("mouseup");
2689
+ ui.helper.hide();
2690
+ } else {
2691
+ nodeTag.addClass("dynatree-drag-source");
2692
+ }
2693
+ break;
2694
+ case "enter":
2695
+ res = dnd.onDragEnter ? dnd.onDragEnter(node, otherNode) : null;
2696
+ res = {
2697
+ over: (res !== false) && ((res === true) || (res === "over") || $.inArray("over", res) >= 0),
2698
+ before: (res !== false) && ((res === true) || (res === "before") || $.inArray("before", res) >= 0),
2699
+ after: (res !== false) && ((res === true) || (res === "after") || $.inArray("after", res) >= 0)
2700
+ };
2701
+ ui.helper.data("enterResponse", res);
2702
+ // this.logDebug("helper.enterResponse: %o", res);
2703
+ break;
2704
+ case "over":
2705
+ var enterResponse = ui.helper.data("enterResponse");
2706
+ hitMode = null;
2707
+ if(enterResponse === false){
2708
+ // Don't call onDragOver if onEnter returned false.
2709
+ break;
2710
+ } else if(typeof enterResponse === "string") {
2711
+ // Use hitMode from onEnter if provided.
2712
+ hitMode = enterResponse;
2713
+ } else {
2714
+ // Calculate hitMode from relative cursor position.
2715
+ var nodeOfs = nodeTag.offset();
2716
+ // var relPos = { x: event.clientX - nodeOfs.left,
2717
+ // y: event.clientY - nodeOfs.top };
2718
+ // nodeOfs.top += this.parentTop;
2719
+ // nodeOfs.left += this.parentLeft;
2720
+ var relPos = { x: event.pageX - nodeOfs.left,
2721
+ y: event.pageY - nodeOfs.top };
2722
+ var relPos2 = { x: relPos.x / nodeTag.width(),
2723
+ y: relPos.y / nodeTag.height() };
2724
+ // this.logDebug("event.page: %s/%s", event.pageX, event.pageY);
2725
+ // this.logDebug("event.client: %s/%s", event.clientX, event.clientY);
2726
+ // this.logDebug("nodeOfs: %s/%s", nodeOfs.left, nodeOfs.top);
2727
+ //// this.logDebug("parent: %s/%s", this.parentLeft, this.parentTop);
2728
+ // this.logDebug("relPos: %s/%s", relPos.x, relPos.y);
2729
+ // this.logDebug("relPos2: %s/%s", relPos2.x, relPos2.y);
2730
+ if( enterResponse.after && relPos2.y > 0.75 ){
2731
+ hitMode = "after";
2732
+ } else if(!enterResponse.over && enterResponse.after && relPos2.y > 0.5 ){
2733
+ hitMode = "after";
2734
+ } else if(enterResponse.before && relPos2.y <= 0.25) {
2735
+ hitMode = "before";
2736
+ } else if(!enterResponse.over && enterResponse.before && relPos2.y <= 0.5) {
2737
+ hitMode = "before";
2738
+ } else if(enterResponse.over) {
2739
+ hitMode = "over";
2740
+ }
2741
+ // Prevent no-ops like 'before source node'
2742
+ // TODO: these are no-ops when moving nodes, but not in copy mode
2743
+ if( dnd.preventVoidMoves ){
2744
+ if(node === otherNode){
2745
+ // this.logDebug(" drop over source node prevented");
2746
+ hitMode = null;
2747
+ }else if(hitMode === "before" && otherNode && node === otherNode.getNextSibling()){
2748
+ // this.logDebug(" drop after source node prevented");
2749
+ hitMode = null;
2750
+ }else if(hitMode === "after" && otherNode && node === otherNode.getPrevSibling()){
2751
+ // this.logDebug(" drop before source node prevented");
2752
+ hitMode = null;
2753
+ }else if(hitMode === "over" && otherNode
2754
+ && otherNode.parent === node && otherNode.isLastSibling() ){
2755
+ // this.logDebug(" drop last child over own parent prevented");
2756
+ hitMode = null;
2757
+ }
2758
+ }
2759
+ // this.logDebug("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling());
2760
+ ui.helper.data("hitMode", hitMode);
2761
+ }
2762
+ // Auto-expand node (only when 'over' the node, not 'before', or 'after')
2763
+ if(hitMode === "over"
2764
+ && dnd.autoExpandMS && node.hasChildren() !== false && !node.bExpanded) {
2765
+ node.scheduleAction("expand", dnd.autoExpandMS);
2766
+ }
2767
+ if(hitMode && dnd.onDragOver){
2768
+ res = dnd.onDragOver(node, otherNode, hitMode);
2769
+ }
2770
+ this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false);
2771
+ break;
2772
+ case "drop":
2773
+ hitMode = ui.helper.data("hitMode");
2774
+ if(hitMode && dnd.onDrop){
2775
+ dnd.onDrop(node, otherNode, hitMode, ui, draggable);
2776
+ }
2777
+ break;
2778
+ case "leave":
2779
+ // Cancel pending expand request
2780
+ node.scheduleAction("cancel");
2781
+ ui.helper.data("enterResponse", null);
2782
+ ui.helper.data("hitMode", null);
2783
+ this._setDndStatus(otherNode, node, ui.helper, "out", undefined);
2784
+ if(dnd.onDragLeave){
2785
+ dnd.onDragLeave(node, otherNode);
2786
+ }
2787
+ break;
2788
+ case "stop":
2789
+ nodeTag.removeClass("dynatree-drag-source");
2790
+ if(dnd.onDragStop){
2791
+ dnd.onDragStop(node);
2792
+ }
2793
+ break;
2794
+ default:
2795
+ throw "Unsupported drag event: " + eventName;
2796
+ }
2797
+ return res;
2798
+ },
2799
+
2800
+ cancelDrag: function() {
2801
+ var dd = $.ui.ddmanager.current;
2802
+ if(dd){
2803
+ dd.cancel();
2804
+ }
2805
+ },
2806
+
2807
+ // --- end of class
2808
+ lastentry: undefined
2809
+ };
2810
+
2811
+ /*************************************************************************
2812
+ * Widget $(..).dynatree
2813
+ */
2814
+
2815
+ $.widget("ui.dynatree", {
2816
+ /*
2817
+ init: function() {
2818
+ // ui.core 1.6 renamed init() to _init(): this stub assures backward compatibility
2819
+ _log("warn", "ui.dynatree.init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher.");
2820
+ return this._init();
2821
+ },
2822
+ */
2823
+ _init: function() {
2824
+ if( parseFloat($.ui.version) < 1.8 ) {
2825
+ // jquery.ui.core 1.8 renamed _init() to _create(): this stub assures backward compatibility
2826
+ if(this.options.debugLevel >= 0){
2827
+ _log("warn", "ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher.");
2828
+ }
2829
+ return this._create();
2830
+ }
2831
+ // jquery.ui.core 1.8 still uses _init() to perform "default functionality"
2832
+ if(this.options.debugLevel >= 2){
2833
+ _log("debug", "ui.dynatree._init() was called; no current default functionality.");
2834
+ }
2835
+ },
2836
+
2837
+ _create: function() {
2838
+ var opts = this.options;
2839
+ if(opts.debugLevel >= 1){
2840
+ logMsg("Dynatree._create(): version='%s', debugLevel=%o.", $.ui.dynatree.version, this.options.debugLevel);
2841
+ }
2842
+ // The widget framework supplies this.element and this.options.
2843
+ this.options.event += ".dynatree"; // namespace event
2844
+
2845
+ var divTree = this.element.get(0);
2846
+ /* // Clear container, in case it contained some 'waiting' or 'error' text
2847
+ // for clients that don't support JS
2848
+ if( opts.children || (opts.initAjax && opts.initAjax.url) || opts.initId )
2849
+ $(divTree).empty();
2850
+ */
2851
+ // Create the DynaTree object
2852
+ this.tree = new DynaTree(this);
2853
+ this.tree._load();
2854
+ this.tree.logDebug("Dynatree._init(): done.");
2855
+ },
2856
+
2857
+ bind: function() {
2858
+ // Prevent duplicate binding
2859
+ this.unbind();
2860
+
2861
+ var eventNames = "click.dynatree dblclick.dynatree";
2862
+ if( this.options.keyboard ){
2863
+ // Note: leading ' '!
2864
+ eventNames += " keypress.dynatree keydown.dynatree";
2865
+ }
2866
+ this.element.bind(eventNames, function(event){
2867
+ var dtnode = $.ui.dynatree.getNode(event.target);
2868
+ if( !dtnode ){
2869
+ return true; // Allow bubbling of other events
2870
+ }
2871
+ var tree = dtnode.tree;
2872
+ var o = tree.options;
2873
+ tree.logDebug("event(%s): dtnode: %s", event.type, dtnode);
2874
+ var prevPhase = tree.phase;
2875
+ tree.phase = "userEvent";
2876
+ try {
2877
+ switch(event.type) {
2878
+ case "click":
2879
+ return ( o.onClick && o.onClick.call(tree, dtnode, event)===false ) ? false : dtnode._onClick(event);
2880
+ case "dblclick":
2881
+ return ( o.onDblClick && o.onDblClick.call(tree, dtnode, event)===false ) ? false : dtnode._onDblClick(event);
2882
+ case "keydown":
2883
+ return ( o.onKeydown && o.onKeydown.call(tree, dtnode, event)===false ) ? false : dtnode._onKeydown(event);
2884
+ case "keypress":
2885
+ return ( o.onKeypress && o.onKeypress.call(tree, dtnode, event)===false ) ? false : dtnode._onKeypress(event);
2886
+ }
2887
+ } catch(e) {
2888
+ var _ = null; // issue 117
2889
+ tree.logWarning("bind(%o): dtnode: %o, error: %o", event, dtnode, e);
2890
+ } finally {
2891
+ tree.phase = prevPhase;
2892
+ }
2893
+ });
2894
+
2895
+ // focus/blur don't bubble, i.e. are not delegated to parent <div> tags,
2896
+ // so we use the addEventListener capturing phase.
2897
+ // See http://www.howtocreate.co.uk/tutorials/javascript/domevents
2898
+ function __focusHandler(event) {
2899
+ // Handles blur and focus.
2900
+ // Fix event for IE:
2901
+ // doesn't pass JSLint:
2902
+ // event = arguments[0] = $.event.fix( event || window.event );
2903
+ // what jQuery does:
2904
+ // var args = jQuery.makeArray( arguments );
2905
+ // event = args[0] = jQuery.event.fix( event || window.event );
2906
+ event = $.event.fix( event || window.event );
2907
+ var dtnode = $.ui.dynatree.getNode(event.target);
2908
+ return dtnode ? dtnode._onFocus(event) : false;
2909
+ }
2910
+ var div = this.tree.divTree;
2911
+ if( div.addEventListener ) {
2912
+ div.addEventListener("focus", __focusHandler, true);
2913
+ div.addEventListener("blur", __focusHandler, true);
2914
+ } else {
2915
+ div.onfocusin = div.onfocusout = __focusHandler;
2916
+ }
2917
+ // EVENTS
2918
+ // disable click if event is configured to something else
2919
+ // if (!(/^click/).test(o.event))
2920
+ // this.$tabs.bind("click.tabs", function() { return false; });
2921
+
2922
+ },
2923
+
2924
+ unbind: function() {
2925
+ this.element.unbind(".dynatree");
2926
+ },
2927
+
2928
+ /* TODO: we could handle option changes during runtime here (maybe to re-render, ...)
2929
+ setData: function(key, value) {
2930
+ this.tree.logDebug("dynatree.setData('" + key + "', '" + value + "')");
2931
+ },
2932
+ */
2933
+ enable: function() {
2934
+ this.bind();
2935
+ // Call default disable(): remove -disabled from css:
2936
+ $.Widget.prototype.enable.apply(this, arguments);
2937
+ },
2938
+
2939
+ disable: function() {
2940
+ this.unbind();
2941
+ // Call default disable(): add -disabled to css:
2942
+ $.Widget.prototype.disable.apply(this, arguments);
2943
+ },
2944
+
2945
+ // --- getter methods (i.e. NOT returning a reference to $)
2946
+ getTree: function() {
2947
+ return this.tree;
2948
+ },
2949
+
2950
+ getRoot: function() {
2951
+ return this.tree.getRoot();
2952
+ },
2953
+
2954
+ getActiveNode: function() {
2955
+ return this.tree.getActiveNode();
2956
+ },
2957
+
2958
+ getSelectedNodes: function() {
2959
+ return this.tree.getSelectedNodes();
2960
+ },
2961
+
2962
+ // ------------------------------------------------------------------------
2963
+ lastentry: undefined
2964
+ });
2965
+
2966
+
2967
+ // The following methods return a value (thus breaking the jQuery call chain):
2968
+ if( parseFloat($.ui.version) < 1.8 ) {
2969
+ $.ui.dynatree.getter = "getTree getRoot getActiveNode getSelectedNodes";
2970
+ }
2971
+
2972
+ /*******************************************************************************
2973
+ * Tools in ui.dynatree namespace
2974
+ */
2975
+ $.ui.dynatree.version = "$Version: 1.2.1_rc3$";
2976
+
2977
+ /**
2978
+ * Return a DynaTreeNode object for a given DOM element
2979
+ */
2980
+ $.ui.dynatree.getNode = function(el) {
2981
+ if(el instanceof DynaTreeNode){
2982
+ return el; // el already was a DynaTreeNode
2983
+ }
2984
+ if(el.selector !== undefined){
2985
+ el = el[0]; // el was a jQuery object: use the DOM element
2986
+ }
2987
+ // TODO: for some reason $el.parents("[dtnode]") does not work (jQuery 1.6.1)
2988
+ // maybe, because dtnode is a property, not an attribute
2989
+ while( el ) {
2990
+ if(el.dtnode) {
2991
+ return el.dtnode;
2992
+ }
2993
+ el = el.parentNode;
2994
+ }
2995
+ return null;
2996
+ /*
2997
+ var $el = el.selector === undefined ? $(el) : el,
2998
+ // parent = $el.closest("[dtnode]"),
2999
+ // parent = $el.parents("[dtnode]").first(),
3000
+ useProp = (typeof $el.prop == "function"),
3001
+ node;
3002
+ $el.parents().each(function(){
3003
+ node = useProp ? $(this).prop("dtnode") : $(this).attr("dtnode");
3004
+ if(node){
3005
+ return false;
3006
+ }
3007
+ });
3008
+ return node;
3009
+ */
3010
+ }
3011
+
3012
+ /**Return persistence information from cookies.*/
3013
+ $.ui.dynatree.getPersistData = DynaTreeStatus._getTreePersistData;
3014
+
3015
+ /*******************************************************************************
3016
+ * Plugin default options:
3017
+ */
3018
+ $.ui.dynatree.prototype.options = {
3019
+ title: "Dynatree", // Tree's name (only used for debug output)
3020
+ minExpandLevel: 1, // 1: root node is not collapsible
3021
+ imagePath: null, // Path to a folder containing icons. Defaults to 'skin/' subdirectory.
3022
+ children: null, // Init tree structure from this object array.
3023
+ initId: null, // Init tree structure from a <ul> element with this ID.
3024
+ initAjax: null, // Ajax options used to initialize the tree strucuture.
3025
+ autoFocus: true, // Set focus to first child, when expanding or lazy-loading.
3026
+ keyboard: true, // Support keyboard navigation.
3027
+ persist: false, // Persist expand-status to a cookie
3028
+ autoCollapse: false, // Automatically collapse all siblings, when a node is expanded.
3029
+ clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand
3030
+ activeVisible: true, // Make sure, active nodes are visible (expanded).
3031
+ checkbox: false, // Show checkboxes.
3032
+ selectMode: 2, // 1:single, 2:multi, 3:multi-hier
3033
+ fx: null, // Animations, e.g. null or { height: "toggle", duration: 200 }
3034
+ noLink: false, // Use <span> instead of <a> tags for all nodes
3035
+ // Low level event handlers: onEvent(dtnode, event): return false, to stop default processing
3036
+ onClick: null, // null: generate focus, expand, activate, select events.
3037
+ onDblClick: null, // (No default actions.)
3038
+ onKeydown: null, // null: generate keyboard navigation (focus, expand, activate).
3039
+ onKeypress: null, // (No default actions.)
3040
+ onFocus: null, // null: set focus to node.
3041
+ onBlur: null, // null: remove focus from node.
3042
+
3043
+ // Pre-event handlers onQueryEvent(flag, dtnode): return false, to stop processing
3044
+ onQueryActivate: null, // Callback(flag, dtnode) before a node is (de)activated.
3045
+ onQuerySelect: null, // Callback(flag, dtnode) before a node is (de)selected.
3046
+ onQueryExpand: null, // Callback(flag, dtnode) before a node is expanded/collpsed.
3047
+
3048
+ // High level event handlers
3049
+ onPostInit: null, // Callback(isReloading, isError) when tree was (re)loaded.
3050
+ onActivate: null, // Callback(dtnode) when a node is activated.
3051
+ onDeactivate: null, // Callback(dtnode) when a node is deactivated.
3052
+ onSelect: null, // Callback(flag, dtnode) when a node is (de)selected.
3053
+ onExpand: null, // Callback(flag, dtnode) when a node is expanded/collapsed.
3054
+ onLazyRead: null, // Callback(dtnode) when a lazy node is expanded for the first time.
3055
+ onCustomRender: null, // Callback(dtnode) before a node is rendered. Return a HTML string to override.
3056
+ onCreate: null, // Callback(dtnode, nodeSpan) after a node was rendered for the first time.
3057
+ onRender: null, // Callback(dtnode, nodeSpan) after a node was rendered.
3058
+
3059
+ // Drag'n'drop support
3060
+ dnd: {
3061
+ // Make tree nodes draggable:
3062
+ onDragStart: null, // Callback(sourceNode), return true, to enable dnd
3063
+ onDragStop: null, // Callback(sourceNode)
3064
+ // helper: null,
3065
+ // Make tree nodes accept draggables
3066
+ autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
3067
+ preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
3068
+ onDragEnter: null, // Callback(targetNode, sourceNode)
3069
+ onDragOver: null, // Callback(targetNode, sourceNode, hitMode)
3070
+ onDrop: null, // Callback(targetNode, sourceNode, hitMode)
3071
+ onDragLeave: null // Callback(targetNode, sourceNode)
3072
+ },
3073
+ ajaxDefaults: { // Used by initAjax option
3074
+ cache: false, // false: Append random '_' argument to the request url to prevent caching.
3075
+ timeout: 0, // >0: Make sure we get an ajax error for invalid URLs
3076
+ dataType: "json" // Expect json format and pass json object to callbacks.
3077
+ },
3078
+ strings: {
3079
+ loading: "Loading&#8230;",
3080
+ loadError: "Load error!"
3081
+ },
3082
+ generateIds: false, // Generate id attributes like <span id='dynatree-id-KEY'>
3083
+ idPrefix: "dynatree-id-", // Used to generate node id's like <span id="dynatree-id-<key>">.
3084
+ keyPathSeparator: "/", // Used by node.getKeyPath() and tree.loadKeyPath().
3085
+ // cookieId: "dynatree-cookie", // Choose a more unique name, to allow multiple trees.
3086
+ cookieId: "dynatree", // Choose a more unique name, to allow multiple trees.
3087
+ cookie: {
3088
+ expires: null //7, // Days or Date; null: session cookie
3089
+ // path: "/", // Defaults to current page
3090
+ // domain: "jquery.com",
3091
+ // secure: true
3092
+ },
3093
+ // Class names used, when rendering the HTML markup.
3094
+ // Note: if only single entries are passed for options.classNames, all other
3095
+ // values are still set to default.
3096
+ classNames: {
3097
+ container: "dynatree-container",
3098
+ node: "dynatree-node",
3099
+ folder: "dynatree-folder",
3100
+ // document: "dynatree-document",
3101
+
3102
+ empty: "dynatree-empty",
3103
+ vline: "dynatree-vline",
3104
+ expander: "dynatree-expander",
3105
+ connector: "dynatree-connector",
3106
+ checkbox: "dynatree-checkbox",
3107
+ nodeIcon: "dynatree-icon",
3108
+ title: "dynatree-title",
3109
+ noConnector: "dynatree-no-connector",
3110
+
3111
+ nodeError: "dynatree-statusnode-error",
3112
+ nodeWait: "dynatree-statusnode-wait",
3113
+ hidden: "dynatree-hidden",
3114
+ combinedExpanderPrefix: "dynatree-exp-",
3115
+ combinedIconPrefix: "dynatree-ico-",
3116
+ nodeLoading: "dynatree-loading",
3117
+ // disabled: "dynatree-disabled",
3118
+ hasChildren: "dynatree-has-children",
3119
+ active: "dynatree-active",
3120
+ selected: "dynatree-selected",
3121
+ expanded: "dynatree-expanded",
3122
+ lazy: "dynatree-lazy",
3123
+ focused: "dynatree-focused",
3124
+ partsel: "dynatree-partsel",
3125
+ lastsib: "dynatree-lastsib"
3126
+ },
3127
+ debugLevel: 1,
3128
+
3129
+ // ------------------------------------------------------------------------
3130
+ lastentry: undefined
3131
+ };
3132
+ //
3133
+ if( parseFloat($.ui.version) < 1.8 ) {
3134
+ $.ui.dynatree.defaults = $.ui.dynatree.prototype.options;
3135
+ }
3136
+
3137
+ /*******************************************************************************
3138
+ * Reserved data attributes for a tree node.
3139
+ */
3140
+ $.ui.dynatree.nodedatadefaults = {
3141
+ title: null, // (required) Displayed name of the node (html is allowed here)
3142
+ key: null, // May be used with activate(), select(), find(), ...
3143
+ isFolder: false, // Use a folder icon. Also the node is expandable but not selectable.
3144
+ isLazy: false, // Call onLazyRead(), when the node is expanded for the first time to allow for delayed creation of children.
3145
+ tooltip: null, // Show this popup text.
3146
+ href: null, // Added to the generated <a> tag.
3147
+ icon: null, // Use a custom image (filename relative to tree.options.imagePath). 'null' for default icon, 'false' for no icon.
3148
+ addClass: null, // Class name added to the node's span tag.
3149
+ noLink: false, // Use <span> instead of <a> tag for this node
3150
+ activate: false, // Initial active status.
3151
+ focus: false, // Initial focused status.
3152
+ expand: false, // Initial expanded status.
3153
+ select: false, // Initial selected status.
3154
+ hideCheckbox: false, // Suppress checkbox display for this node.
3155
+ unselectable: false, // Prevent selection.
3156
+ // disabled: false,
3157
+ // The following attributes are only valid if passed to some functions:
3158
+ children: null, // Array of child nodes.
3159
+ // NOTE: we can also add custom attributes here.
3160
+ // This may then also be used in the onActivate(), onSelect() or onLazyTree() callbacks.
3161
+ // ------------------------------------------------------------------------
3162
+ lastentry: undefined
3163
+ };
3164
+
3165
+ /*******************************************************************************
3166
+ * Drag and drop support
3167
+ */
3168
+ function _initDragAndDrop(tree) {
3169
+ var dnd = tree.options.dnd || null;
3170
+ // Register 'connectToDynatree' option with ui.draggable
3171
+ if(dnd && (dnd.onDragStart || dnd.onDrop)) {
3172
+ _registerDnd();
3173
+ }
3174
+ // Attach ui.draggable to this Dynatree instance
3175
+ if(dnd && dnd.onDragStart ) {
3176
+ tree.$tree.draggable({
3177
+ addClasses: false,
3178
+ appendTo: "body",
3179
+ containment: false,
3180
+ delay: 0,
3181
+ distance: 4,
3182
+ revert: false,
3183
+ scroll: true, // issue 244: enable scrolling (if ul.dynatree-container)
3184
+ scrollSpeed: 7,
3185
+ scrollSensitivity: 10,
3186
+ // Delegate draggable.start, drag, and stop events to our handler
3187
+ connectToDynatree: true,
3188
+ // Let source tree create the helper element
3189
+ helper: function(event) {
3190
+ var sourceNode = $.ui.dynatree.getNode(event.target);
3191
+ if(!sourceNode){ // issue 211
3192
+ return "<div></div>";
3193
+ }
3194
+ return sourceNode.tree._onDragEvent("helper", sourceNode, null, event, null, null);
3195
+ },
3196
+ start: function(event, ui) {
3197
+ // var sourceNode = $.ui.dynatree.getNode(event.target);
3198
+ // don't return false if sourceNode == null (see issue 268)
3199
+ },
3200
+ _last: null
3201
+ });
3202
+ }
3203
+ // Attach ui.droppable to this Dynatree instance
3204
+ if(dnd && dnd.onDrop) {
3205
+ tree.$tree.droppable({
3206
+ addClasses: false,
3207
+ tolerance: "intersect",
3208
+ greedy: false,
3209
+ _last: null
3210
+ });
3211
+ }
3212
+ }
3213
+
3214
+ //--- Extend ui.draggable event handling --------------------------------------
3215
+ var didRegisterDnd = false;
3216
+ var _registerDnd = function() {
3217
+ if(didRegisterDnd){
3218
+ return;
3219
+ }
3220
+ // Register proxy-functions for draggable.start/drag/stop
3221
+ $.ui.plugin.add("draggable", "connectToDynatree", {
3222
+ start: function(event, ui) {
3223
+ var draggable = $(this).data("draggable"),
3224
+ sourceNode = ui.helper.data("dtSourceNode") || null;
3225
+ // logMsg("draggable-connectToDynatree.start, %s", sourceNode);
3226
+ // logMsg(" this: %o", this);
3227
+ // logMsg(" event: %o", event);
3228
+ // logMsg(" draggable: %o", draggable);
3229
+ // logMsg(" ui: %o", ui);
3230
+
3231
+ if(sourceNode) {
3232
+ // Adjust helper offset, so cursor is slightly outside top/left corner
3233
+ // draggable.offset.click.top -= event.target.offsetTop;
3234
+ // draggable.offset.click.left -= event.target.offsetLeft;
3235
+ draggable.offset.click.top = -2;
3236
+ draggable.offset.click.left = + 16;
3237
+ // logMsg(" draggable2: %o", draggable);
3238
+ // logMsg(" draggable.offset.click FIXED: %s/%s", draggable.offset.click.left, draggable.offset.click.top);
3239
+ // Trigger onDragStart event
3240
+ // TODO: when called as connectTo..., the return value is ignored(?)
3241
+ return sourceNode.tree._onDragEvent("start", sourceNode, null, event, ui, draggable);
3242
+ }
3243
+ },
3244
+ drag: function(event, ui) {
3245
+ var draggable = $(this).data("draggable"),
3246
+ sourceNode = ui.helper.data("dtSourceNode") || null,
3247
+ prevTargetNode = ui.helper.data("dtTargetNode") || null,
3248
+ targetNode = $.ui.dynatree.getNode(event.target);
3249
+ // logMsg("$.ui.dynatree.getNode(%o): %s", event.target, targetNode);
3250
+ // logMsg("connectToDynatree.drag: helper: %o", ui.helper[0]);
3251
+ if(event.target && !targetNode){
3252
+ // We got a drag event, but the targetNode could not be found
3253
+ // at the event location. This may happen,
3254
+ // 1. if the mouse jumped over the drag helper,
3255
+ // 2. or if non-dynatree element is dragged
3256
+ // We ignore it:
3257
+ var isHelper = $(event.target).closest("div.dynatree-drag-helper,#dynatree-drop-marker").length > 0;
3258
+ if(isHelper){
3259
+ // logMsg("Drag event over helper: ignored.");
3260
+ return;
3261
+ }
3262
+ }
3263
+ // logMsg("draggable-connectToDynatree.drag: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode"));
3264
+ ui.helper.data("dtTargetNode", targetNode);
3265
+ // Leaving a tree node
3266
+ if(prevTargetNode && prevTargetNode !== targetNode ) {
3267
+ prevTargetNode.tree._onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
3268
+ }
3269
+ if(targetNode){
3270
+ if(!targetNode.tree.options.dnd.onDrop) {
3271
+ // not enabled as drop target
3272
+ noop(); // Keep JSLint happy
3273
+ } else if(targetNode === prevTargetNode) {
3274
+ // Moving over same node
3275
+ targetNode.tree._onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
3276
+ }else{
3277
+ // Entering this node first time
3278
+ targetNode.tree._onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
3279
+ }
3280
+ }
3281
+ // else go ahead with standard event handling
3282
+ },
3283
+ stop: function(event, ui) {
3284
+ var draggable = $(this).data("draggable"),
3285
+ sourceNode = ui.helper.data("dtSourceNode") || null,
3286
+ targetNode = ui.helper.data("dtTargetNode") || null,
3287
+ mouseDownEvent = draggable._mouseDownEvent,
3288
+ eventType = event.type,
3289
+ dropped = (eventType == "mouseup" && event.which == 1);
3290
+ // logMsg("draggable-connectToDynatree.stop: targetNode(from event): %s, dtTargetNode: %s", targetNode, ui.helper.data("dtTargetNode"));
3291
+ // logMsg("draggable-connectToDynatree.stop, %s", sourceNode);
3292
+ // logMsg(" type: %o, downEvent: %o, upEvent: %o", eventType, mouseDownEvent, event);
3293
+ // logMsg(" targetNode: %o", targetNode);
3294
+ if(!dropped){
3295
+ logMsg("Drag was cancelled");
3296
+ }
3297
+ if(targetNode) {
3298
+ if(dropped){
3299
+ targetNode.tree._onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
3300
+ }
3301
+ targetNode.tree._onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
3302
+ }
3303
+ if(sourceNode){
3304
+ sourceNode.tree._onDragEvent("stop", sourceNode, null, event, ui, draggable);
3305
+ }
3306
+ }
3307
+ });
3308
+ didRegisterDnd = true;
3309
+ };
3310
+
3311
+ // ---------------------------------------------------------------------------
3312
+ })(jQuery);