fancytree-rails 2.0.0.pre.6.pre.1 → 2.0.0.pre.11.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Rakefile +6 -7
  4. data/lib/fancytree/rails/version.rb +2 -2
  5. data/vendor/assets/images/fancytree/skin-win8-xxl/icons.gif +0 -0
  6. data/vendor/assets/images/fancytree/skin-win8-xxl/loading.gif +0 -0
  7. data/vendor/assets/javascripts/fancytree.js +1 -1
  8. data/vendor/assets/javascripts/fancytree/MIT-LICENSE.txt +21 -0
  9. data/vendor/assets/javascripts/fancytree/jquery.fancytree-all.js +1267 -475
  10. data/vendor/assets/javascripts/fancytree/jquery.fancytree-custom.min.js +41 -0
  11. data/vendor/assets/javascripts/fancytree/jquery.fancytree.js +582 -310
  12. data/vendor/assets/javascripts/fancytree/jquery.fancytree.min.js +14 -7
  13. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.childcounter.js +185 -0
  14. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.clones.js +417 -0
  15. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.columnview.js +149 -0
  16. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.debug.js +142 -0
  17. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.dnd.js +539 -0
  18. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.edit.js +318 -0
  19. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.filter.js +173 -0
  20. data/vendor/assets/javascripts/fancytree/{jquery.fancytree.awesome.js → src/jquery.fancytree.glyph.js} +28 -26
  21. data/vendor/assets/javascripts/fancytree/{jquery.fancytree.gridnav.js → src/jquery.fancytree.gridnav.js} +77 -41
  22. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.js +4027 -0
  23. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.menu.js +155 -0
  24. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.persist.js +345 -0
  25. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.table.js +345 -0
  26. data/vendor/assets/javascripts/fancytree/src/jquery.fancytree.themeroller.js +82 -0
  27. data/vendor/assets/stylesheets/fancytree/skin-awesome/ui.fancytree.css +29 -15
  28. data/vendor/assets/stylesheets/fancytree/skin-awesome/ui.fancytree.min.css +1 -1
  29. data/vendor/assets/stylesheets/fancytree/skin-bootstrap/ui.fancytree.css +366 -0
  30. data/vendor/assets/stylesheets/fancytree/skin-bootstrap/ui.fancytree.min.css +6 -0
  31. data/vendor/assets/stylesheets/fancytree/skin-lion/ui.fancytree.css +34 -7
  32. data/vendor/assets/stylesheets/fancytree/skin-lion/ui.fancytree.min.css +1 -1
  33. data/vendor/assets/stylesheets/fancytree/skin-vista/ui.fancytree.css +34 -7
  34. data/vendor/assets/stylesheets/fancytree/skin-vista/ui.fancytree.min.css +1 -1
  35. data/vendor/assets/stylesheets/fancytree/skin-win7/ui.fancytree.css +34 -7
  36. data/vendor/assets/stylesheets/fancytree/skin-win7/ui.fancytree.min.css +1 -1
  37. data/vendor/assets/stylesheets/fancytree/skin-win8-xxl/ui.fancytree.css +507 -0
  38. data/vendor/assets/stylesheets/fancytree/skin-win8-xxl/ui.fancytree.min.css +11 -0
  39. data/vendor/assets/stylesheets/fancytree/skin-win8/ui.fancytree.css +34 -7
  40. data/vendor/assets/stylesheets/fancytree/skin-win8/ui.fancytree.min.css +1 -1
  41. data/vendor/assets/stylesheets/fancytree/skin-xp/ui.fancytree.css +34 -7
  42. data/vendor/assets/stylesheets/fancytree/skin-xp/ui.fancytree.min.css +1 -1
  43. metadata +24 -13
  44. data/vendor/assets/javascripts/fancytree/jquery.fancytree-all.min.js +0 -7
  45. data/vendor/assets/javascripts/fancytree/jquery.fancytree-all.min.js.map +0 -1
  46. data/vendor/assets/javascripts/fancytree/jquery.fancytree.min.js.map +0 -1
  47. data/vendor/assets/stylesheets/fancytree/skin-lion/ui.fancytree-org.css +0 -460
  48. data/vendor/assets/stylesheets/fancytree/skin-themeroller/ui.fancytree-org.css +0 -505
  49. data/vendor/assets/stylesheets/fancytree/skin-vista/ui.fancytree-org.css +0 -610
  50. data/vendor/assets/stylesheets/fancytree/skin-win7/ui.fancytree-org.css +0 -592
  51. data/vendor/assets/stylesheets/fancytree/skin-win8/ui.fancytree-org.css +0 -602
  52. data/vendor/assets/stylesheets/fancytree/skin-xp/ui.fancytree-org.css +0 -578
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5f3bd0723ab5734e8be4fb9538142c604bc0e103
4
- data.tar.gz: 70027741a2c1b9432fac77556e7e7ba4d8238961
3
+ metadata.gz: 1f8597dbe02db0bcac1ca59245d2b4b1ef806241
4
+ data.tar.gz: 6f23c661681871ae96ea0f8e6bf6408c3c8f1031
5
5
  SHA512:
6
- metadata.gz: 755155660f79623e8c76001669dece17ecc56111fd9fd680e20dfd35b7dc51a8cfea71aca2f7923e4435c028d0be889d7cb3946549dc09ca45948c089e7d70fb
7
- data.tar.gz: 7c2d1befb9f1661c6c0c91129064e3909fd2fd231ab8fa582892e8b2b2b9d501c75583e0f25be1b2beea14b3b52397a43ad469f6de8a35ae3ae17f810be2bfdd
6
+ metadata.gz: fd9930807b36cffe63374ee0ecc41dd5d8c334300122d27379ed07136eb7aa099b0f2a2842574e7fc3818338a196e214c0d4eefc6a64cae78484640ab550805a
7
+ data.tar.gz: 1d1e2ab886857144211259f04c889765b75ee3b22ad7422b2716754c5cf971ec327ba880cc43aa0149eb03600981e18f4d38e25bcc4c602b5cd0cdc16a172a90
@@ -1,3 +1,7 @@
1
+ ## v2.0.0-11-1
2
+
3
+ * use fancytree v. 2.0.0-11
4
+
1
5
  ## v2.0.0-6-1
2
6
 
3
7
  * align versions with FancyTree
data/Rakefile CHANGED
@@ -7,8 +7,9 @@ namespace :fancytree do
7
7
  task :update => :build do
8
8
  cd "tmp/fancytree" do
9
9
 
10
- cd "build" do
11
- FileUtils.cp(Dir.glob("*.js"), '../../../vendor/assets/javascripts/fancytree')
10
+ cd "dist" do
11
+ js = FileList['*'].exclude(/\Askip*/)
12
+ FileUtils.cp_r(js, '../../../vendor/assets/javascripts/fancytree')
12
13
 
13
14
  css_files = Dir.glob("skin*/*.css")
14
15
  css_files.each do |file|
@@ -23,17 +24,15 @@ namespace :fancytree do
23
24
  end
24
25
  end
25
26
 
26
- #following files are not in the build yet:
27
- FileUtils.cp(['src/jquery.fancytree.awesome.js'], '../../vendor/assets/javascripts/fancytree')
28
-
29
27
  end
30
28
  end
31
29
 
32
30
  task :build => "tmp/fancytree" do
33
31
  cd "tmp/fancytree" do
34
32
  #sh "npm install -g grunt-cli"
35
- sh "npm install"
36
- sh "grunt build --force" #foce because of tabfix
33
+ #Non needed any more, currnetyl ./dist contains compiled version for the release
34
+ # sh "npm install"
35
+ # sh "grunt build --force" #foce because of tabfix
37
36
  end
38
37
  end
39
38
 
@@ -1,6 +1,6 @@
1
1
  module Fancytree
2
2
  module Rails
3
- VERSION = "2.0.0-6-1"
4
- FANCYTREE_VERSION="v2.0.0-6"
3
+ VERSION = "2.0.0-11-1"
4
+ FANCYTREE_VERSION="v2.0.0-11"
5
5
  end
6
6
  end
@@ -1,2 +1,2 @@
1
1
  //= require jquery
2
- //= require fancytree/jquery.fancytree-all.min.js
2
+ //= require fancytree/jquery.fancytree-all.js
@@ -0,0 +1,21 @@
1
+ Copyright 2006-2014 Martin Wendt,
2
+ http://wwWendt.de/
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -7,8 +7,8 @@
7
7
  * Released under the MIT license
8
8
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
9
9
  *
10
- * @version 2.0.0-6
11
- * @date 2014-02-10T10:52
10
+ * @version 2.0.0-11
11
+ * @date 2014-04-27T22:28
12
12
  */
13
13
 
14
14
  /** Core Fancytree module.
@@ -168,6 +168,20 @@ function _makeResolveFunc(deferred, context){
168
168
  }
169
169
 
170
170
 
171
+ function _getElementDataAsDict($el){
172
+ // Evaluate 'data-NAME' attributes with special treatment for 'data-json'.
173
+ var d = $.extend({}, $el.data()),
174
+ json = d.json;
175
+ delete d.fancytree; // added to container by widget factory
176
+ if( json ) {
177
+ delete d.json;
178
+ // <li data-json='...'> is already returned as object (http://api.jquery.com/data/#data-html5)
179
+ d = $.extend(d, json);
180
+ }
181
+ return d;
182
+ }
183
+
184
+
171
185
  // TODO: use currying
172
186
  function _makeNodeTitleMatcher(s){
173
187
  s = s.toLowerCase();
@@ -178,11 +192,12 @@ function _makeNodeTitleMatcher(s){
178
192
 
179
193
  var i,
180
194
  FT = null, // initialized below
195
+ ENTITY_MAP = {"&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;", "/": "&#x2F;"},
181
196
  //boolean attributes that can be set with equivalent class names in the LI tags
182
- CLASS_ATTRS = "active expanded focus folder lazy selected unselectable".split(" "),
197
+ CLASS_ATTRS = "active expanded focus folder hideCheckbox lazy selected unselectable".split(" "),
183
198
  CLASS_ATTR_MAP = {},
184
199
  // Top-level Fancytree node attributes, that can be set by dict
185
- NODE_ATTRS = "expanded extraClasses folder hideCheckbox key lazy selected title tooltip unselectable".split(" "),
200
+ NODE_ATTRS = "expanded extraClasses folder hideCheckbox key lazy refKey selected title tooltip unselectable".split(" "),
186
201
  NODE_ATTR_MAP = {},
187
202
  // Attribute names that should NOT be added to node.data
188
203
  NONE_NODE_DATA_MAP = {"active": true, "children": true, "data": true, "focus": true};
@@ -205,21 +220,22 @@ for(i=0; i<NODE_ATTRS.length; i++){ NODE_ATTR_MAP[NODE_ATTRS[i]] = true; }
205
220
  * @param {FancytreeNode} parent
206
221
  * @param {NodeData} obj
207
222
  *
208
- * @property {Fancytree} tree
209
- * @property {FancytreeNode} parent Parent node
210
- * @property {string} key
211
- * @property {string} title
223
+ * @property {Fancytree} tree The tree instance
224
+ * @property {FancytreeNode} parent The parent node
225
+ * @property {string} key Node id (must be unique inside the tree)
226
+ * @property {string} title Display name (may contain HTML)
212
227
  * @property {object} data Contains all extra data that was passed on node creation
213
- * @property {FancytreeNode[] | null | undefined} children list of child nodes
214
- * @property {boolean} isStatusNode
215
- * @property {boolean} expanded
216
- * @property {boolean} folder
217
- * @property {string} extraClasses
218
- * @property {boolean} lazy
219
- * @property {boolean} selected
220
- * @property {string} tooltip
221
- * @property {string} data.href
222
- * @property {string} data.target
228
+ * @property {FancytreeNode[] | null | undefined} children Array of child nodes.<br>
229
+ * For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array
230
+ * to define a node that has no children.
231
+ * @property {boolean} expanded Use isExpanded(), setExpanded() to access this property.
232
+ * @property {string} extraClasses Addtional CSS classes, added to the node's `&lt;span>`
233
+ * @property {boolean} folder Folder nodes have different default icons and click behavior.<br>
234
+ * Note: Also non-folders may have children.
235
+ * @property {string} statusNodeType null or type of temporarily generated system node like 'loading', or 'error'.
236
+ * @property {boolean} lazy True if this node is loaded on demand, i.e. on first expansion.
237
+ * @property {boolean} selected Use isSelected(), setSelected() to access this property.
238
+ * @property {string} tooltip Alternative description used as hover banner
223
239
  */
224
240
  function FancytreeNode(parent, obj){
225
241
  var i, l, name, cl;
@@ -228,7 +244,9 @@ function FancytreeNode(parent, obj){
228
244
  this.tree = parent.tree;
229
245
  this.ul = null;
230
246
  this.li = null; // <li id='key' ftnode=this> tag
231
- this.isStatusNode = false;
247
+ this.statusNodeType = null; // if this is a temp. node to display the status of its parent
248
+ this._isLoading = false; // if this node itself is loading
249
+ this._error = null; // {message: '...'} if a load error occured
232
250
  this.data = {};
233
251
 
234
252
  // TODO: merge this code with node.toDict()
@@ -251,8 +269,14 @@ function FancytreeNode(parent, obj){
251
269
 
252
270
  // Fix missing key
253
271
  if( this.key == null ){ // test for null OR undefined
254
- this.key = "_" + (FT._nextNodeKey++);
272
+ if( this.tree.options.defaultKey ) {
273
+ this.key = this.tree.options.defaultKey(this);
274
+ _assert(this.key, "defaultKey() must return a unique key");
275
+ } else {
276
+ this.key = "_" + (FT._nextNodeKey++);
277
+ }
255
278
  }
279
+
256
280
  // Fix tree.activeNode
257
281
  // TODO: not elegant: we use obj.active as marker to set tree.activeNode
258
282
  // when loading from a dictionary.
@@ -260,6 +284,9 @@ function FancytreeNode(parent, obj){
260
284
  _assert(this.tree.activeNode === null, "only one active node allowed");
261
285
  this.tree.activeNode = this;
262
286
  }
287
+ if( obj.selected ){ // #186
288
+ this.tree.lastSelectedNode = this;
289
+ }
263
290
  // TODO: handle obj.focus = true
264
291
  // Create child nodes
265
292
  this.children = null;
@@ -267,6 +294,10 @@ function FancytreeNode(parent, obj){
267
294
  if(cl && cl.length){
268
295
  this._setChildren(cl);
269
296
  }
297
+ // Add to key/ref map (except for root node)
298
+ // if( parent ) {
299
+ this.tree._callHook("treeRegisterNode", this.tree, true, this);
300
+ // }
270
301
  }
271
302
 
272
303
 
@@ -368,6 +399,17 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
368
399
  }
369
400
  _assert(false, "Invalid mode: " + mode);
370
401
  },
402
+ /**
403
+ * Append new node after this.
404
+ *
405
+ * This a convenience function that calls addNode(node, 'after')
406
+ *
407
+ * @param {NodeData} node node definition
408
+ * @returns {FancytreeNode} new node
409
+ */
410
+ appendSibling: function(node){
411
+ return this.addNode(node, "after");
412
+ },
371
413
  /**
372
414
  * Modify existing child nodes.
373
415
  *
@@ -417,7 +459,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
417
459
  }
418
460
  return promise;
419
461
  },
420
- /**
462
+ /** Collapse all sibling nodes.
421
463
  * @returns {$.Promise}
422
464
  */
423
465
  collapseSiblings: function() {
@@ -462,12 +504,12 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
462
504
  consoleApply("debug", arguments);
463
505
  }
464
506
  },
465
- /** Remove all children of a lazy node and collapse.*/
507
+ /** Deprecated.
508
+ * @deprecated since 2014-02-16. Use resetLazy() instead.
509
+ */
466
510
  discard: function(){
467
- if(this.lazy && $.isArray(this.children)){
468
- this.removeChildren();
469
- return this.setExpanded(false);
470
- }
511
+ this.warn("FancytreeNode.discard() is deprecated since 2014-02-16. Use .resetLazy() instead.");
512
+ return this.resetLazy();
471
513
  },
472
514
  // TODO: expand(flag)
473
515
  /**Find all nodes that contain `match` in the title.
@@ -529,7 +571,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
529
571
  default:
530
572
  _assert(false, "invalid state: " + state);
531
573
  }
532
- this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed);
574
+ // this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed);
533
575
  if( changed ){
534
576
  this.renderStatus();
535
577
  }
@@ -658,23 +700,31 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
658
700
  this.addChild(children);
659
701
  */
660
702
  },
661
- /** @returns {FancytreeNode[] | undefined} list of child nodes (undefined for unexpanded lazy nodes).*/
703
+ /** Return the list of child nodes (undefined for unexpanded lazy nodes).
704
+ * @returns {FancytreeNode[] | undefined}
705
+ */
662
706
  getChildren: function() {
663
707
  if(this.hasChildren() === undefined){ // TODO: only required for lazy nodes?
664
708
  return undefined; // Lazy node: unloaded, currently loading, or load error
665
709
  }
666
710
  return this.children;
667
711
  },
668
- /** @returns {FancytreeNode | null}*/
712
+ /** Return the first child node or null.
713
+ * @returns {FancytreeNode | null}
714
+ */
669
715
  getFirstChild: function() {
670
716
  return this.children ? this.children[0] : null;
671
717
  },
672
- /** @returns {int} 0-based child index.*/
718
+ /** Return the 0-based child index.
719
+ * @returns {int}
720
+ */
673
721
  getIndex: function() {
674
722
  // return this.parent.children.indexOf(this);
675
723
  return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7
676
724
  },
677
- /**@returns {string} hierarchical child index (1-based: '3.2.4').*/
725
+ /** Return the hierarchical child index (1-based, e.g. '3.2.4').
726
+ * @returns {string}
727
+ */
678
728
  getIndexHier: function(separator) {
679
729
  separator = separator || ".";
680
730
  var res = [];
@@ -683,9 +733,9 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
683
733
  });
684
734
  return res.join(separator);
685
735
  },
686
- /**
736
+ /** Return the parent keys separated by options.keyPathSeparator, e.g. "id_1/id_17/id_32".
687
737
  * @param {boolean} [excludeSelf=false]
688
- * @returns {string} parent keys separated by options.keyPathSeparator
738
+ * @returns {string}
689
739
  */
690
740
  getKeyPath: function(excludeSelf) {
691
741
  var path = [],
@@ -697,11 +747,15 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
697
747
  }, !excludeSelf);
698
748
  return sep + path.join(sep);
699
749
  },
700
- /**@returns {FancytreeNode | null} last child of this node.*/
750
+ /** Return the last child of this node or null.
751
+ * @returns {FancytreeNode | null}
752
+ */
701
753
  getLastChild: function() {
702
754
  return this.children ? this.children[this.children.length - 1] : null;
703
755
  },
704
- /** @returns {int} node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, .... */
756
+ /** Return node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, ... .
757
+ * @returns {int}
758
+ */
705
759
  getLevel: function() {
706
760
  var level = 0,
707
761
  dtn = this.parent;
@@ -711,7 +765,9 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
711
765
  }
712
766
  return level;
713
767
  },
714
- /** @returns {FancytreeNode | null} */
768
+ /** Return the successor node (under the same parent) or null.
769
+ * @returns {FancytreeNode | null}
770
+ */
715
771
  getNextSibling: function() {
716
772
  // TODO: use indexOf, if available: (not in IE6)
717
773
  if( this.parent ){
@@ -726,14 +782,16 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
726
782
  }
727
783
  return null;
728
784
  },
729
- /** @returns {FancytreeNode | null} returns null for the system root node*/
785
+ /** Return the parent node (null for the system root node).
786
+ * @returns {FancytreeNode | null}
787
+ */
730
788
  getParent: function() {
731
789
  // TODO: return null for top-level nodes?
732
790
  return this.parent;
733
791
  },
734
- /**
735
- * @param {boolean} [includeRoot=false]
736
- * @param {boolean} [includeSelf=false]
792
+ /** Return an array of all parent nodes (top-down).
793
+ * @param {boolean} [includeRoot=false] Include the invisible system root node.
794
+ * @param {boolean} [includeSelf=false] Include the node itself.
737
795
  * @returns {FancytreeNode[]}
738
796
  */
739
797
  getParentList: function(includeRoot, includeSelf) {
@@ -747,7 +805,9 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
747
805
  }
748
806
  return l;
749
807
  },
750
- /** @returns {FancytreeNode | null} */
808
+ /** Return the predecessor node (under the same parent) or null.
809
+ * @returns {FancytreeNode | null}
810
+ */
751
811
  getPrevSibling: function() {
752
812
  if( this.parent ){
753
813
  var i, l,
@@ -761,7 +821,9 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
761
821
  }
762
822
  return null;
763
823
  },
764
- /** @returns {boolean | undefined} Check if node has children (returns undefined, if not sure). */
824
+ /** Return true if node has children. Return undefined if not sure, i.e. the node is lazy and not yet loaded).
825
+ * @returns {boolean | undefined}
826
+ */
765
827
  hasChildren: function() {
766
828
  if(this.lazy){
767
829
  if(this.children == null ){
@@ -770,7 +832,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
770
832
  }else if(this.children.length === 0){
771
833
  // Loaded, but response was empty
772
834
  return false;
773
- }else if(this.children.length === 1 && this.children[0].isStatusNode ){
835
+ }else if(this.children.length === 1 && this.children[0].isStatusNode() ){
774
836
  // Currently loading or load error
775
837
  return undefined;
776
838
  }
@@ -778,24 +840,28 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
778
840
  }
779
841
  return !!this.children;
780
842
  },
781
- /**@returns {boolean} true, if node has keyboard focus*/
843
+ /** Return true if node has keyboard focus.
844
+ * @returns {boolean}
845
+ */
782
846
  hasFocus: function() {
783
847
  return (this.tree.hasFocus() && this.tree.focusNode === this);
784
848
  },
785
- /**@returns {boolean} true, if node is active*/
849
+ /** Return true if node is active (see also FancytreeNode#isSelected).
850
+ * @returns {boolean}
851
+ */
786
852
  isActive: function() {
787
853
  return (this.tree.activeNode === this);
788
854
  },
789
- /**
855
+ /** Return true if node is a direct child of otherNode.
790
856
  * @param {FancytreeNode} otherNode
791
- * @returns {boolean} true, if node is a direct child of otherNode
857
+ * @returns {boolean}
792
858
  */
793
859
  isChildOf: function(otherNode) {
794
860
  return (this.parent && this.parent === otherNode);
795
861
  },
796
- /**
862
+ /** Return true, if node is a direct or indirect sub node of otherNode.
797
863
  * @param {FancytreeNode} otherNode
798
- * @returns {boolean} true, if node is a sub node of otherNode
864
+ * @returns {boolean}
799
865
  */
800
866
  isDescendantOf: function(otherNode) {
801
867
  if(!otherNode || otherNode.tree !== this.tree){
@@ -810,45 +876,79 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
810
876
  }
811
877
  return false;
812
878
  },
813
- /** @returns {boolean} true, if node is expanded*/
879
+ /** Return true if node is expanded.
880
+ * @returns {boolean}
881
+ */
814
882
  isExpanded: function() {
815
883
  return !!this.expanded;
816
884
  },
817
- /** @returns {boolean}*/
885
+ /** Return true if node is the first node of its parent's children.
886
+ * @returns {boolean}
887
+ */
818
888
  isFirstSibling: function() {
819
889
  var p = this.parent;
820
890
  return !p || p.children[0] === this;
821
891
  },
822
- /** @returns {boolean}*/
892
+ /** Return true if node is a folder, i.e. has the node.folder attribute set.
893
+ * @returns {boolean}
894
+ */
823
895
  isFolder: function() {
824
896
  return !!this.folder;
825
897
  },
826
- /** @returns {boolean}*/
898
+ /** Return true if node is the last node of its parent's children.
899
+ * @returns {boolean}
900
+ */
827
901
  isLastSibling: function() {
828
902
  var p = this.parent;
829
903
  return !p || p.children[p.children.length-1] === this;
830
904
  },
831
- /** @returns {boolean} true, if node is lazy (even if data was already loaded)*/
905
+ /** Return true if node is lazy (even if data was already loaded)
906
+ * @returns {boolean}
907
+ */
832
908
  isLazy: function() {
833
909
  return !!this.lazy;
834
910
  },
835
- /** @returns {boolean} true, if children are currently beeing loaded*/
911
+ /** Return true if node is lazy and loaded. For non-lazy nodes always return true.
912
+ * @returns {boolean}
913
+ */
914
+ isLoaded: function() {
915
+ return !this.lazy || this.hasChildren() !== undefined; // Also checks if the only child is a status node
916
+ },
917
+ /** Return true if children are currently beeing loaded, i.e. a Ajax request is pending.
918
+ * @returns {boolean}
919
+ */
836
920
  isLoading: function() {
837
- _raiseNotImplemented(); // TODO: implement
921
+ return !!this._isLoading;
838
922
  },
839
- /**@returns {boolean} true, if node is the (invisible) system root node*/
923
+ /** Return true if this is the (invisible) system root node.
924
+ * @returns {boolean}
925
+ */
840
926
  isRoot: function() {
841
927
  return (this.tree.rootNode === this);
842
928
  },
843
- /** @returns {boolean} true, if node is selected (e.g. has a checkmark set)*/
929
+ /** Return true if node is selected, i.e. has a checkmark set (see also FancytreeNode#isActive).
930
+ * @returns {boolean}
931
+ */
844
932
  isSelected: function() {
845
933
  return !!this.selected;
846
934
  },
847
- // TODO: use _isStatusNode as class attribute name
848
- // isStatusNode: function() {
849
- // return (this.data.isStatusNode === true);
850
- // },
851
- /** Return true, if all parents are expanded. */
935
+ /** Return true if this node is a temporarily generated system node like
936
+ * 'loading', or 'error' (node.statusNodeType contains the type).
937
+ * @returns {boolean}
938
+ */
939
+ isStatusNode: function() {
940
+ return !!this.statusNodeType;
941
+ },
942
+ /** Return true if node is lazy and not yet loaded. For non-lazy nodes always return false.
943
+ * @returns {boolean}
944
+ */
945
+ isUndefined: function() {
946
+ return this.hasChildren() === undefined; // also checks if the only child is a status node
947
+ },
948
+ /** Return true if all parent nodes are expanded. Note: this does not check
949
+ * whether the node is scrolled into the visible part of the screen.
950
+ * @returns {boolean}
951
+ */
852
952
  isVisible: function() {
853
953
  var i, l,
854
954
  parents = this.getParentList(false, false);
@@ -858,28 +958,87 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
858
958
  }
859
959
  return true;
860
960
  },
861
- /** Expand all parents and optionally scroll into visible area as neccessary (async).
862
- *
961
+ /** Deprecated.
962
+ * @deprecated since 2014-02-16: use load() instead.
863
963
  */
864
- makeVisible: function() {
865
- // TODO: implement scolling (http://www.w3.org/TR/wai-aria-practices/#visualfocus)
866
- // TODO: return $.promise
867
- var i, l,
868
- parents = this.getParentList(false, false);
964
+ lazyLoad: function(discard) {
965
+ this.warn("FancytreeNode.lazyLoad() is deprecated since 2014-02-16. Use .load() instead.");
966
+ return this.load(discard);
967
+ },
968
+ /**
969
+ * Load all children of a lazy node.
970
+ * @param {boolean} [forceReload=false] Pass true to discard any existing nodes before.
971
+ * @returns {$.Promise}
972
+ */
973
+ load: function(forceReload) {
974
+ var res, source,
975
+ that = this;
869
976
 
870
- for(i=0, l=parents.length; i<l; i++){
871
- parents[i].setExpanded(true);
977
+ _assert( this.isLazy(), "load() requires a lazy node" );
978
+ _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
979
+
980
+ if( this.isLoaded() ){
981
+ this.resetLazy(); // also collapses
872
982
  }
983
+ // This method is also called by setExpanded() and loadKeyPath(), so we
984
+ // have to avoid recursion.
985
+ source = this.tree._triggerNodeEvent("lazyLoad", this);
986
+ if( source === false ) { // #69
987
+ return _getResolvedPromise(this);
988
+ }
989
+ _assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
990
+ res = this.tree._callHook("nodeLoadChildren", this, source);
991
+ if( this.expanded ) {
992
+ res.always(function(){
993
+ that.render();
994
+ });
995
+ }
996
+ return res;
997
+ },
998
+ /** Expand all parents and optionally scroll into visible area as neccessary.
999
+ * Promise is resolved, when lazy loading and animations are done.
1000
+ * @param {object} [opts] passed to `setExpanded()`.
1001
+ * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
1002
+ * @returns {$.Promise}
1003
+ */
1004
+ makeVisible: function(opts) {
1005
+ var i,
1006
+ that = this,
1007
+ deferreds = [],
1008
+ dfd = new $.Deferred(),
1009
+ parents = this.getParentList(false, false),
1010
+ len = parents.length,
1011
+ effects = !(opts && opts.noAnimation === true),
1012
+ scroll = !(opts && opts.scrollIntoView === false);
1013
+
1014
+ // Expand bottom-up, so only the top node is animated
1015
+ for(i = len - 1; i >= 0; i--){
1016
+ // that.debug("pushexpand" + parents[i]);
1017
+ deferreds.push(parents[i].setExpanded(true, opts));
1018
+ }
1019
+ $.when.apply($, deferreds).done(function(){
1020
+ // All expands have finished
1021
+ // that.debug("expand DONE", scroll);
1022
+ if( scroll ){
1023
+ that.scrollIntoView(effects).done(function(){
1024
+ // that.debug("scroll DONE");
1025
+ dfd.resolve();
1026
+ });
1027
+ } else {
1028
+ dfd.resolve();
1029
+ }
1030
+ });
1031
+ return dfd.promise();
873
1032
  },
874
1033
  /** Move this node to targetNode.
875
1034
  * @param {FancytreeNode} targetNode
876
- * @param {string} mode
1035
+ * @param {string} mode <pre>
877
1036
  * 'child': append this node as last child of targetNode.
878
1037
  * This is the default. To be compatble with the D'n'd
879
1038
  * hitMode, we also accept 'over'.
880
1039
  * 'before': add this node as sibling before targetNode.
881
- * 'after': add this node as sibling after targetNode.
882
- * @param [map] optional callback(FancytreeNode) to allow modifcations
1040
+ * 'after': add this node as sibling after targetNode.</pre>
1041
+ * @param {function} [map] optional callback(FancytreeNode) to allow modifcations
883
1042
  */
884
1043
  moveTo: function(targetNode, mode, map) {
885
1044
  if(mode === undefined || mode === "over"){
@@ -993,8 +1152,8 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
993
1152
  }
994
1153
 
995
1154
  // In multi-hier mode, update the parents selection state
996
- // issue #82: only if not initializing, because the children may not exist yet
997
- // if( !ftnode.data.isStatusNode && opts.selectMode==3 && !isInitializing )
1155
+ // DT issue #82: only if not initializing, because the children may not exist yet
1156
+ // if( !ftnode.data.isStatusNode() && opts.selectMode==3 && !isInitializing )
998
1157
  // ftnode._fixSelectionState();
999
1158
 
1000
1159
  // In multi-hier mode, update the parents selection state
@@ -1091,52 +1250,74 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1091
1250
  }
1092
1251
  },
1093
1252
  /**
1094
- * Discard and reload all children of a lazy node.
1095
- * @param {boolean} [discard=false]
1096
- * @returns $.Promise
1253
+ * Remove this node (not allowed for system root).
1097
1254
  */
1098
- lazyLoad: function(discard) {
1099
- if(discard || this.hasChildren() === undefined){
1100
- this.discard();
1101
- }
1102
- _assert(!$.isArray(this.children));
1103
- var source = this.tree._triggerNodeEvent("lazyload", this);
1104
- _assert(typeof source !== "boolean", "lazyload event must return source in data.result");
1105
- return this.tree._callHook("nodeLoadChildren", this, source);
1255
+ remove: function() {
1256
+ return this.parent.removeChild(this);
1257
+ },
1258
+ /**
1259
+ * Remove childNode from list of direct children.
1260
+ * @param {FancytreeNode} childNode
1261
+ */
1262
+ removeChild: function(childNode) {
1263
+ return this.tree._callHook("nodeRemoveChild", this, childNode);
1106
1264
  },
1107
1265
  /**
1108
- * @see Fancytree_Hooks#nodeRender
1266
+ * Remove all child nodes and descendents. This converts the node into a leaf.<br>
1267
+ * If this was a lazy node, it is still considered 'loaded'; call node.resetLazy()
1268
+ * in order to trigger lazyLoad on next expand.
1269
+ */
1270
+ removeChildren: function() {
1271
+ return this.tree._callHook("nodeRemoveChildren", this);
1272
+ },
1273
+ /**
1274
+ * This method renders and updates all HTML markup that is required
1275
+ * to display this node in its current state.<br>
1276
+ * Note:
1277
+ * <ul>
1278
+ * <li>It should only be neccessary to call this method after the node object
1279
+ * was modified by direct access to its properties, because the common
1280
+ * API methods (node.setTitle(), moveTo(), addChildren(), remove(), ...)
1281
+ * already handle this.
1282
+ * <li> {@link FancytreeNode#renderTitle} and {@link FancytreeNode#renderStatus}
1283
+ * are implied. If changes are more local, calling only renderTitle() or
1284
+ * renderStatus() may be sufficient and faster.
1285
+ * <li>If a node was created/removed, node.render() must be called <i>on the parent</i>.
1286
+ * </ul>
1287
+ *
1288
+ * @param {boolean} [force=false] re-render, even if html markup was already created
1289
+ * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
1109
1290
  */
1110
1291
  render: function(force, deep) {
1111
1292
  return this.tree._callHook("nodeRender", this, force, deep);
1112
1293
  },
1113
- /**
1294
+ /** Create HTML markup for the node's outer <span> (expander, checkbox, icon, and title).
1114
1295
  * @see Fancytree_Hooks#nodeRenderTitle
1115
1296
  */
1116
1297
  renderTitle: function() {
1117
1298
  return this.tree._callHook("nodeRenderTitle", this);
1118
1299
  },
1119
- /**
1300
+ /** Update element's CSS classes according to node state.
1120
1301
  * @see Fancytree_Hooks#nodeRenderStatus
1121
1302
  */
1122
1303
  renderStatus: function() {
1123
1304
  return this.tree._callHook("nodeRenderStatus", this);
1124
1305
  },
1125
- /** Remove this node (not allowed for root).*/
1126
- remove: function() {
1127
- return this.parent.removeChild(this);
1128
- },
1129
- /**Remove childNode from list of direct children.*/
1130
- removeChild: function(childNode) {
1131
- return this.tree._callHook("nodeRemoveChild", this, childNode);
1132
- },
1133
- /**Remove all child nodes (and descendents).*/
1134
- removeChildren: function() {
1135
- return this.tree._callHook("nodeRemoveChildren", this);
1306
+ /**
1307
+ * Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
1308
+ * event is triggered on next expand.
1309
+ */
1310
+ resetLazy: function() {
1311
+ this.removeChildren();
1312
+ this.expanded = false;
1313
+ this.lazy = true;
1314
+ this.children = undefined;
1315
+ this.renderStatus();
1136
1316
  },
1137
- // TODO: resetLazy()
1138
1317
  /** Schedule activity for delayed execution (cancel any pending request).
1139
1318
  * scheduleAction('cancel') will only cancel a pending request (if any).
1319
+ * @param {string} mode
1320
+ * @param {number} ms
1140
1321
  */
1141
1322
  scheduleAction: function(mode, ms) {
1142
1323
  if( this.tree.timer ) {
@@ -1171,12 +1352,13 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1171
1352
  * @param {boolean | PlainObject} [effects=false] animation options.
1172
1353
  * @param {FancytreeNode} [topNode=null] this node will remain visible in
1173
1354
  * any case, even if `this` is outside the scroll pane.
1174
- * @returns $.Promise
1355
+ * @returns {$.Promise}
1175
1356
  */
1176
1357
  scrollIntoView: function(effects, topNode) {
1177
1358
  effects = (effects === true) ? {duration: 200, queue: false} : effects;
1178
1359
  var topNodeY,
1179
1360
  dfd = new $.Deferred(),
1361
+ that = this,
1180
1362
  nodeY = $(this.span).position().top,
1181
1363
  nodeHeight = $(this.span).height(),
1182
1364
  $container = this.tree.$container,
@@ -1210,7 +1392,12 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1210
1392
  if(effects){
1211
1393
  // TODO: resolve dfd after animation
1212
1394
  // var that = this;
1213
- $container.animate({scrollTop: newScrollTop}, effects);
1395
+ effects.complete = function(){
1396
+ dfd.resolveWith(that);
1397
+ };
1398
+ $container.animate({
1399
+ scrollTop: newScrollTop
1400
+ }, effects);
1214
1401
  }else{
1215
1402
  $container[0].scrollTop = newScrollTop;
1216
1403
  dfd.resolveWith(this);
@@ -1245,10 +1432,10 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1245
1432
  setActive: function(flag, opts){
1246
1433
  return this.tree._callHook("nodeSetActive", this, flag, opts);
1247
1434
  },
1248
- /**Expand or collapse this node.
1435
+ /**Expand or collapse this node. Promise is resolved, when lazy loading and animations are done.
1249
1436
  * @param {boolean} [flag=true] pass false to collapse
1250
1437
  * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
1251
- * @returns {$.Promise} resolved, when lazy loading and animations are done
1438
+ * @returns {$.Promise}
1252
1439
  */
1253
1440
  setExpanded: function(flag, opts){
1254
1441
  return this.tree._callHook("nodeSetExpanded", this, flag, opts);
@@ -1261,7 +1448,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1261
1448
  return this.tree._callHook("nodeSetFocus", this, flag);
1262
1449
  },
1263
1450
  // TODO: setLazyNodeStatus
1264
- /**Select this node.
1451
+ /**Select this node, i.e. check the checkbox.
1265
1452
  * @param {boolean} [flag=true] pass false to deselect
1266
1453
  */
1267
1454
  setSelected: function(flag){
@@ -1334,7 +1521,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1334
1521
  dict.children = [];
1335
1522
  for(i=0, l=this.children.length; i<l; i++ ){
1336
1523
  node = this.children[i];
1337
- if( !node.isStatusNode ){
1524
+ if( !node.isStatusNode() ){
1338
1525
  dict.children.push(node.toDict(true, callback));
1339
1526
  }
1340
1527
  }
@@ -1355,12 +1542,14 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1355
1542
  toString: function() {
1356
1543
  return "<FancytreeNode(#" + this.key + ", '" + this.title + "')>";
1357
1544
  },
1358
- /** Call fn(node) for all child nodes. Stop iteration, if fn() returns false.
1359
- * Skip current branch, if fn() returns 'skip'.
1545
+ /** Call fn(node) for all child nodes.<br>
1546
+ * Stop iteration, if fn() returns false. Skip current branch, if fn() returns "skip".<br>
1547
+ * Return false if iteration was stopped.
1548
+ *
1360
1549
  * @param {function} fn the callback function.
1361
1550
  * Return false to stop iteration, return "skip" to skip this node and children only.
1362
1551
  * @param {boolean} [includeSelf=false]
1363
- * @returns {boolean} false, if the iterator was stopped.
1552
+ * @returns {boolean}
1364
1553
  */
1365
1554
  visit: function(fn, includeSelf) {
1366
1555
  var i, l,
@@ -1383,10 +1572,13 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1383
1572
  }
1384
1573
  return res;
1385
1574
  },
1386
- /**
1575
+ /** Call fn(node) for all parent nodes, bottom-up, including invisible system root.<br>
1576
+ * Stop iteration, if fn() returns false.<br>
1577
+ * Return false if iteration was stopped.
1387
1578
  *
1388
- * @param fn
1389
- * @param includeSelf
1579
+ * @param {function} fn the callback function.
1580
+ * Return false to stop iteration, return "skip" to skip this node and children only.
1581
+ * @param {boolean} [includeSelf=false]
1390
1582
  * @returns {boolean}
1391
1583
  */
1392
1584
  visitParents: function(fn, includeSelf) {
@@ -1421,7 +1613,7 @@ FancytreeNode.prototype = /** @lends FancytreeNode# */{
1421
1613
  * Construct a new tree object.
1422
1614
  *
1423
1615
  * @class Fancytree
1424
- * @classdesc A Fancytree is the controller behind a fancytree.
1616
+ * @classdesc The controller behind a fancytree.
1425
1617
  * This class also contains 'hook methods': see {@link Fancytree_Hooks}.
1426
1618
  *
1427
1619
  * @param {Widget} widget
@@ -1446,15 +1638,24 @@ function Fancytree(widget) {
1446
1638
  this.widget = widget;
1447
1639
  this.$div = widget.element;
1448
1640
  this.options = widget.options;
1641
+ if( this.options && $.isFunction(this.options.lazyload) ) {
1642
+ if( ! $.isFunction(this.options.lazyLoad ) ) {
1643
+ this.options.lazyLoad = function() {
1644
+ FT.warn("The 'lazyload' event is deprecated since 2014-02-25. Use 'lazyLoad' (with uppercase L) instead.");
1645
+ widget.options.lazyload.apply(this, arguments);
1646
+ };
1647
+ }
1648
+ }
1449
1649
  this.ext = {}; // Active extension instances
1450
- this.data = {};
1650
+ // allow to init tree.data.foo from <div data-foo=''>
1651
+ this.data = _getElementDataAsDict(this.$div);
1451
1652
  this._id = $.ui.fancytree._nextId++;
1452
1653
  this._ns = ".fancytree-" + this._id; // append for namespaced events
1453
1654
  this.activeNode = null;
1454
1655
  this.focusNode = null;
1455
1656
  this._hasFocus = null;
1456
1657
  this.lastSelectedNode = null;
1457
- this.systemFocusElement = null,
1658
+ this.systemFocusElement = null;
1458
1659
 
1459
1660
  this.statusClassPropName = "span";
1460
1661
  this.ariaPropName = "li";
@@ -1695,7 +1896,7 @@ Fancytree.prototype = /** @lends Fancytree# */{
1695
1896
  }
1696
1897
  },
1697
1898
  /**
1698
- * Return the currently active FancytreeNode or null.
1899
+ * Return the currently active node or null.
1699
1900
  * @returns {FancytreeNode}
1700
1901
  */
1701
1902
  getActiveNode: function() {
@@ -1862,13 +2063,13 @@ Fancytree.prototype = /** @lends Fancytree# */{
1862
2063
  // Avoid jshint warning 'Don't make functions within a loop.':
1863
2064
  function __lazyload(key, node, dfd){
1864
2065
  callback.call(self, node, "loading");
1865
- node.lazyLoad().done(function(){
1866
- self.loadKeyPath.call(self, loadMap[key], callback, node).always(_makeResolveFunc(dfd, self));
1867
- }).fail(function(errMsg){
1868
- self.warn("loadKeyPath: error loading: " + key + " (parent: " + root + ")");
1869
- callback.call(self, node, "error");
1870
- dfd.reject();
1871
- });
2066
+ node.load().done(function(){
2067
+ self.loadKeyPath.call(self, loadMap[key], callback, node).always(_makeResolveFunc(dfd, self));
2068
+ }).fail(function(errMsg){
2069
+ self.warn("loadKeyPath: error loading: " + key + " (parent: " + root + ")");
2070
+ callback.call(self, node, "error");
2071
+ dfd.reject();
2072
+ });
1872
2073
  }
1873
2074
  for(key in loadMap){
1874
2075
  node = root._findDirectChild(key);
@@ -1983,8 +2184,10 @@ Fancytree.prototype = /** @lends Fancytree# */{
1983
2184
  $.extend(Fancytree.prototype,
1984
2185
  /** @lends Fancytree_Hooks# */
1985
2186
  {
1986
-
1987
- /** _Default handling for mouse click events. */
2187
+ /** Default handling for mouse click events.
2188
+ *
2189
+ * @param {EventData} ctx
2190
+ */
1988
2191
  nodeClick: function(ctx) {
1989
2192
  // this.tree.logDebug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which);
1990
2193
  var activate, expand,
@@ -1997,11 +2200,11 @@ $.extend(Fancytree.prototype,
1997
2200
  if( targetType === "expander" ) {
1998
2201
  // Clicking the expander icon always expands/collapses
1999
2202
  this._callHook("nodeToggleExpanded", ctx);
2000
- // this._callHook("nodeSetFocus", ctx, true); // issue 95
2203
+ // this._callHook("nodeSetFocus", ctx, true); // DT issue 95
2001
2204
  } else if( targetType === "checkbox" ) {
2002
2205
  // Clicking the checkbox always (de)selects
2003
2206
  this._callHook("nodeToggleSelected", ctx);
2004
- this._callHook("nodeSetFocus", ctx, true); // issue 95
2207
+ this._callHook("nodeSetFocus", ctx, true); // DT issue 95
2005
2208
  } else {
2006
2209
  // Honor `clickFolderMode` for
2007
2210
  expand = false;
@@ -2037,6 +2240,11 @@ $.extend(Fancytree.prototype,
2037
2240
  }
2038
2241
  // TODO: return promise?
2039
2242
  },
2243
+ /** Collapse all other children of same parent.
2244
+ *
2245
+ * @param {EventData} ctx
2246
+ * @param {object} callOpts
2247
+ */
2040
2248
  nodeCollapseSiblings: function(ctx, callOpts) {
2041
2249
  // TODO: return promise?
2042
2250
  var ac, i, l,
@@ -2051,6 +2259,9 @@ $.extend(Fancytree.prototype,
2051
2259
  }
2052
2260
  }
2053
2261
  },
2262
+ /** Default handling for mouse douleclick events.
2263
+ * @param {EventData} ctx
2264
+ */
2054
2265
  nodeDblclick: function(ctx) {
2055
2266
  // TODO: return promise?
2056
2267
  if( ctx.targetType === "title" && ctx.options.clickFolderMode === 4) {
@@ -2066,6 +2277,7 @@ $.extend(Fancytree.prototype,
2066
2277
  /** Default handling for mouse keydown events.
2067
2278
  *
2068
2279
  * NOTE: this may be called with node == null if tree (but no node) has focus.
2280
+ * @param {EventData} ctx
2069
2281
  */
2070
2282
  nodeKeydown: function(ctx) {
2071
2283
  // TODO: return promise?
@@ -2128,17 +2340,12 @@ $.extend(Fancytree.prototype,
2128
2340
  // var event = ctx.originalEvent;
2129
2341
  // },
2130
2342
 
2131
- // /** Trigger lazyload event (async). */
2343
+ // /** Trigger lazyLoad event (async). */
2132
2344
  // nodeLazyLoad: function(ctx) {
2133
2345
  // var node = ctx.node;
2134
2346
  // if(this._triggerNodeEvent())
2135
2347
  // },
2136
- /** Load children (async).
2137
- * source may be
2138
- * - an array of children
2139
- * - a node object
2140
- * - an Ajax options object
2141
- * - an Ajax.promise
2348
+ /** Load child nodes (async).
2142
2349
  *
2143
2350
  * @param {EventData} ctx
2144
2351
  * @param {object[]|object|string|$.Promise|function} source
@@ -2178,9 +2385,12 @@ $.extend(Fancytree.prototype,
2178
2385
  }
2179
2386
 
2180
2387
  // TODO: change 'pipe' to 'then' for jQuery 1.8
2388
+ // $.pipe returns a new Promise with filtered results
2181
2389
  source = source.pipe(function (data, textStatus, jqXHR) {
2182
2390
  var res;
2183
- if(typeof data === "string"){ $.error("Ajax request returned a string (did you get the JSON dataType wrong?)."); }
2391
+ if(typeof data === "string"){
2392
+ $.error("Ajax request returned a string (did you get the JSON dataType wrong?).");
2393
+ }
2184
2394
  // postProcess is similar to the standard dataFilter hook,
2185
2395
  // but it is also called for JSONP
2186
2396
  if( ctx.options.postProcess ){
@@ -2202,8 +2412,11 @@ $.extend(Fancytree.prototype,
2202
2412
  }
2203
2413
 
2204
2414
  if($.isFunction(source.promise)){
2205
- // `source` is a promise
2415
+ // `source` is a deferred, i.e. ajax request
2416
+ _assert(!node.isLoading());
2417
+ // node._isLoading = true;
2206
2418
  tree.nodeSetStatus(ctx, "loading");
2419
+
2207
2420
  source.done(function () {
2208
2421
  tree.nodeSetStatus(ctx, "ok");
2209
2422
  }).fail(function(error){
@@ -2222,13 +2435,13 @@ $.extend(Fancytree.prototype,
2222
2435
  tree.nodeSetStatus(ctx, "error", ctxErr.message, ctxErr.details);
2223
2436
  });
2224
2437
  }
2225
-
2438
+ // $.when(source) resolves also for non-deferreds
2226
2439
  return $.when(source).done(function(children){
2227
2440
  var metaData;
2228
2441
 
2229
2442
  if( $.isPlainObject(children) ){
2230
2443
  // We got {foo: 'abc', children: [...]}
2231
- // Copy extra properties to tree.data.
2444
+ // Copy extra properties to tree.data.foo
2232
2445
  _assert($.isArray(children.children), "source must contain (or be) an array of children");
2233
2446
  _assert(node.isRoot(), "source may only be an object for root nodes");
2234
2447
  metaData = children;
@@ -2238,34 +2451,18 @@ $.extend(Fancytree.prototype,
2238
2451
  }
2239
2452
  _assert($.isArray(children), "expected array of children");
2240
2453
  node._setChildren(children);
2241
- if(node.parent){
2242
- // trigger fancytreeloadchildren (except for tree-reload)
2243
- tree._triggerNodeEvent("loadChildren", node);
2244
- }
2454
+ // trigger fancytreeloadchildren
2455
+ // if( node.parent ) {
2456
+ tree._triggerNodeEvent("loadChildren", node);
2457
+ // }
2458
+ // }).always(function(){
2459
+ // node._isLoading = false;
2245
2460
  });
2246
2461
  },
2247
- // isVisible: function() {
2248
- // // Return true, if all parents are expanded.
2249
- // var parents = ctx.node.getParentList(false, false);
2250
- // for(var i=0, l=parents.length; i<l; i++){
2251
- // if( ! parents[i].expanded ){ return false; }
2252
- // }
2253
- // return true;
2254
- // },
2255
- /** Expand all keys that */
2462
+ /** [Not Implemented] */
2256
2463
  nodeLoadKeyPath: function(ctx, keyPathList) {
2257
2464
  // TODO: implement and improve
2258
- // http://code.google.com/p/fancytree/issues/detail?id=222
2259
- },
2260
- /** Expand all parents.*/
2261
- nodeMakeVisible: function(ctx) {
2262
- // TODO: also scroll as neccessary: http://stackoverflow.com/questions/8938352/fancytree-how-to-scroll-to-active-node
2263
- // Do we need an extra parameter?
2264
- var i, l,
2265
- parents = ctx.node.getParentList(false, false);
2266
- for(i=0, l=parents.length; i<l; i++){
2267
- parents[i].setExpanded(true);
2268
- }
2465
+ // http://code.google.com/p/dynatree/issues/detail?id=222
2269
2466
  },
2270
2467
  /**
2271
2468
  * Remove a single direct child of ctx.node.
@@ -2279,7 +2476,7 @@ $.extend(Fancytree.prototype,
2279
2476
  subCtx = $.extend({}, ctx, {node: childNode}),
2280
2477
  children = node.children;
2281
2478
 
2282
- FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
2479
+ // FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
2283
2480
 
2284
2481
  if( children.length === 1 ) {
2285
2482
  _assert(childNode === children[0]);
@@ -2300,6 +2497,7 @@ $.extend(Fancytree.prototype,
2300
2497
  childNode.visit(function(n){
2301
2498
  n.parent = null;
2302
2499
  }, true);
2500
+ this._callHook("treeRegisterNode", this, false, childNode);
2303
2501
  if ( opts.removeNode ){
2304
2502
  opts.removeNode.call(ctx.tree, {type: "removeNode"}, subCtx);
2305
2503
  }
@@ -2312,7 +2510,7 @@ $.extend(Fancytree.prototype,
2312
2510
  nodeRemoveChildMarkup: function(ctx) {
2313
2511
  var node = ctx.node;
2314
2512
 
2315
- FT.debug("nodeRemoveChildMarkup()", node.toString());
2513
+ // FT.debug("nodeRemoveChildMarkup()", node.toString());
2316
2514
  // TODO: Unlink attr.ftnode to support GC
2317
2515
  if(node.ul){
2318
2516
  if( node.isRoot() ) {
@@ -2331,11 +2529,12 @@ $.extend(Fancytree.prototype,
2331
2529
  */
2332
2530
  nodeRemoveChildren: function(ctx) {
2333
2531
  var subCtx,
2532
+ tree = ctx.tree,
2334
2533
  node = ctx.node,
2335
2534
  children = node.children,
2336
2535
  opts = ctx.options;
2337
2536
 
2338
- FT.debug("nodeRemoveChildren()", node.toString());
2537
+ // FT.debug("nodeRemoveChildren()", node.toString());
2339
2538
  if(!children){
2340
2539
  return;
2341
2540
  }
@@ -2352,14 +2551,18 @@ $.extend(Fancytree.prototype,
2352
2551
  subCtx = $.extend({}, ctx);
2353
2552
  node.visit(function(n){
2354
2553
  n.parent = null;
2554
+ tree._callHook("treeRegisterNode", tree, false, n);
2355
2555
  if ( opts.removeNode ){
2356
2556
  subCtx.node = n;
2357
2557
  opts.removeNode.call(ctx.tree, {type: "removeNode"}, subCtx);
2358
2558
  }
2359
2559
  });
2360
- // Set to 'undefined' which is interpreted as 'not yet loaded' for lazy nodes
2361
- node.children = undefined;
2362
- // TODO: ? this._isLoading = false;
2560
+ if( node.lazy ){
2561
+ // 'undefined' would be interpreted as 'not yet loaded' for lazy nodes
2562
+ node.children = [];
2563
+ } else{
2564
+ node.children = null;
2565
+ }
2363
2566
  this.nodeRenderStatus(ctx);
2364
2567
  },
2365
2568
  /**Remove HTML markup for ctx.node and all its descendents.
@@ -2367,7 +2570,7 @@ $.extend(Fancytree.prototype,
2367
2570
  */
2368
2571
  nodeRemoveMarkup: function(ctx) {
2369
2572
  var node = ctx.node;
2370
- FT.debug("nodeRemoveMarkup()", node.toString());
2573
+ // FT.debug("nodeRemoveMarkup()", node.toString());
2371
2574
  // TODO: Unlink attr.ftnode to support GC
2372
2575
  if(node.li){
2373
2576
  $(node.li).remove();
@@ -2376,7 +2579,7 @@ $.extend(Fancytree.prototype,
2376
2579
  this.nodeRemoveChildMarkup(ctx);
2377
2580
  },
2378
2581
  /**
2379
- * Create `<li><span>..</span> .. </li>` tags for this node.
2582
+ * Create `&lt;li>&lt;span>..&lt;/span> .. &lt;/li>` tags for this node.
2380
2583
  *
2381
2584
  * This method takes care that all HTML markup is created that is required
2382
2585
  * to display this node in it's current state.
@@ -2386,7 +2589,7 @@ $.extend(Fancytree.prototype,
2386
2589
  * nodeRenderTitle() and nodeRenderStatus() are implied.
2387
2590
  *
2388
2591
  * Note: if a node was created/removed, nodeRender() must be called for the
2389
- * parent!
2592
+ * parent.
2390
2593
  * <code>
2391
2594
  * <li id='KEY' ftnode=NODE>
2392
2595
  * <span class='fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e'>
@@ -2402,10 +2605,10 @@ $.extend(Fancytree.prototype,
2402
2605
  * </li>
2403
2606
  * </code>
2404
2607
  *
2405
- * @param: {EventData} ctx
2406
- * @param: {boolean} [force=false] re-render, even if html markup was already created
2407
- * @param: {boolean} [deep=false] also render all descendants, even if parent is collapsed
2408
- * @param: {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
2608
+ * @param {EventData} ctx
2609
+ * @param {boolean} [force=false] re-render, even if html markup was already created
2610
+ * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
2611
+ * @param {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
2409
2612
  */
2410
2613
  nodeRender: function(ctx, force, deep, collapsed, _recursive) {
2411
2614
  /* This method must take care of all cases where the current data mode
@@ -2426,10 +2629,10 @@ $.extend(Fancytree.prototype,
2426
2629
  parent = node.parent,
2427
2630
  isRootNode = !parent,
2428
2631
  children = node.children;
2429
- FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
2632
+ // FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
2430
2633
 
2431
2634
  if( ! isRootNode && ! parent.ul ) {
2432
- // issue #105: calling node.collapse on a deep, unrendered node
2635
+ // Calling node.collapse on a deep, unrendered node
2433
2636
  return;
2434
2637
  }
2435
2638
  _assert(isRootNode || parent.ul, "parent UL must exist");
@@ -2485,6 +2688,7 @@ $.extend(Fancytree.prototype,
2485
2688
  }
2486
2689
  }else{
2487
2690
  // this.nodeRenderTitle(ctx);
2691
+ this.nodeRenderStatus(ctx);
2488
2692
  }
2489
2693
  // Allow tweaking after node state was rendered
2490
2694
  if ( opts.renderNode ){
@@ -2535,7 +2739,7 @@ $.extend(Fancytree.prototype,
2535
2739
  childNode1 = children[i];
2536
2740
  childNode2 = childLI.ftnode;
2537
2741
  if( childNode1 !== childNode2 ) {
2538
- node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
2742
+ // node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
2539
2743
  node.ul.insertBefore(childNode1.li, childNode2.li);
2540
2744
  } else {
2541
2745
  childLI = childLI.nextSibling;
@@ -2552,7 +2756,7 @@ $.extend(Fancytree.prototype,
2552
2756
  }
2553
2757
  if( !isRootNode ){
2554
2758
  // Update element classes according to node state
2555
- this.nodeRenderStatus(ctx);
2759
+ // this.nodeRenderStatus(ctx);
2556
2760
  // Finally add the whole structure to the DOM, so the browser can render
2557
2761
  if(firstTime){
2558
2762
  parent.ul.appendChild(node.li);
@@ -2560,7 +2764,10 @@ $.extend(Fancytree.prototype,
2560
2764
  }
2561
2765
  },
2562
2766
  /** Create HTML for the node's outer <span> (expander, checkbox, icon, and title).
2767
+ *
2768
+ * nodeRenderStatus() is implied.
2563
2769
  * @param {EventData} ctx
2770
+ * @param {string} [title] optinal new title
2564
2771
  */
2565
2772
  nodeRenderTitle: function(ctx, title) {
2566
2773
  // set node connector images, links and text
@@ -2600,7 +2807,7 @@ $.extend(Fancytree.prototype,
2600
2807
  }
2601
2808
  }
2602
2809
  // Checkbox mode
2603
- if( opts.checkbox && node.hideCheckbox !== true && !node.isStatusNode ) {
2810
+ if( opts.checkbox && node.hideCheckbox !== true && !node.isStatusNode() ) {
2604
2811
  if(aria){
2605
2812
  ares.push("<span role='checkbox' class='fancytree-checkbox'></span>");
2606
2813
  }else{
@@ -2628,7 +2835,7 @@ $.extend(Fancytree.prototype,
2628
2835
  }
2629
2836
  if(!nodeTitle){
2630
2837
  // TODO: escape tooltip string
2631
- tooltip = node.tooltip ? " title='" + node.tooltip.replace(/\"/g, "&quot;") + "'" : "";
2838
+ tooltip = node.tooltip ? " title='" + FT.escapeHtml(node.tooltip) + "'" : "";
2632
2839
  id = aria ? " id='ftal_" + node.key + "'" : "";
2633
2840
  role = aria ? " role='treeitem'" : "";
2634
2841
  tabindex = opts.titlesTabbable ? " tabindex='0'" : "";
@@ -2639,6 +2846,8 @@ $.extend(Fancytree.prototype,
2639
2846
  // Note: this will trigger focusout, if node had the focus
2640
2847
  //$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
2641
2848
  node.span.innerHTML = ares.join("");
2849
+ // Update CSS classes
2850
+ this.nodeRenderStatus(ctx);
2642
2851
  },
2643
2852
  /** Update element classes according to node state.
2644
2853
  * @param {EventData} ctx
@@ -2668,7 +2877,7 @@ $.extend(Fancytree.prototype,
2668
2877
  cnList.push(cn.active);
2669
2878
  // $(">span.fancytree-title", statusElem).attr("tabindex", "0");
2670
2879
  // tree.$container.removeAttr("tabindex");
2671
- }else{
2880
+ // }else{
2672
2881
  // $(">span.fancytree-title", statusElem).removeAttr("tabindex");
2673
2882
  // tree.$container.attr("tabindex", "0");
2674
2883
  }
@@ -2712,6 +2921,12 @@ $.extend(Fancytree.prototype,
2712
2921
  if( node.partsel ){
2713
2922
  cnList.push(cn.partsel);
2714
2923
  }
2924
+ if( node._isLoading ){
2925
+ cnList.push(cn.loading);
2926
+ }
2927
+ if( node._error ){
2928
+ cnList.push(cn.error);
2929
+ }
2715
2930
  if( node.selected ){
2716
2931
  cnList.push(cn.selected);
2717
2932
  if(aria){
@@ -2755,7 +2970,7 @@ $.extend(Fancytree.prototype,
2755
2970
  * If flag is false, the node is deactivated (must be a synchronous operation)
2756
2971
  * @param {EventData} ctx
2757
2972
  * @param {boolean} [flag=true]
2758
- * @param {object} [opts] additional options. Defaults to {}
2973
+ * @param {object} [opts] additional options. Defaults to {noEvents: false}
2759
2974
  */
2760
2975
  nodeSetActive: function(ctx, flag, callOpts) {
2761
2976
  // Handle user click / [space] / [enter], according to clickFolderMode.
@@ -2765,16 +2980,17 @@ $.extend(Fancytree.prototype,
2765
2980
  tree = ctx.tree,
2766
2981
  opts = ctx.options,
2767
2982
  // userEvent = !!ctx.originalEvent,
2983
+ noEvents = (callOpts.noEvents === true),
2768
2984
  isActive = (node === tree.activeNode);
2769
2985
 
2770
2986
  // flag defaults to true
2771
2987
  flag = (flag !== false);
2772
- node.debug("nodeSetActive", flag);
2988
+ // node.debug("nodeSetActive", flag);
2773
2989
 
2774
2990
  if(isActive === flag){
2775
2991
  // Nothing to do
2776
2992
  return _getResolvedPromise(node);
2777
- }else if(flag && this._triggerNodeEvent("beforeActivate", node, ctx.originalEvent) === false ){
2993
+ }else if(flag && !noEvents && this._triggerNodeEvent("beforeActivate", node, ctx.originalEvent) === false ){
2778
2994
  // Callback returned false
2779
2995
  return _getRejectedPromise(node, ["rejected"]);
2780
2996
  }
@@ -2786,24 +3002,29 @@ $.extend(Fancytree.prototype,
2786
3002
  _assert(tree.activeNode === null, "deactivate was out of sync?");
2787
3003
  }
2788
3004
  if(opts.activeVisible){
2789
- tree.nodeMakeVisible(ctx);
3005
+ // tree.nodeMakeVisible(ctx);
3006
+ node.makeVisible();
2790
3007
  }
2791
3008
  tree.activeNode = node;
2792
3009
  tree.nodeRenderStatus(ctx);
2793
3010
  tree.nodeSetFocus(ctx);
2794
- tree._triggerNodeEvent("activate", node);
3011
+ if( !noEvents ) {
3012
+ tree._triggerNodeEvent("activate", node, ctx.originalEvent);
3013
+ }
2795
3014
  }else{
2796
3015
  _assert(tree.activeNode === node, "node was not active (inconsistency)");
2797
3016
  tree.activeNode = null;
2798
3017
  this.nodeRenderStatus(ctx);
2799
- ctx.tree._triggerNodeEvent("deactivate", node);
3018
+ if( !noEvents ) {
3019
+ ctx.tree._triggerNodeEvent("deactivate", node, ctx.originalEvent);
3020
+ }
2800
3021
  }
2801
3022
  },
2802
3023
  /** Expand or collapse node, return Deferred.promise.
2803
3024
  *
2804
3025
  * @param {EventData} ctx
2805
3026
  * @param {boolean} [flag=true]
2806
- * @param {object} [opts] additional options. Defaults to {noAnimation: false}
3027
+ * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
2807
3028
  * @returns {$.Promise} The deferred will be resolved as soon as the (lazy)
2808
3029
  * data was retrieved, rendered, and the expand animation finshed.
2809
3030
  */
@@ -2813,24 +3034,26 @@ $.extend(Fancytree.prototype,
2813
3034
  node = ctx.node,
2814
3035
  tree = ctx.tree,
2815
3036
  opts = ctx.options,
2816
- noAnimation = callOpts.noAnimation === true;
3037
+ noAnimation = (callOpts.noAnimation === true),
3038
+ noEvents = (callOpts.noEvents === true);
2817
3039
 
2818
3040
  // flag defaults to true
2819
3041
  flag = (flag !== false);
2820
3042
 
2821
- node.debug("nodeSetExpanded(" + flag + ")");
3043
+ // node.debug("nodeSetExpanded(" + flag + ")");
2822
3044
 
2823
3045
  if((node.expanded && flag) || (!node.expanded && !flag)){
2824
3046
  // Nothing to do
2825
- node.debug("nodeSetExpanded(" + flag + "): nothing to do");
3047
+ // node.debug("nodeSetExpanded(" + flag + "): nothing to do");
2826
3048
  return _getResolvedPromise(node);
2827
3049
  }else if(flag && !node.lazy && !node.hasChildren() ){
2828
3050
  // Prevent expanding of empty nodes
2829
- return _getRejectedPromise(node, ["empty"]);
3051
+ // return _getRejectedPromise(node, ["empty"]);
3052
+ return _getResolvedPromise(node);
2830
3053
  }else if( !flag && node.getLevel() < opts.minExpandLevel ) {
2831
3054
  // Prevent collapsing locked levels
2832
3055
  return _getRejectedPromise(node, ["locked"]);
2833
- }else if ( this._triggerNodeEvent("beforeExpand", node, ctx.originalEvent) === false ){
3056
+ }else if ( !noEvents && this._triggerNodeEvent("beforeExpand", node, ctx.originalEvent) === false ){
2834
3057
  // Callback returned false
2835
3058
  return _getRejectedPromise(node, ["rejected"]);
2836
3059
  }
@@ -2857,10 +3080,17 @@ $.extend(Fancytree.prototype,
2857
3080
  }
2858
3081
  // Trigger expand/collapse after expanding
2859
3082
  dfd.done(function(){
2860
- ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
2861
3083
  if( opts.autoScroll && !noAnimation ) {
2862
3084
  // Scroll down to last child, but keep current node visible
2863
- node.getLastChild().scrollIntoView(true, node);
3085
+ node.getLastChild().scrollIntoView(true, node).always(function(){
3086
+ if( !noEvents ) {
3087
+ ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
3088
+ }
3089
+ });
3090
+ } else {
3091
+ if( !noEvents ) {
3092
+ ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
3093
+ }
2864
3094
  }
2865
3095
  });
2866
3096
 
@@ -2896,9 +3126,9 @@ $.extend(Fancytree.prototype,
2896
3126
  } else {
2897
3127
  duration = opts.fx.duration || 200;
2898
3128
  easing = opts.fx.easing;
2899
- node.debug("nodeSetExpanded: animate start...");
3129
+ // node.debug("nodeSetExpanded: animate start...");
2900
3130
  $(node.ul).animate(opts.fx, duration, easing, function(){
2901
- node.debug("nodeSetExpanded: animate done");
3131
+ // node.debug("nodeSetExpanded: animate done");
2902
3132
  callback();
2903
3133
  });
2904
3134
  return;
@@ -2910,9 +3140,9 @@ $.extend(Fancytree.prototype,
2910
3140
 
2911
3141
  // Load lazy nodes, if any. Then continue with _afterLoad()
2912
3142
  if(flag && node.lazy && node.hasChildren() === undefined){
2913
- node.debug("nodeSetExpanded: load start...");
2914
- node.lazyLoad().done(function(){
2915
- node.debug("nodeSetExpanded: load done");
3143
+ // node.debug("nodeSetExpanded: load start...");
3144
+ node.load().done(function(){
3145
+ // node.debug("nodeSetExpanded: load done");
2916
3146
  if(dfd.notifyWith){ // requires jQuery 1.6+
2917
3147
  dfd.notifyWith(node, ["loaded"]);
2918
3148
  }
@@ -2921,8 +3151,8 @@ $.extend(Fancytree.prototype,
2921
3151
  _afterLoad(function () { dfd.rejectWith(node, ["load failed (" + errMsg + ")"]); });
2922
3152
  });
2923
3153
  /*
2924
- var source = tree._triggerNodeEvent("lazyload", node, ctx.originalEvent);
2925
- _assert(typeof source !== "boolean", "lazyload event must return source in data.result");
3154
+ var source = tree._triggerNodeEvent("lazyLoad", node, ctx.originalEvent);
3155
+ _assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
2926
3156
  node.debug("nodeSetExpanded: load start...");
2927
3157
  this._callHook("nodeLoadChildren", ctx, source).done(function(){
2928
3158
  node.debug("nodeSetExpanded: load done");
@@ -2937,15 +3167,15 @@ $.extend(Fancytree.prototype,
2937
3167
  }else{
2938
3168
  _afterLoad(function () { dfd.resolveWith(node); });
2939
3169
  }
2940
- node.debug("nodeSetExpanded: returns");
3170
+ // node.debug("nodeSetExpanded: returns");
2941
3171
  return dfd.promise();
2942
3172
  },
2943
- /**
3173
+ /** Focus ot blur this node.
2944
3174
  * @param {EventData} ctx
2945
3175
  * @param {boolean} [flag=true]
2946
3176
  */
2947
3177
  nodeSetFocus: function(ctx, flag) {
2948
- ctx.node.debug("nodeSetFocus(" + flag + ")");
3178
+ // ctx.node.debug("nodeSetFocus(" + flag + ")");
2949
3179
  var ctx2,
2950
3180
  tree = ctx.tree,
2951
3181
  node = ctx.node;
@@ -2955,7 +3185,7 @@ $.extend(Fancytree.prototype,
2955
3185
  // Blur previous node if any
2956
3186
  if(tree.focusNode){
2957
3187
  if(tree.focusNode === node && flag){
2958
- node.debug("nodeSetFocus(" + flag + "): nothing to do");
3188
+ // node.debug("nodeSetFocus(" + flag + "): nothing to do");
2959
3189
  return;
2960
3190
  }
2961
3191
  ctx2 = $.extend({}, ctx, {node: tree.focusNode});
@@ -2970,7 +3200,8 @@ $.extend(Fancytree.prototype,
2970
3200
  // Note: we pass _calledByNodeSetFocus=true
2971
3201
  this._callHook("treeSetFocus", ctx, true, true);
2972
3202
  }
2973
- this.nodeMakeVisible(ctx);
3203
+ // this.nodeMakeVisible(ctx);
3204
+ node.makeVisible();
2974
3205
  tree.focusNode = node;
2975
3206
  // node.debug("FOCUS...");
2976
3207
  // $(node.span).find(".fancytree-title").focus();
@@ -3032,19 +3263,19 @@ $.extend(Fancytree.prototype,
3032
3263
  * @param details
3033
3264
  */
3034
3265
  nodeSetStatus: function(ctx, status, message, details) {
3035
- var _clearStatusNode, _setStatusNode,
3036
- node = ctx.node,
3037
- tree = ctx.tree,
3038
- cn = ctx.options._classNames;
3266
+ var node = ctx.node,
3267
+ tree = ctx.tree;
3268
+ // cn = ctx.options._classNames;
3039
3269
 
3040
- _clearStatusNode = function() {
3270
+ function _clearStatusNode() {
3271
+ // Remove dedicated dummy node, if any
3041
3272
  var firstChild = ( node.children ? node.children[0] : null );
3042
- if ( firstChild && firstChild.isStatusNode ) {
3273
+ if ( firstChild && firstChild.isStatusNode() ) {
3043
3274
  try{
3044
3275
  // I've seen exceptions here with loadKeyPath...
3045
3276
  if(node.ul){
3046
3277
  node.ul.removeChild(firstChild.li);
3047
- firstChild.li = null; // avoid leaks (issue 215)
3278
+ firstChild.li = null; // avoid leaks (DT issue 215)
3048
3279
  }
3049
3280
  }catch(e){}
3050
3281
  if( node.children.length === 1 ){
@@ -3053,48 +3284,58 @@ $.extend(Fancytree.prototype,
3053
3284
  node.children.shift();
3054
3285
  }
3055
3286
  }
3056
- };
3057
- _setStatusNode = function(data) {
3287
+ }
3288
+ function _setStatusNode(data, type) {
3289
+ // Create/modify the dedicated dummy node for 'loading...' or
3290
+ // 'error!' status. (only called for direct child of the invisible
3291
+ // system root)
3058
3292
  var firstChild = ( node.children ? node.children[0] : null );
3059
- if ( firstChild && firstChild.isStatusNode ) {
3293
+ if ( firstChild && firstChild.isStatusNode() ) {
3060
3294
  $.extend(firstChild, data);
3061
3295
  tree._callHook("nodeRender", firstChild);
3062
3296
  } else {
3063
3297
  data.key = "_statusNode";
3064
3298
  node._setChildren([data]);
3065
- node.children[0].isStatusNode = true;
3299
+ node.children[0].statusNodeType = type;
3066
3300
  tree.render();
3067
3301
  }
3068
3302
  return node.children[0];
3069
- };
3070
- switch(status){
3303
+ }
3304
+
3305
+ switch( status ){
3071
3306
  case "ok":
3072
3307
  _clearStatusNode();
3073
- $(node.span).removeClass(cn.loading);
3074
- $(node.span).removeClass(cn.error);
3308
+ // $(node.span).removeClass(cn.loading).removeClass(cn.error);
3309
+ node._isLoading = false;
3310
+ node._error = null;
3311
+ node.renderStatus();
3075
3312
  break;
3076
3313
  case "loading":
3077
- $(node.span).removeClass(cn.error);
3078
- $(node.span).addClass(cn.loading);
3079
- if(!node.parent){
3314
+ // $(node.span).removeClass(cn.error).addClass(cn.loading);
3315
+ if( !node.parent ) {
3080
3316
  _setStatusNode({
3081
3317
  title: tree.options.strings.loading + (message ? " (" + message + ") " : ""),
3082
3318
  tooltip: details,
3083
3319
  extraClasses: "fancytree-statusnode-wait"
3084
- });
3320
+ }, status);
3085
3321
  }
3322
+ node._isLoading = true;
3323
+ node._error = null;
3324
+ node.renderStatus();
3086
3325
  break;
3087
3326
  case "error":
3088
- $(node.span).removeClass(cn.loading);
3089
- $(node.span).addClass(cn.error);
3327
+ // $(node.span).removeClass(cn.loading).addClass(cn.error);
3090
3328
  _setStatusNode({
3091
3329
  title: tree.options.strings.loadError + (message ? " (" + message + ") " : ""),
3092
3330
  tooltip: details,
3093
3331
  extraClasses: "fancytree-statusnode-error"
3094
- });
3332
+ }, status);
3333
+ node._isLoading = false;
3334
+ node._error = { message: message, details: details };
3335
+ node.renderStatus();
3095
3336
  break;
3096
3337
  default:
3097
- $.error("invalid status " + status);
3338
+ $.error("invalid node status " + status);
3098
3339
  }
3099
3340
  },
3100
3341
  /**
@@ -3140,7 +3381,7 @@ $.extend(Fancytree.prototype,
3140
3381
  },
3141
3382
  /** Parse Fancytree from source, as configured in the options.
3142
3383
  * @param {EventData} ctx
3143
- * @param {object} [source] new source
3384
+ * @param {object} [source] optional new source (use last data otherwise)
3144
3385
  */
3145
3386
  treeLoad: function(ctx, source) {
3146
3387
  var type, $ul,
@@ -3162,6 +3403,8 @@ $.extend(Fancytree.prototype,
3162
3403
  $ul = $container.find(">ul:first");
3163
3404
  $ul.addClass("ui-fancytree-source ui-helper-hidden");
3164
3405
  source = $.ui.fancytree.parseHtml($ul);
3406
+ // allow to init tree.data.foo from <ul data-foo=''>
3407
+ this.data = $.extend(this.data, _getElementDataAsDict($ul));
3165
3408
  break;
3166
3409
  case "json":
3167
3410
  // $().addClass("ui-helper-hidden");
@@ -3193,12 +3436,23 @@ $.extend(Fancytree.prototype,
3193
3436
  });
3194
3437
  return dfd;
3195
3438
  },
3439
+ /** Node was inserted into or removed from the tree.
3440
+ * @param {EventData} ctx
3441
+ * @param {boolean} add
3442
+ * @param {FancytreeNode} node
3443
+ */
3444
+ treeRegisterNode: function(ctx, add, node) {
3445
+ },
3446
+ /** Widget got focus.
3447
+ * @param {EventData} ctx
3448
+ * @param {boolean} [flag=true]
3449
+ */
3196
3450
  treeSetFocus: function(ctx, flag, _calledByNodeSetFocus) {
3197
3451
  flag = (flag !== false);
3198
3452
 
3199
- this.debug("treeSetFocus(" + flag + "), _calledByNodeSetFocus: " + _calledByNodeSetFocus);
3200
- this.debug(" focusNode: " + this.focusNode);
3201
- this.debug(" activeNode: " + this.activeNode);
3453
+ // this.debug("treeSetFocus(" + flag + "), _calledByNodeSetFocus: " + _calledByNodeSetFocus);
3454
+ // this.debug(" focusNode: " + this.focusNode);
3455
+ // this.debug(" activeNode: " + this.activeNode);
3202
3456
  if( flag !== this.hasFocus() ){
3203
3457
  this._hasFocus = flag;
3204
3458
  this.$container.toggleClass("fancytree-treefocus", flag);
@@ -3211,20 +3465,21 @@ $.extend(Fancytree.prototype,
3211
3465
  /* ******************************************************************************
3212
3466
  * jQuery UI widget boilerplate
3213
3467
  */
3468
+
3214
3469
  /**
3215
- * This constructor is not called directly. Use `$(selector).fancytre({})`
3216
- * to initialize the plugin instead.
3217
- *
3218
- * @class ui.fancytree
3219
- * @classdesc The plugin (derrived from <a href=" http://api.jqueryui.com/jQuery.widget/">jQuery.Widget</a>).<br>
3220
- * <pre class="sh_javascript sunlight-highlight-javascript">// Access instance methods and members:
3221
- * var tree = $(selector).fancytree("getTree");
3222
- * // Access static members:
3223
- * alert($.ui.fancytree.version);
3470
+ * The plugin (derrived from <a href=" http://api.jqueryui.com/jQuery.widget/">jQuery.Widget</a>).<br>
3471
+ * This constructor is not called directly. Use `$(selector).fancytree({})`
3472
+ * to initialize the plugin instead.<br>
3473
+ * <pre class="sh_javascript sunlight-highlight-javascript">// Access widget methods and members:
3474
+ * var tree = $("#tree").fancytree("getTree");
3475
+ * var node = $("#tree").fancytree("getActiveNode", "1234");
3224
3476
  * </pre>
3477
+ *
3478
+ * @mixin Fancytree_Widget
3225
3479
  */
3480
+
3226
3481
  $.widget("ui.fancytree",
3227
- /** @lends ui.fancytree# */
3482
+ /** @lends Fancytree_Widget# */
3228
3483
  {
3229
3484
  /**These options will be used as defaults
3230
3485
  * @type {FancytreeOptions}
@@ -3281,7 +3536,7 @@ $.widget("ui.fancytree",
3281
3536
  error: "fancytree-error"
3282
3537
  },
3283
3538
  // events
3284
- lazyload: null,
3539
+ lazyLoad: null,
3285
3540
  postProcess: null
3286
3541
  },
3287
3542
  /* Set up the widget, Called on first $().fancytree() */
@@ -3466,29 +3721,35 @@ $.widget("ui.fancytree",
3466
3721
  return ( tree._triggerNodeEvent("dblclick", ctx, event) === false ) ? false : tree._callHook("nodeDblclick", ctx);
3467
3722
  }
3468
3723
  // } catch(e) {
3469
- // // var _ = null; // issue 117 // TODO
3724
+ // // var _ = null; // DT issue 117 // TODO
3470
3725
  // $.error(e);
3471
3726
  } finally {
3472
3727
  tree.phase = prevPhase;
3473
3728
  }
3474
3729
  });
3475
3730
  },
3476
- /** @returns {FancytreeNode} the active node or null */
3731
+ /** Return the active node or null.
3732
+ * @returns {FancytreeNode}
3733
+ */
3477
3734
  getActiveNode: function() {
3478
3735
  return this.tree.activeNode;
3479
3736
  },
3480
- /**
3737
+ /** Return the matching node or null.
3481
3738
  * @param {string} key
3482
- * @returns {FancytreeNode} the matching node or null
3739
+ * @returns {FancytreeNode}
3483
3740
  */
3484
3741
  getNodeByKey: function(key) {
3485
3742
  return this.tree.getNodeByKey(key);
3486
3743
  },
3487
- /** @returns {FancytreeNode} the invisible system root node */
3744
+ /** Return the invisible system root node.
3745
+ * @returns {FancytreeNode}
3746
+ */
3488
3747
  getRootNode: function() {
3489
3748
  return this.tree.rootNode;
3490
3749
  },
3491
- /** @returns {Fancytree} the current tree instance */
3750
+ /** Return the current tree instance.
3751
+ * @returns {Fancytree}
3752
+ */
3492
3753
  getTree: function() {
3493
3754
  return this.tree;
3494
3755
  }
@@ -3497,22 +3758,26 @@ $.widget("ui.fancytree",
3497
3758
  // $.ui.fancytree was created by the widget factory. Create a local shortcut:
3498
3759
  FT = $.ui.fancytree;
3499
3760
 
3500
- /*
3501
- * Static members in the `$.ui.fancytree` namespace.
3502
- *
3503
- * @example:
3504
- * alert(""version: " + $.ui.fancytree.version);
3761
+ /**
3762
+ * Static members in the `$.ui.fancytree` namespace.<br>
3763
+ * <br>
3764
+ * <pre class="sh_javascript sunlight-highlight-javascript">// Access static members:
3505
3765
  * var node = $.ui.fancytree.getNode(element);
3766
+ * alert($.ui.fancytree.version);
3767
+ * </pre>
3768
+ *
3769
+ * @mixin Fancytree_Static
3506
3770
  */
3507
3771
  $.extend($.ui.fancytree,
3508
- /** @lends ui.fancytree */
3772
+ /** @lends Fancytree_Static# */
3509
3773
  {
3510
3774
  /** @type {string} */
3511
- version: "2.0.0-6",
3775
+ version: "2.0.0-11", // Set to semver by 'grunt release'
3512
3776
  /** @type {string} */
3513
- buildType: "release",
3777
+ buildType: "production", // Set to 'production' by 'grunt build'
3514
3778
  /** @type {int} */
3515
- debugLevel: 1, // used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
3779
+ debugLevel: 1, // Set to 1 by 'grunt build'
3780
+ // Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
3516
3781
 
3517
3782
  _nextId: 1,
3518
3783
  _nextNodeKey: 1,
@@ -3528,19 +3793,48 @@ $.extend($.ui.fancytree,
3528
3793
  // http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
3529
3794
  positionMyOfs: isVersionAtLeast($.ui.version, 1, 9)
3530
3795
  },
3796
+ /** Throw an error if condition fails (debug method).
3797
+ * @param {boolean} cond
3798
+ * @param {string} msg
3799
+ */
3531
3800
  assert: function(cond, msg){
3532
3801
  return _assert(cond, msg);
3533
3802
  },
3803
+ /** Write message to console if debugLevel >= 2
3804
+ * @param {string} msg
3805
+ */
3534
3806
  debug: function(msg){
3535
3807
  /*jshint expr:true */
3536
3808
  ($.ui.fancytree.debugLevel >= 2) && consoleApply("log", arguments);
3537
3809
  },
3810
+ /** Write error message to console.
3811
+ * @param {string} msg
3812
+ */
3538
3813
  error: function(msg){
3539
3814
  consoleApply("error", arguments);
3540
3815
  },
3816
+ /** Convert &lt;, &gt;, &amp;, &quot;, &#39;, &#x2F; to the equivalent entitites.
3817
+ *
3818
+ * @param {string} s
3819
+ * @returns {string}
3820
+ */
3821
+ escapeHtml: function(s){
3822
+ return ("" + s).replace(/[&<>"'\/]/g, function (s) {
3823
+ return ENTITY_MAP[s];
3824
+ });
3825
+ },
3826
+ /** Inverse of escapeHtml().
3827
+ *
3828
+ * @param {string} s
3829
+ * @returns {string}
3830
+ */
3831
+ unescapeHtml: function(s){
3832
+ var e = document.createElement("div");
3833
+ e.innerHTML = s;
3834
+ return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
3835
+ },
3541
3836
  /** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
3542
3837
  *
3543
- * @static
3544
3838
  * @param {Event} event Mouse event, e.g. click, ...
3545
3839
  * @returns {string} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
3546
3840
  */
@@ -3563,12 +3857,12 @@ $.extend($.ui.fancytree,
3563
3857
  res.type = "title";
3564
3858
  }else if( /\bfancytree-expander\b/.test(tcn) ){
3565
3859
  res.type = (res.node.hasChildren() === false ? "prefix" : "expander");
3566
- }else if( /\bfancytree-checkbox\b/.test(tcn) ){
3860
+ }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
3567
3861
  res.type = "checkbox";
3568
3862
  }else if( /\bfancytree-icon\b/.test(tcn) ){
3569
3863
  res.type = "icon";
3570
3864
  }else if( /\bfancytree-node\b/.test(tcn) ){
3571
- // TODO: issue #93 (http://code.google.com/p/fancytree/issues/detail?id=93)
3865
+ // TODO: (http://code.google.com/p/dynatree/issues/detail?id=93)
3572
3866
  // res.type = this._getTypeForOuterNodeEvent(event);
3573
3867
  res.type = "title";
3574
3868
  }
@@ -3612,6 +3906,9 @@ $.extend($.ui.fancytree,
3612
3906
  return null;
3613
3907
  },
3614
3908
  */
3909
+ /** Write message to console if debugLevel >= 1
3910
+ * @param {string} msg
3911
+ */
3615
3912
  info: function(msg){
3616
3913
  /*jshint expr:true */
3617
3914
  ($.ui.fancytree.debugLevel >= 1) && consoleApply("info", arguments);
@@ -3625,13 +3922,12 @@ $.extend($.ui.fancytree,
3625
3922
  parseHtml: function($ul) {
3626
3923
  // TODO: understand this:
3627
3924
  /*jshint validthis:true */
3628
- var $children = $ul.find(">li"),
3629
- extraClasses, i, l, iPos, tmp, classes, className,
3925
+ var extraClasses, i, l, iPos, tmp, tmp2, classes, className,
3926
+ $children = $ul.find(">li"),
3630
3927
  children = [];
3631
- // that = this;
3632
3928
 
3633
3929
  $children.each(function() {
3634
- var allData, jsonData,
3930
+ var allData,
3635
3931
  $li = $(this),
3636
3932
  $liSpan = $li.find(">span:first", this),
3637
3933
  $liA = $liSpan.length ? null : $li.find(">a:first"),
@@ -3685,24 +3981,20 @@ $.extend($.ui.fancytree,
3685
3981
  d.key = tmp;
3686
3982
  }
3687
3983
  // Add <li data-NAME='...'> as node.data.NAME
3688
- // See http://api.jquery.com/data/#data-html5
3689
- allData = $li.data();
3690
- // alert("d: " + JSON.stringify(allData));
3984
+ allData = _getElementDataAsDict($li);
3691
3985
  if(allData && !$.isEmptyObject(allData)) {
3692
- // Special handling for <li data-json='...'>
3693
- jsonData = allData.json;
3694
- delete allData.json;
3695
- $.extend(d.data, allData);
3696
- // If a 'data-json' attribute is present, evaluate and add to node.data
3697
- if(jsonData) {
3698
- // alert("$li.data()" + JSON.stringify(jsonData));
3699
- // <li data-json='...'> is already returned as object
3700
- // see http://api.jquery.com/data/#data-html5
3701
- $.extend(d.data, jsonData);
3986
+ // #56: Allow to set special node.attributes from data-...
3987
+ for(i=0, l=NODE_ATTRS.length; i<l; i++){
3988
+ tmp = NODE_ATTRS[i];
3989
+ tmp2 = allData[tmp];
3990
+ if( tmp2 != null ) {
3991
+ delete allData[tmp];
3992
+ d[tmp] = tmp2;
3993
+ }
3702
3994
  }
3995
+ // All other data-... goes to node.data...
3996
+ $.extend(d.data, allData);
3703
3997
  }
3704
- // that.debug("parse ", d);
3705
- // var childNode = parentTreeNode.addChild(data);
3706
3998
  // Recursive reading of child nodes, if LI tag contains an UL tag
3707
3999
  $ul = $li.find(">ul:first");
3708
4000
  if( $ul.length ) {
@@ -3717,42 +4009,208 @@ $.extend($.ui.fancytree,
3717
4009
  },
3718
4010
  /** Add Fancytree extension definition to the list of globally available extensions.
3719
4011
  *
3720
- * @param {Object} definition
4012
+ * @param {object} definition
3721
4013
  */
3722
4014
  registerExtension: function(definition){
3723
4015
  _assert(definition.name != null, "extensions must have a `name` property.");
3724
4016
  _assert(definition.version != null, "extensions must have a `version` property.");
3725
4017
  $.ui.fancytree._extensions[definition.name] = definition;
3726
4018
  },
4019
+ /** Write warning message to console.
4020
+ * @param {string} msg
4021
+ */
3727
4022
  warn: function(msg){
3728
4023
  consoleApply("warn", arguments);
3729
4024
  }
3730
4025
  });
3731
4026
 
3732
- // Use $.ui.fancytree.debugLevel as default for tree.options.debugLevel
3733
- //$.ui.fancytree.debug($.ui.fancytree.prototype);
3734
- //$.ui.fancytree.prototype.options.debugLevel = $.ui.fancytree.debugLevel;
4027
+ }(jQuery, window, document));
3735
4028
 
4029
+ // Extending Fancytree
4030
+ // ===================
4031
+ //
4032
+ // See also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
4033
+ //
4034
+ // Every extension should have a comment header containing some information
4035
+ // about the author, copyright and licensing. Also a pointer to the latest
4036
+ // source code.
4037
+ // Prefix with `/*!` so the comment is not removed by the minifier.
3736
4038
 
3737
- /* *****************************************************************************
3738
- * Register AMD
4039
+ /*!
4040
+ * jquery.fancytree.childcounter.js
4041
+ *
4042
+ * Add a child counter bubble to tree nodes.
4043
+ * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
4044
+ *
4045
+ * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
4046
+ *
4047
+ * Released under the MIT license
4048
+ * https://github.com/mar10/fancytree/wiki/LicenseInfo
4049
+ *
4050
+ * @version 2.0.0-11
4051
+ * @date 2014-04-27T22:28
3739
4052
  */
3740
- // http://stackoverflow.com/questions/10918063/how-to-make-a-jquery-plugin-loadable-with-requirejs
3741
4053
 
3742
- // if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
3743
- // define( "jquery", [], function () { return jQuery; } );
3744
- // }
4054
+ // To keep the global namespace clean, we wrap everything in a closure
3745
4055
 
3746
- // TODO: maybe like so:?
3747
- // https://raw.github.com/malsup/blockui/master/jquery.blockUI.js
3748
- /*
3749
- if( typeof define === "function" && define.amd ) {
3750
- define( ["jquery"], function () {
3751
- return jQuery.ui.fancytree;
3752
- });
3753
- }
3754
- */
3755
- }(jQuery, window, document));
4056
+ ;(function($, undefined) {
4057
+
4058
+ // Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
4059
+ "use strict";
4060
+
4061
+ // The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
4062
+ // require jshint compliance.
4063
+ // But for this sample, we want to allow unused variables for demonstration purpose.
4064
+
4065
+ /*jshint unused:false */
4066
+
4067
+
4068
+ // Adding methods
4069
+ // --------------
4070
+
4071
+ // New member functions can be added to the `Fancytree` class.
4072
+ // This function will be available for every tree instance.
4073
+ //
4074
+ // var tree = $("#tree").fancytree("getTree");
4075
+ // tree.countSelected(false);
4076
+
4077
+ $.ui.fancytree._FancytreeClass.prototype.countSelected = function(topOnly){
4078
+ var tree = this,
4079
+ treeOptions = tree.options;
4080
+ return tree.getSelectedNodes(topOnly).length;
4081
+ };
4082
+
4083
+
4084
+ // The `FancytreeNode` class can also be easily extended. This would be called
4085
+ // like
4086
+ //
4087
+ // node.toUpper();
4088
+
4089
+ $.ui.fancytree._FancytreeNodeClass.prototype.toUpper = function(){
4090
+ var node = this;
4091
+ return node.setTitle(node.title.toUpperCase());
4092
+ };
4093
+
4094
+
4095
+ // Finally, we can extend the widget API and create functions that are called
4096
+ // like so:
4097
+ //
4098
+ // $("#tree").fancytree("widgetMethod1", "abc");
4099
+
4100
+ $.ui.fancytree.prototype.widgetMethod1 = function(arg1){
4101
+ var tree = this.tree;
4102
+ return arg1;
4103
+ };
4104
+
4105
+
4106
+ // Register a Fancytree extension
4107
+ // ------------------------------
4108
+ // A full blown extension, extension is available for all trees and can be
4109
+ // enabled like so (see also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
4110
+ //
4111
+ // <script src="../src/jquery.fancytree.js" type="text/javascript"></script>
4112
+ // <script src="../src/jquery.fancytree.childcounter.js" type="text/javascript"></script>
4113
+ // ...
4114
+ //
4115
+ // $("#tree").fancytree({
4116
+ // extensions: ["childcounter"],
4117
+ // childcounter: {
4118
+ // hideExpanded: true
4119
+ // },
4120
+ // ...
4121
+ // });
4122
+ //
4123
+
4124
+
4125
+ /* 'childcounter' extension */
4126
+ $.ui.fancytree.registerExtension({
4127
+ // Every extension must be registered by a unique name.
4128
+ name: "childcounter",
4129
+ // Version information should be compliant with [semver](http://semver.org)
4130
+ version: "1.0.0",
4131
+
4132
+ // Extension specific options and their defaults.
4133
+ // This options will be available as `tree.options.childcounter.hideExpanded`
4134
+
4135
+ options: {
4136
+ deep: true,
4137
+ hideZeros: true,
4138
+ hideExpanded: false
4139
+ },
4140
+
4141
+ // Attributes other than `options` (or functions) can be defined here, and
4142
+ // will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
4143
+ // They can also be accessed as `this._local.foo` from within the extension
4144
+ // methods.
4145
+ foo: 42,
4146
+
4147
+ // Local functions are prefixed with an underscore '_'.
4148
+ // Callable as `this._local._appendCounter()`.
4149
+
4150
+ _appendCounter: function(bar){
4151
+ var tree = this;
4152
+ },
4153
+
4154
+ // **Override virtual methods for this extension.**
4155
+ //
4156
+ // Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
4157
+ // with a `ctx` argument (see [EventData](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
4158
+ // for details) and an extended calling context:<br>
4159
+ // `this` : the Fancytree instance<br>
4160
+ // `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
4161
+ // `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
4162
+ //
4163
+ // See also the [complete list of available hook functions](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
4164
+
4165
+ /* Init */
4166
+ // `treeInit` is triggered when a tree is initalized. We can set up classes or
4167
+ // bind event handlers here...
4168
+ treeInit: function(ctx){
4169
+ var tree = this, // same as ctx.tree,
4170
+ opts = ctx.options,
4171
+ extOpts = ctx.options.childcounter;
4172
+ // Optionally check for dependencies with other extensions
4173
+ // this._requireExtension("glyph", false, false);
4174
+ // Call the base implementation
4175
+ this._super(ctx);
4176
+ // Add a class to the tree container
4177
+ this.$container.addClass("fancytree-ext-childcounter");
4178
+ },
4179
+
4180
+ // Destroy this tree instance (we only call the default implementation, so
4181
+ // this method could as well be omitted).
4182
+
4183
+ treeDestroy: function(ctx){
4184
+ this._super(ctx);
4185
+ },
4186
+
4187
+ // Overload the `renderTitle` hook, to append a counter badge
4188
+ nodeRenderTitle: function(ctx, title) {
4189
+ var node = ctx.node,
4190
+ extOpts = ctx.options.childcounter,
4191
+ count = (node.data.childCounter == null) ? node.countChildren(extOpts.deep) : +node.data.childCounter;
4192
+ // Let the base implementation render the title
4193
+ this._super(ctx, title);
4194
+ // Append a counter badge
4195
+ if( (count || ! extOpts.hideZeros) && (!node.isExpanded() || !extOpts.hideExpanded) ){
4196
+ $("span.fancytree-icon", node.span).append($("<span class='fancytree-childcounter'/>").text(count));
4197
+ }
4198
+ },
4199
+ // Overload the `setExpanded` hook, so the counters are updated
4200
+ nodeSetExpanded: function(ctx, flag, opts) {
4201
+ var tree = ctx.tree,
4202
+ node = ctx.node;
4203
+ // Let the base implementation expand/collapse the node, then redraw the title
4204
+ // after the animation has finished
4205
+ return this._super(ctx, flag, opts).always(function(){
4206
+ tree.nodeRenderTitle(ctx);
4207
+ });
4208
+ }
4209
+
4210
+ // End of extension definition
4211
+ });
4212
+ // End of namespace closure
4213
+ }(jQuery));
3756
4214
 
3757
4215
  /*!
3758
4216
  * jquery.fancytree.dnd.js
@@ -3765,8 +4223,8 @@ if( typeof define === "function" && define.amd ) {
3765
4223
  * Released under the MIT license
3766
4224
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
3767
4225
  *
3768
- * @version 2.0.0-6
3769
- * @date 2014-02-10T10:52
4226
+ * @version 2.0.0-11
4227
+ * @date 2014-04-27T22:28
3770
4228
  */
3771
4229
 
3772
4230
  ;(function($, window, document, undefined) {
@@ -3790,12 +4248,12 @@ function offsetString(n){
3790
4248
  function _initDragAndDrop(tree) {
3791
4249
  var dnd = tree.options.dnd || null;
3792
4250
  // Register 'connectToFancytree' option with ui.draggable
3793
- if(dnd /*&& (dnd.dragStart || dnd.dragDrop)*/) {
4251
+ if( dnd ) {
3794
4252
  _registerDnd();
3795
4253
  }
3796
4254
  // Attach ui.draggable to this Fancytree instance
3797
4255
  if(dnd && dnd.dragStart ) {
3798
- tree.widget.element.draggable({
4256
+ tree.widget.element.draggable($.extend({
3799
4257
  addClasses: false,
3800
4258
  appendTo: "body",
3801
4259
  containment: false,
@@ -3811,21 +4269,21 @@ function _initDragAndDrop(tree) {
3811
4269
  // Let source tree create the helper element
3812
4270
  helper: function(event) {
3813
4271
  var sourceNode = $.ui.fancytree.getNode(event.target);
3814
- if(!sourceNode){ // issue 211
3815
- // TODO: remove this hint, when we understand when it happens
4272
+ if(!sourceNode){ // Dynatree issue 211
4273
+ // might happen, if dragging a table *header*
3816
4274
  return "<div>ERROR?: helper requested but sourceNode not found</div>";
3817
4275
  }
3818
4276
  return sourceNode.tree.ext.dnd._onDragEvent("helper", sourceNode, null, event, null, null);
3819
4277
  },
3820
4278
  start: function(event, ui) {
3821
- // var sourceNode = $.ui.fancytree.getNode(event.target);
3822
- // don't return false if sourceNode == null (see issue 268)
4279
+ var sourceNode = ui.helper.data("ftSourceNode");
4280
+ return !!sourceNode; // Abort dragging if no node could be found
3823
4281
  }
3824
- });
4282
+ }, tree.options.dnd.draggable));
3825
4283
  }
3826
4284
  // Attach ui.droppable to this Fancytree instance
3827
4285
  if(dnd && dnd.dragDrop) {
3828
- tree.widget.element.droppable({
4286
+ tree.widget.element.droppable($.extend({
3829
4287
  addClasses: false,
3830
4288
  tolerance: "intersect",
3831
4289
  greedy: false
@@ -3850,7 +4308,7 @@ function _initDragAndDrop(tree) {
3850
4308
  logMsg("droppable - over", event, ui);
3851
4309
  }
3852
4310
  */
3853
- });
4311
+ }, tree.options.dnd.droppable));
3854
4312
  }
3855
4313
  }
3856
4314
 
@@ -3963,21 +4421,24 @@ function _registerDnd() {
3963
4421
  $.ui.fancytree.registerExtension(
3964
4422
  {
3965
4423
  name: "dnd",
3966
- version: "0.0.1",
4424
+ version: "0.1.0",
3967
4425
  // Default options for this extension.
3968
4426
  options: {
3969
4427
  // Make tree nodes draggable:
3970
- dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
3971
- dragStop: null, // Callback(sourceNode, data)
4428
+ dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
4429
+ dragStop: null, // Callback(sourceNode, data)
3972
4430
  // helper: null,
3973
4431
  // Make tree nodes accept draggables
3974
4432
  autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
3975
4433
  preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
3976
4434
  preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
3977
- dragEnter: null, // Callback(targetNode, data)
3978
- dragOver: null, // Callback(targetNode, data)
3979
- dragDrop: null, // Callback(targetNode, data)
3980
- dragLeave: null // Callback(targetNode, data)
4435
+ dragEnter: null, // Callback(targetNode, data)
4436
+ dragOver: null, // Callback(targetNode, data)
4437
+ dragDrop: null, // Callback(targetNode, data)
4438
+ dragLeave: null, // Callback(targetNode, data)
4439
+ //
4440
+ draggable: null, // Additional options passed to jQuery draggable
4441
+ droppable: null // Additional options passed to jQuery droppable
3981
4442
  },
3982
4443
  // Override virtual methods for this extension.
3983
4444
  // `this` : Fancytree instance
@@ -4150,7 +4611,7 @@ $.ui.fancytree.registerExtension(
4150
4611
  res = $helper;
4151
4612
  break;
4152
4613
  case "start":
4153
- if( node.isStatusNode ) {
4614
+ if( node.isStatusNode() ) {
4154
4615
  res = false;
4155
4616
  } else if(dnd.dragStart) {
4156
4617
  res = dnd.dragStart(node, ctx);
@@ -4302,11 +4763,8 @@ $.ui.fancytree.registerExtension(
4302
4763
  * Released under the MIT license
4303
4764
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
4304
4765
  *
4305
- * @version 2.0.0-6
4306
- * @date 2014-02-10T10:52
4307
- */
4308
- /**
4309
- * @module fancytree/edit
4766
+ * @version 2.0.0-11
4767
+ * @date 2014-04-27T22:28
4310
4768
  */
4311
4769
 
4312
4770
  ;(function($, window, document, undefined) {
@@ -4318,7 +4776,9 @@ $.ui.fancytree.registerExtension(
4318
4776
  * Private functions and variables
4319
4777
  */
4320
4778
 
4321
- var isMac = /Mac/.test(navigator.platform)
4779
+ var isMac = /Mac/.test(navigator.platform),
4780
+ escapeHtml = $.ui.fancytree.escapeHtml,
4781
+ unescapeHtml = $.ui.fancytree.unescapeHtml;
4322
4782
  // modifiers = {shift: "shiftKey", ctrl: "ctrlKey", alt: "altKey", meta: "metaKey"},
4323
4783
  // specialKeys = {
4324
4784
  // 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
@@ -4334,8 +4794,8 @@ var isMac = /Mac/.test(navigator.platform)
4334
4794
  // "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
4335
4795
  // "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
4336
4796
  // ".": ">", "/": "?", "\\": "|"
4337
- // }
4338
- ;
4797
+ // };
4798
+
4339
4799
 
4340
4800
  // $.ui.fancytree.isKeydownEvent = function(e, code){
4341
4801
  // var i, part, partmap, partlist = code.split("+"), len = parts.length;
@@ -4383,7 +4843,7 @@ $.ui.fancytree._FancytreeNodeClass.prototype.editStart = function(){
4383
4843
  // Replace node with <input>
4384
4844
  $input = $("<input />", {
4385
4845
  "class": "fancytree-edit-input",
4386
- value: prevTitle
4846
+ value: unescapeHtml(prevTitle)
4387
4847
  });
4388
4848
  if ( instOpts.adjustWidthOfs != null ) {
4389
4849
  $input.width($title.width() + instOpts.adjustWidthOfs);
@@ -4456,7 +4916,7 @@ $.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function(applyChanges, _e
4456
4916
  $(document).off(".fancytree-edit");
4457
4917
 
4458
4918
  if( doSave ) {
4459
- node.setTitle( newVal );
4919
+ node.setTitle( escapeHtml(newVal) );
4460
4920
  }else{
4461
4921
  node.renderTitle();
4462
4922
  }
@@ -4473,13 +4933,13 @@ $.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function(applyChanges, _e
4473
4933
 
4474
4934
 
4475
4935
  $.ui.fancytree._FancytreeNodeClass.prototype.startEdit = function(){
4476
- this.warn("FancytreeNode.startEdit() is deprecated. Use .editStart() instead.");
4936
+ this.warn("FancytreeNode.startEdit() is deprecated since 2014-01-04. Use .editStart() instead.");
4477
4937
  return this.editStart.apply(this, arguments);
4478
4938
  };
4479
4939
 
4480
4940
 
4481
4941
  $.ui.fancytree._FancytreeNodeClass.prototype.endEdit = function(){
4482
- this.warn("FancytreeNode.endEdit() is deprecated. Use .editEnd() instead.");
4942
+ this.warn("FancytreeNode.endEdit() is deprecated since 2014-01-04. Use .editEnd() instead.");
4483
4943
  return this.editEnd.apply(this, arguments);
4484
4944
  };
4485
4945
 
@@ -4622,8 +5082,8 @@ $.ui.fancytree.registerExtension({
4622
5082
  * Released under the MIT license
4623
5083
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
4624
5084
  *
4625
- * @version 2.0.0-6
4626
- * @date 2014-02-10T10:52
5085
+ * @version 2.0.0-11
5086
+ * @date 2014-04-27T22:28
4627
5087
  */
4628
5088
 
4629
5089
  ;(function($, window, document, undefined) {
@@ -4640,24 +5100,33 @@ function _escapeRegex(str){
4640
5100
  return (str + "").replace(/([.?*+\^\$\[\]\\(){}|-])/g, "\\$1");
4641
5101
  }
4642
5102
 
5103
+ /* EXT-TABLE: Show/hide all rows that are structural descendants of `parent`. */
5104
+ // function setChildRowVisibility(parent, flag) {
5105
+ // parent.visit(function(node){
5106
+ // var tr = node.tr;
5107
+ // if(tr){
5108
+ // tr.style.display = flag ? "" : "none";
5109
+ // }
5110
+ // node.debug(flag ? "SHOW" : "HIDE");
5111
+ // if(!node.expanded){
5112
+ // return "skip";
5113
+ // }
5114
+ // });
5115
+ // }
4643
5116
 
4644
5117
  /**
4645
- * Dimm or hide nodes.
5118
+ * [ext-filter] Dimm or hide nodes.
4646
5119
  *
4647
5120
  * @param {function | string} filter
4648
5121
  * @returns {integer} count
4649
- * @lends Fancytree.prototype
5122
+ * @alias Fancytree#applyFilter
4650
5123
  * @requires jquery.fancytree.filter.js
4651
5124
  */
4652
5125
  $.ui.fancytree._FancytreeClass.prototype.applyFilter = function(filter){
4653
5126
  var match, re,
4654
5127
  count = 0,
5128
+ hideMode = this.options.filter.mode === "hide",
4655
5129
  leavesOnly = this.options.filter.leavesOnly;
4656
- // Reset current filter
4657
- this.visit(function(node){
4658
- delete node.match;
4659
- delete node.subMatch;
4660
- });
4661
5130
 
4662
5131
  // Default to 'match title substring (not case sensitive)'
4663
5132
  if(typeof filter === "string"){
@@ -4670,38 +5139,49 @@ $.ui.fancytree._FancytreeClass.prototype.applyFilter = function(filter){
4670
5139
 
4671
5140
  this.enableFilter = true;
4672
5141
  this.$div.addClass("fancytree-ext-filter");
4673
- if( this.options.filter.mode === "hide"){
5142
+ if( hideMode ){
4674
5143
  this.$div.addClass("fancytree-ext-filter-hide");
5144
+ } else {
5145
+ this.$div.addClass("fancytree-ext-filter-dimm");
4675
5146
  }
5147
+ // Reset current filter
5148
+ this.visit(function(node){
5149
+ // node.hide = hideMode && true;
5150
+ delete node.match;
5151
+ delete node.subMatch;
5152
+ });
5153
+ // Adjust node.hide, .match, .subMatch flags
4676
5154
  this.visit(function(node){
4677
5155
  if ((!leavesOnly || node.children == null) && filter(node)) {
4678
5156
  count++;
5157
+ // node.hide = false;
4679
5158
  node.match = true;
4680
5159
  node.visitParents(function(p){
5160
+ // p.hide = false;
4681
5161
  p.subMatch = true;
4682
5162
  });
4683
5163
  }
4684
5164
  });
5165
+ // Redraw
4685
5166
  this.render();
4686
5167
  return count;
4687
5168
  };
4688
5169
 
4689
5170
  /**
4690
- * Reset the filter.
5171
+ * [ext-filter] Reset the filter.
4691
5172
  *
4692
- * @lends Fancytree.prototype
5173
+ * @alias Fancytree#applyFilter
4693
5174
  * @requires jquery.fancytree.filter.js
4694
5175
  */
4695
5176
  $.ui.fancytree._FancytreeClass.prototype.clearFilter = function(){
4696
5177
  this.visit(function(node){
5178
+ // delete node.hide;
4697
5179
  delete node.match;
4698
5180
  delete node.subMatch;
4699
- $(node.li).show();
4700
5181
  });
4701
-
4702
5182
  this.enableFilter = false;
5183
+ this.$div.removeClass("fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide");
4703
5184
  this.render();
4704
- this.$div.removeClass("fancytree-ext-filter fancytree-ext-filter-hide");
4705
5185
  };
4706
5186
 
4707
5187
 
@@ -4710,7 +5190,7 @@ $.ui.fancytree._FancytreeClass.prototype.clearFilter = function(){
4710
5190
  */
4711
5191
  $.ui.fancytree.registerExtension({
4712
5192
  name: "filter",
4713
- version: "0.0.1",
5193
+ version: "0.0.2",
4714
5194
  // Default options for this extension.
4715
5195
  options: {
4716
5196
  mode: "dimm",
@@ -4729,39 +5209,168 @@ $.ui.fancytree.registerExtension({
4729
5209
  },
4730
5210
  nodeRenderStatus: function(ctx) {
4731
5211
  // Set classes for current status
4732
- var visible,
5212
+ var res,
4733
5213
  node = ctx.node,
4734
- opts = ctx.options,
4735
5214
  tree = ctx.tree,
4736
5215
  $span = $(node[tree.statusClassPropName]);
4737
5216
 
5217
+ res = this._super(ctx);
5218
+
4738
5219
  if(!$span.length){
4739
- return; // nothing to do, if node was not yet rendered
5220
+ return res; // nothing to do, if node was not yet rendered
4740
5221
  }
4741
- this._super(ctx);
4742
5222
  if(!tree.enableFilter){
5223
+ return res;
5224
+ }
5225
+ $span.toggleClass("fancytree-match", !!node.match);
5226
+ $span.toggleClass("fancytree-submatch", !!node.subMatch);
5227
+ $span.toggleClass("fancytree-hide", !(node.match || node.subMatch));
5228
+
5229
+ // if(opts.filter.mode === "hide"){
5230
+ // // visible = !!(node.match || node.subMatch);
5231
+ // visible = !node.hide;
5232
+ // node.debug(node.title + ": visible=" + visible);
5233
+ // if( node.li ) {
5234
+ // $(node.li).toggle(visible);
5235
+ // } else if( node.tr ) {
5236
+ // // Show/hide all rows that are structural descendants of `parent`
5237
+ // $(node.tr).toggle(visible);
5238
+ // // if( !visible ) {
5239
+ // // setChildRowVisibility(node, visible);
5240
+ // // }
5241
+ // }
5242
+ // }
5243
+ return res;
5244
+ }
5245
+ });
5246
+ }(jQuery, window, document));
5247
+
5248
+ /*!
5249
+ * jquery.fancytree.glyph.js
5250
+ *
5251
+ * Use glyph fonts as instead of icon sprites.
5252
+ * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
5253
+ *
5254
+ * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
5255
+ *
5256
+ * Released under the MIT license
5257
+ * https://github.com/mar10/fancytree/wiki/LicenseInfo
5258
+ *
5259
+ * @version 2.0.0-11
5260
+ * @date 2014-04-27T22:28
5261
+ */
5262
+
5263
+ ;(function($, window, document, undefined) {
5264
+
5265
+ "use strict";
5266
+
5267
+ /* *****************************************************************************
5268
+ * Private functions and variables
5269
+ */
5270
+
5271
+ function _getIcon(opts, type){
5272
+ return opts.map[type];
5273
+ }
5274
+
5275
+ $.ui.fancytree.registerExtension({
5276
+ name: "glyph",
5277
+ version: "0.0.2",
5278
+ // Default options for this extension.
5279
+ options: {
5280
+ prefix: "icon-",
5281
+ extra: null,
5282
+ map: {
5283
+ doc: "icon-file-alt",
5284
+ docOpen: "icon-file-alt",
5285
+ checkbox: "icon-check-empty",
5286
+ checkboxSelected: "icon-check",
5287
+ checkboxUnknown: "icon-check icon-muted",
5288
+ error: "icon-exclamation-sign",
5289
+ expanderClosed: "icon-caret-right",
5290
+ expanderLazy: "icon-angle-right",
5291
+ // expanderLazy: "icon-refresh icon-spin",
5292
+ expanderOpen: "icon-caret-down",
5293
+ folder: "icon-folder-close-alt",
5294
+ folderOpen: "icon-folder-open-alt",
5295
+ loading: "icon-refresh icon-spin",
5296
+ // loading: "icon-spinner icon-spin"
5297
+ noExpander: ""
5298
+ },
5299
+ icon: null // TODO: allow callback here
5300
+ },
5301
+ // Overide virtual methods for this extension.
5302
+ // `this` : is this extension object
5303
+ // `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
5304
+ treeInit: function(ctx){
5305
+ var tree = ctx.tree;
5306
+ this._super(ctx);
5307
+ tree.$container.addClass("fancytree-ext-glyph");
5308
+ },
5309
+ nodeRenderStatus: function(ctx) {
5310
+ var icon, span,
5311
+ node = ctx.node,
5312
+ opts = ctx.options.glyph,
5313
+ // callback = opts.icon,
5314
+ map = opts.map
5315
+ // prefix = opts.prefix
5316
+ // $span = $(node.span)
5317
+ ;
5318
+
5319
+ this._super(ctx);
5320
+
5321
+ if( node.isRoot() ){
4743
5322
  return;
4744
5323
  }
4745
- if( node.match ){
4746
- $span.addClass("fancytree-match");
4747
- }else{
4748
- $span.removeClass("fancytree-match");
5324
+
5325
+ span = $("span.fancytree-expander", node.span).get(0);
5326
+ if( span ){
5327
+ if( node.isLoading() ){
5328
+ icon = "loading";
5329
+ }else if( node.expanded ){
5330
+ icon = "expanderOpen";
5331
+ }else if( node.isUndefined() ){
5332
+ icon = "expanderLazy";
5333
+ }else if( node.hasChildren() ){
5334
+ icon = "expanderClosed";
5335
+ }else{
5336
+ icon = "noExpander";
5337
+ }
5338
+ span.className = "fancytree-expander " + map[icon];
5339
+ }
5340
+
5341
+ span = $("span.fancytree-checkbox", node.tr || node.span).get(0);
5342
+ if( span ){
5343
+ icon = node.selected ? "checkboxSelected" : (node.partsel ? "checkboxUnknown" : "checkbox");
5344
+ span.className = "fancytree-checkbox " + map[icon];
4749
5345
  }
4750
- if( node.subMatch ){
4751
- $span.addClass("fancytree-submatch");
5346
+
5347
+ span = $("span.fancytree-icon", node.span).get(0);
5348
+ if( span ){
5349
+ if( node.folder ){
5350
+ icon = node.expanded ? _getIcon(opts, "folderOpen") : _getIcon(opts, "folder");
5351
+ }else{
5352
+ icon = node.expanded ? _getIcon(opts, "docOpen") : _getIcon(opts, "doc");
5353
+ }
5354
+ span.className = "fancytree-icon " + icon;
5355
+ }
5356
+ },
5357
+ nodeSetStatus: function(ctx, status, message, details) {
5358
+ var span,
5359
+ opts = ctx.options.glyph,
5360
+ node = ctx.node;
5361
+
5362
+ this._super(ctx, status, message, details);
5363
+
5364
+ if(node.parent){
5365
+ span = $("span.fancytree-expander", node.span).get(0);
4752
5366
  }else{
4753
- $span.removeClass("fancytree-submatch");
4754
- }
4755
- if(opts.filter.mode === "hide"){
4756
- visible = !!(node.match || node.subMatch);
4757
- node.debug(node.title + ": visible=" + visible);
4758
- $(node.li).toggle(visible);
4759
- // TODO: handle ext-table.
4760
- // The following is too simple, since we have to hide all TRs that
4761
- // belong to that parent:
4762
- // if( node.tr ) {
4763
- // $(node.tr).toggle(visible);
4764
- // }
5367
+ span = $(".fancytree-statusnode-wait, .fancytree-statusnode-error", node[this.nodeContainerAttrName]).find("span.fancytree-expander").get(0);
5368
+ }
5369
+ if( status === "loading"){
5370
+ // $("span.fancytree-expander", ctx.node.span).addClass(_getIcon(opts, "loading"));
5371
+ span.className = "fancytree-expander " + _getIcon(opts, "loading");
5372
+ }else if( status === "error"){
5373
+ span.className = "fancytree-expander " + _getIcon(opts, "error");
4765
5374
  }
4766
5375
  }
4767
5376
  });
@@ -4778,8 +5387,8 @@ $.ui.fancytree.registerExtension({
4778
5387
  * Released under the MIT license
4779
5388
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
4780
5389
  *
4781
- * @version 2.0.0-6
4782
- * @date 2014-02-10T10:52
5390
+ * @version 2.0.0-11
5391
+ * @date 2014-04-27T22:28
4783
5392
  */
4784
5393
 
4785
5394
  ;(function($, window, document, undefined) {
@@ -4805,21 +5414,80 @@ var KC = $.ui.keyCode,
4805
5414
  };
4806
5415
 
4807
5416
 
5417
+ /* Calculate TD column index (considering colspans).*/
5418
+ function getColIdx($tr, $td) {
5419
+ var colspan,
5420
+ td = $td.get(0),
5421
+ idx = 0;
5422
+
5423
+ $tr.children().each(function () {
5424
+ if( this === td ) {
5425
+ return false;
5426
+ }
5427
+ colspan = $(this).prop("colspan");
5428
+ idx += colspan ? colspan : 1;
5429
+ });
5430
+ return idx;
5431
+ }
5432
+
5433
+
5434
+ /* Find TD at given column index (considering colspans).*/
5435
+ function findTdAtColIdx($tr, colIdx) {
5436
+ var colspan,
5437
+ res = null,
5438
+ idx = 0;
5439
+
5440
+ $tr.children().each(function () {
5441
+ if( idx >= colIdx ) {
5442
+ res = $(this);
5443
+ return false;
5444
+ }
5445
+ colspan = $(this).prop("colspan");
5446
+ idx += colspan ? colspan : 1;
5447
+ });
5448
+ return res;
5449
+ }
5450
+
5451
+
5452
+ /* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
4808
5453
  function findNeighbourTd($target, keyCode){
4809
- var $td = $target.closest("td");
5454
+ var $tr, colIdx,
5455
+ $td = $target.closest("td"),
5456
+ $tdNext = null;
5457
+
4810
5458
  switch( keyCode ){
4811
5459
  case KC.LEFT:
4812
- return $td.prev();
5460
+ $tdNext = $td.prev();
5461
+ break;
4813
5462
  case KC.RIGHT:
4814
- return $td.next();
5463
+ $tdNext = $td.next();
5464
+ break;
4815
5465
  case KC.UP:
4816
- return $td.parent().prevAll(":visible").first().find("td").eq($td.index());
4817
5466
  case KC.DOWN:
4818
- return $td.parent().nextAll(":visible").first().find("td").eq($td.index());
5467
+ $tr = $td.parent();
5468
+ colIdx = getColIdx($tr, $td);
5469
+ while( true ) {
5470
+ $tr = (keyCode === KC.UP) ? $tr.prev() : $tr.next();
5471
+ if( !$tr.length ) {
5472
+ break;
5473
+ }
5474
+ // Skip hidden rows
5475
+ if( $tr.is(":hidden") ) {
5476
+ continue;
5477
+ }
5478
+ // Find adjacent cell in the same column
5479
+ $tdNext = findTdAtColIdx($tr, colIdx);
5480
+ // Skip cells that don't conatain a focusable element
5481
+ if( $tdNext && $tdNext.find(":input").length ) {
5482
+ break;
5483
+ }
5484
+ }
5485
+ break;
4819
5486
  }
4820
- return null;
5487
+ return $tdNext;
4821
5488
  }
4822
5489
 
5490
+
4823
5491
  /*******************************************************************************
4824
5492
  * Extension code
4825
5493
  */
@@ -4887,7 +5555,7 @@ $.ui.fancytree.registerExtension({
4887
5555
 
4888
5556
  // jQuery
4889
5557
  inputType = $target.is(":input:enabled") ? $target.prop("type") : null;
4890
- ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
5558
+ // ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
4891
5559
 
4892
5560
  if( inputType && opts.handleCursorKeys ){
4893
5561
  handleKeys = NAV_KEYS[inputType];
@@ -4921,8 +5589,8 @@ $.ui.fancytree.registerExtension({
4921
5589
  * Released under the MIT license
4922
5590
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
4923
5591
  *
4924
- * @version 2.0.0-6
4925
- * @date 2014-02-10T10:52
5592
+ * @version 2.0.0-11
5593
+ * @date 2014-04-27T22:28
4926
5594
  */
4927
5595
 
4928
5596
  ;(function($, window, document, undefined) {
@@ -4945,12 +5613,72 @@ var ACTIVE = "active",
4945
5613
  FOCUS = "focus",
4946
5614
  SELECTED = "selected";
4947
5615
 
5616
+
5617
+ /* Recursively load lazy nodes
5618
+ * @param {string} mode 'load', 'expand', false
5619
+ */
5620
+ function _loadLazyNodes(tree, instData, keyList, mode, dfd) {
5621
+ var i, key, l, node,
5622
+ foundOne = false,
5623
+ deferredList = [],
5624
+ // lazyNodeList = [],
5625
+ missingKeyList = []; //keyList.slice(0),
5626
+
5627
+ keyList = keyList || [];
5628
+ // expand = expand !== false;
5629
+ dfd = dfd || $.Deferred();
5630
+
5631
+ for( i=0, l=keyList.length; i<l; i++ ) {
5632
+ key = keyList[i];
5633
+ node = tree.getNodeByKey(key);
5634
+ if( node ) {
5635
+ if( mode && node.isUndefined() ) {
5636
+ // lazyNodeList.push(node);
5637
+ foundOne = true;
5638
+ tree.debug("_loadLazyNodes: " + node + " is lazy: loading...");
5639
+ if( mode === "expand" ) {
5640
+ deferredList.push(node.setExpanded());
5641
+ } else {
5642
+ deferredList.push(node.load());
5643
+ }
5644
+ } else {
5645
+ tree.debug("_loadLazyNodes: " + node + " already loaded.");
5646
+ node.setExpanded();
5647
+ // node.expanded = true;
5648
+ // node.render();
5649
+ }
5650
+ } else {
5651
+ missingKeyList.push(key);
5652
+ tree.debug("_loadLazyNodes: " + node + " was not yet found.");
5653
+ }
5654
+ }
5655
+
5656
+ $.when.apply($, deferredList).always(function(){
5657
+ // All lazy-expands have finished
5658
+ if( foundOne && missingKeyList.length > 0 ) {
5659
+ // If we read new nodes from server, try to resolve yet-missing keys
5660
+ _loadLazyNodes(tree, instData, missingKeyList, mode, dfd);
5661
+ } else {
5662
+ if( missingKeyList.length ) {
5663
+ tree.warn("_loadLazyNodes: could not load those keys: ", missingKeyList);
5664
+ for( i=0, l=missingKeyList.length; i<l; i++ ) {
5665
+ key = keyList[i];
5666
+ instData._setKey(EXPANDED, keyList[i], false);
5667
+ }
5668
+ }
5669
+ dfd.resolve();
5670
+ }
5671
+ });
5672
+ return dfd;
5673
+ }
5674
+
5675
+
4948
5676
  /**
4949
- *
5677
+ * [ext-persist] Remove persistence cookies of the given type(s).
4950
5678
  * Called like
4951
5679
  * $("#tree").fancytree("getTree").clearCookies("active expanded focus selected");
4952
5680
  *
4953
- * @lends Fancytree.prototype
5681
+ * @alias Fancytree#clearCookies
4954
5682
  * @requires jquery.fancytree.persist.js
4955
5683
  */
4956
5684
  $.ui.fancytree._FancytreeClass.prototype.clearCookies = function(types){
@@ -4960,29 +5688,33 @@ $.ui.fancytree._FancytreeClass.prototype.clearCookies = function(types){
4960
5688
  types = types || "active expanded focus selected";
4961
5689
  // TODO: optimize
4962
5690
  if(types.indexOf(ACTIVE) >= 0){
4963
- $.cookie(cookiePrefix + ACTIVE, null);
5691
+ // $.cookie(cookiePrefix + ACTIVE, null);
5692
+ $.removeCookie(cookiePrefix + ACTIVE);
4964
5693
  }
4965
5694
  if(types.indexOf(EXPANDED) >= 0){
4966
- $.cookie(cookiePrefix + EXPANDED, null);
5695
+ // $.cookie(cookiePrefix + EXPANDED, null);
5696
+ $.removeCookie(cookiePrefix + EXPANDED);
4967
5697
  }
4968
5698
  if(types.indexOf(FOCUS) >= 0){
4969
- $.cookie(cookiePrefix + FOCUS, null);
5699
+ // $.cookie(cookiePrefix + FOCUS, null);
5700
+ $.removeCookie(cookiePrefix + FOCUS);
4970
5701
  }
4971
5702
  if(types.indexOf(SELECTED) >= 0){
4972
- $.cookie(cookiePrefix + SELECTED, null);
5703
+ // $.cookie(cookiePrefix + SELECTED, null);
5704
+ $.removeCookie(cookiePrefix + SELECTED);
4973
5705
  }
4974
5706
  };
4975
5707
 
4976
5708
 
4977
5709
  /**
4978
- * Return persistence information from cookies
4979
- *
4980
- * Called like
4981
- * $("#tree").fancytree("getTree").getPersistData();
4982
- *
4983
- * @lends Fancytree.prototype
4984
- * @requires jquery.fancytree.persist.js
4985
- */
5710
+ * [ext-persist] Return persistence information from cookies
5711
+ *
5712
+ * Called like
5713
+ * $("#tree").fancytree("getTree").getPersistData();
5714
+ *
5715
+ * @alias Fancytree#getPersistData
5716
+ * @requires jquery.fancytree.persist.js
5717
+ */
4986
5718
  $.ui.fancytree._FancytreeClass.prototype.getPersistData = function(){
4987
5719
  var inst = this.ext.persist,
4988
5720
  instOpts= this.options.persist,
@@ -4993,6 +5725,7 @@ $.ui.fancytree._FancytreeClass.prototype.getPersistData = function(){
4993
5725
  res[EXPANDED] = ($.cookie(inst.cookiePrefix + EXPANDED) || "").split(delim);
4994
5726
  res[SELECTED] = ($.cookie(inst.cookiePrefix + SELECTED) || "").split(delim);
4995
5727
  res[FOCUS] = $.cookie(inst.cookiePrefix + FOCUS);
5728
+ return res;
4996
5729
  };
4997
5730
 
4998
5731
 
@@ -5001,7 +5734,7 @@ $.ui.fancytree._FancytreeClass.prototype.getPersistData = function(){
5001
5734
  */
5002
5735
  $.ui.fancytree.registerExtension({
5003
5736
  name: "persist",
5004
- version: "0.0.1",
5737
+ version: "0.2.0",
5005
5738
  // Default options for this extension.
5006
5739
  options: {
5007
5740
  // appendRequestInfo: false,
@@ -5014,6 +5747,7 @@ $.ui.fancytree.registerExtension({
5014
5747
  domain: "",
5015
5748
  secure: false
5016
5749
  },
5750
+ expandLazy: false, // true: recursively expand and load lazy nodes
5017
5751
  overrideSource: false, // true: cookie takes precedence over `source` data attributes.
5018
5752
  types: "active expanded focus selected"
5019
5753
  },
@@ -5055,68 +5789,80 @@ $.ui.fancytree.registerExtension({
5055
5789
  instData.storeFocus = instOpts.types.indexOf(FOCUS) >= 0;
5056
5790
 
5057
5791
  // Bind init-handler to apply cookie state
5058
- tree.$div.bind("fancytreeinit", function(e){
5059
- var cookie,
5060
- keyList,
5061
- i,
5062
- prevFocus = $.cookie(instData.cookiePrefix + FOCUS), // record this before node.setActive() overrides it
5063
- node;
5792
+ tree.$div.bind("fancytreeinit", function(event){
5793
+ var cookie, dfd, i, keyList, node,
5794
+ prevFocus = $.cookie(instData.cookiePrefix + FOCUS); // record this before node.setActive() overrides it;
5064
5795
 
5065
5796
  tree.debug("COOKIE " + document.cookie);
5066
5797
 
5067
- if(instData.storeExpanded){
5068
- cookie = $.cookie(instData.cookiePrefix + EXPANDED);
5069
- if(cookie){
5070
- keyList = cookie.split(instOpts.cookieDelimiter);
5071
- for(i=0; i<keyList.length; i++){
5072
- node = tree.getNodeByKey(keyList[i]);
5073
- if(node){
5074
- if(node.expanded === undefined || instOpts.overrideSource && (node.expanded === false)){
5075
- // node.setExpanded();
5076
- node.expanded = true;
5077
- node.render();
5798
+ cookie = $.cookie(instData.cookiePrefix + EXPANDED);
5799
+ keyList = cookie && cookie.split(instOpts.cookieDelimiter);
5800
+
5801
+ if( instData.storeExpanded ) {
5802
+ // Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
5803
+ // Also remove expand-cookies for unmatched nodes
5804
+ dfd = _loadLazyNodes(tree, instData, keyList, instOpts.expandLazy ? "expand" : false , null);
5805
+ } else {
5806
+ // nothing to do
5807
+ dfd = new $.Deferred().resolve();
5808
+ }
5809
+
5810
+ dfd.done(function(){
5811
+ // alert("persistent expand done");
5812
+ // if(instData.storeExpanded){
5813
+ // cookie = $.cookie(instData.cookiePrefix + EXPANDED);
5814
+ // if(cookie){
5815
+ // keyList = cookie.split(instOpts.cookieDelimiter);
5816
+ // for(i=0; i<keyList.length; i++){
5817
+ // node = tree.getNodeByKey(keyList[i]);
5818
+ // if(node){
5819
+ // if(node.expanded === undefined || instOpts.overrideSource && (node.expanded === false)){
5820
+ // // node.setExpanded();
5821
+ // node.expanded = true;
5822
+ // node.render();
5823
+ // }
5824
+ // }else{
5825
+ // // node is no longer member of the tree: remove from cookie
5826
+ // instData._setKey(EXPANDED, keyList[i], false);
5827
+ // }
5828
+ // }
5829
+ // }
5830
+ // }
5831
+ if(instData.storeSelected){
5832
+ cookie = $.cookie(instData.cookiePrefix + SELECTED);
5833
+ if(cookie){
5834
+ keyList = cookie.split(instOpts.cookieDelimiter);
5835
+ for(i=0; i<keyList.length; i++){
5836
+ node = tree.getNodeByKey(keyList[i]);
5837
+ if(node){
5838
+ if(node.selected === undefined || instOpts.overrideSource && (node.selected === false)){
5839
+ // node.setSelected();
5840
+ node.selected = true;
5841
+ node.renderStatus();
5842
+ }
5843
+ }else{
5844
+ // node is no longer member of the tree: remove from cookie also
5845
+ instData._setKey(SELECTED, keyList[i], false);
5078
5846
  }
5079
- }else{
5080
- // node is no longer member of the tree: remove from cookie
5081
- instData._setKey(EXPANDED, keyList[i], false);
5082
5847
  }
5083
5848
  }
5084
5849
  }
5085
- }
5086
- if(instData.storeSelected){
5087
- cookie = $.cookie(instData.cookiePrefix + SELECTED);
5088
- if(cookie){
5089
- keyList = cookie.split(instOpts.cookieDelimiter);
5090
- for(i=0; i<keyList.length; i++){
5091
- node = tree.getNodeByKey(keyList[i]);
5850
+ if(instData.storeActive){
5851
+ cookie = $.cookie(instData.cookiePrefix + ACTIVE);
5852
+ if(cookie && (opts.persist.overrideSource || !tree.activeNode)){
5853
+ node = tree.getNodeByKey(cookie);
5092
5854
  if(node){
5093
- if(node.selected === undefined || instOpts.overrideSource && (node.selected === false)){
5094
- // node.setSelected();
5095
- node.selected = true;
5096
- node.renderStatus();
5097
- }
5098
- }else{
5099
- // node is no longer member of the tree: remove from cookie also
5100
- instData._setKey(SELECTED, keyList[i], false);
5855
+ node.setActive();
5101
5856
  }
5102
5857
  }
5103
5858
  }
5104
- }
5105
- if(instData.storeActive){
5106
- cookie = $.cookie(instData.cookiePrefix + ACTIVE);
5107
- if(cookie && (opts.persist.overrideSource || !tree.activeNode)){
5108
- node = tree.getNodeByKey(cookie);
5859
+ if(instData.storeFocus && prevFocus){
5860
+ node = tree.getNodeByKey(prevFocus);
5109
5861
  if(node){
5110
- node.setActive();
5862
+ node.setFocus();
5111
5863
  }
5112
5864
  }
5113
- }
5114
- if(instData.storeFocus && prevFocus){
5115
- node = tree.getNodeByKey(prevFocus);
5116
- if(node){
5117
- node.setFocus();
5118
- }
5119
- }
5865
+ });
5120
5866
  });
5121
5867
  // Init the tree
5122
5868
  this._super(ctx);
@@ -5128,6 +5874,7 @@ $.ui.fancytree.registerExtension({
5128
5874
  var instData = this._local,
5129
5875
  instOpts = this.options.persist;
5130
5876
 
5877
+ flag = flag !== false;
5131
5878
  this._super(ctx, flag, opts);
5132
5879
 
5133
5880
  if(instData.storeActive){
@@ -5141,6 +5888,7 @@ $.ui.fancytree.registerExtension({
5141
5888
  node = ctx.node,
5142
5889
  instData = this._local;
5143
5890
 
5891
+ flag = flag !== false;
5144
5892
  res = this._super(ctx, flag, opts);
5145
5893
 
5146
5894
  if(instData.storeExpanded){
@@ -5155,7 +5903,7 @@ $.ui.fancytree.registerExtension({
5155
5903
  this._super(ctx);
5156
5904
 
5157
5905
  if(instData.storeFocus){
5158
- $.cookie(this.cookiePrefix + FOCUS,
5906
+ $.cookie(instData.cookiePrefix + FOCUS,
5159
5907
  this.focusNode ? this.focusNode.key : null,
5160
5908
  instOpts.cookie);
5161
5909
  }
@@ -5164,6 +5912,7 @@ $.ui.fancytree.registerExtension({
5164
5912
  var node = ctx.node,
5165
5913
  instData = this._local;
5166
5914
 
5915
+ flag = flag !== false;
5167
5916
  this._super(ctx, flag);
5168
5917
 
5169
5918
  if(instData.storeSelected){
@@ -5184,8 +5933,8 @@ $.ui.fancytree.registerExtension({
5184
5933
  * Released under the MIT license
5185
5934
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
5186
5935
  *
5187
- * @version 2.0.0-6
5188
- * @date 2014-02-10T10:52
5936
+ * @version 2.0.0-11
5937
+ * @date 2014-04-27T22:28
5189
5938
  */
5190
5939
 
5191
5940
  ;(function($, window, document, undefined) {
@@ -5210,8 +5959,9 @@ function insertSiblingAfter(referenceNode, newNode) {
5210
5959
  function setChildRowVisibility(parent, flag) {
5211
5960
  parent.visit(function(node){
5212
5961
  var tr = node.tr;
5962
+ // currentFlag = node.hide ? false : flag; // fix for ext-filter
5213
5963
  if(tr){
5214
- tr.style.display = flag ? "" : "none";
5964
+ tr.style.display = (node.hide || !flag) ? "none" : "";
5215
5965
  }
5216
5966
  if(!node.expanded){
5217
5967
  return "skip";
@@ -5248,12 +5998,13 @@ function findPrevRowNode(node){
5248
5998
 
5249
5999
  $.ui.fancytree.registerExtension({
5250
6000
  name: "table",
5251
- version: "0.1.0",
6001
+ version: "0.2.0",
5252
6002
  // Default options for this extension.
5253
6003
  options: {
5254
- indentation: 16, // indent every node level by 16px
5255
- nodeColumnIdx: 0, // render node expander, icon, and title to column #0
5256
- checkboxColumnIdx: null // render the checkboxes into the 1st column
6004
+ checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
6005
+ customStatus: false, // true: generate renderColumns events for status nodes
6006
+ indentation: 16, // indent every node level by 16px
6007
+ nodeColumnIdx: 0 // render node expander, icon, and title to this column (default: #0)
5257
6008
  },
5258
6009
  // Overide virtual methods for this extension.
5259
6010
  // `this` : is this extension object
@@ -5269,7 +6020,7 @@ $.ui.fancytree.registerExtension({
5269
6020
  $(tree.tbody).empty();
5270
6021
 
5271
6022
  tree.rowFragment = document.createDocumentFragment();
5272
- $row = $("<tr>");
6023
+ $row = $("<tr />");
5273
6024
  tdRole = "";
5274
6025
  if(ctx.options.aria){
5275
6026
  $row.attr("role", "row");
@@ -5277,9 +6028,9 @@ $.ui.fancytree.registerExtension({
5277
6028
  }
5278
6029
  for(i=0; i<tree.columnCount; i++) {
5279
6030
  if(ctx.options.table.nodeColumnIdx === i){
5280
- $row.append("<td" + tdRole + "><span class='fancytree-node'></span></td>");
6031
+ $row.append("<td" + tdRole + "><span class='fancytree-node' /></td>");
5281
6032
  }else{
5282
- $row.append("<td" + tdRole + ">");
6033
+ $row.append("<td" + tdRole + " />");
5283
6034
  }
5284
6035
  }
5285
6036
  tree.rowFragment.appendChild($row.get(0));
@@ -5336,13 +6087,19 @@ $.ui.fancytree.registerExtension({
5336
6087
  if( !_recursive ){
5337
6088
  ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
5338
6089
  }
5339
- $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode);
6090
+ // $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
5340
6091
  if( !isRootNode ){
5341
6092
  if(!node.tr){
6093
+ if( ctx.hasCollapsedParents /*&& !node.parent.tr*/ ) {
6094
+ // #166: we assume that the parent will be (recursively) rendered
6095
+ // later anyway.
6096
+ node.debug("nodeRender ignored due to unrendered parent");
6097
+ return;
6098
+ }
5342
6099
  // Create new <tr> after previous row
5343
6100
  newRow = tree.rowFragment.firstChild.cloneNode(true);
5344
6101
  prevNode = findPrevRowNode(node);
5345
- // $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
6102
+ // $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
5346
6103
  _assert(prevNode);
5347
6104
  if(collapsed === true && _recursive){
5348
6105
  // hide all child rows, so we can use an animation to show it later
@@ -5377,11 +6134,16 @@ $.ui.fancytree.registerExtension({
5377
6134
  opts.createNode.call(tree, {type: "createNode"}, ctx);
5378
6135
  }
5379
6136
  } else {
5380
- // Set icon, link, and title (normally this is only required on initial render)
5381
- //this.nodeRenderTitle(ctx);
6137
+ if( force ) {
6138
+ // Set icon, link, and title (normally this is only required on initial render)
6139
+ this.nodeRenderTitle(ctx); // triggers renderColumns()
6140
+ } else {
6141
+ // Update element classes according to node state
6142
+ this.nodeRenderStatus(ctx);
6143
+ }
5382
6144
  }
5383
6145
  }
5384
- // Allow tweaking after node state was rendered
6146
+ // Allow tweaking after node state was rendered
5385
6147
  // tree._triggerNodeEvent("renderNode", ctx);
5386
6148
  if ( opts.renderNode ){
5387
6149
  opts.renderNode.call(tree, {type: "renderNode"}, ctx);
@@ -5418,9 +6180,9 @@ $.ui.fancytree.registerExtension({
5418
6180
  });
5419
6181
  }
5420
6182
  // Update element classes according to node state
5421
- if(!isRootNode){
5422
- this.nodeRenderStatus(ctx);
5423
- }
6183
+ // if(!isRootNode){
6184
+ // this.nodeRenderStatus(ctx);
6185
+ // }
5424
6186
  },
5425
6187
  nodeRenderTitle: function(ctx, title) {
5426
6188
  var $cb,
@@ -5429,13 +6191,19 @@ $.ui.fancytree.registerExtension({
5429
6191
 
5430
6192
  this._super(ctx);
5431
6193
  // Move checkbox to custom column
5432
- if(opts.checkbox && opts.table.checkboxColumnIdx != null){
6194
+ if(opts.checkbox && opts.table.checkboxColumnIdx != null ){
5433
6195
  $cb = $("span.fancytree-checkbox", node.span).detach();
5434
6196
  $(node.tr).find("td:first").html($cb);
5435
6197
  }
5436
6198
  // Let user code write column content
5437
6199
  // ctx.tree._triggerNodeEvent("renderColumns", node);
5438
- if ( opts.renderColumns ){
6200
+ // Update element classes according to node state
6201
+ if( ! node.isRoot() ){
6202
+ this.nodeRenderStatus(ctx);
6203
+ }
6204
+ if( !opts.table.customStatus && node.isStatusNode() ) {
6205
+ // default rendering for status node: leave other cells empty
6206
+ } else if ( opts.renderColumns ) {
5439
6207
  opts.renderColumns.call(ctx.tree, {type: "renderColumns"}, ctx);
5440
6208
  }
5441
6209
  },
@@ -5453,21 +6221,45 @@ $.ui.fancytree.registerExtension({
5453
6221
  },
5454
6222
  /* Expand node, return Deferred.promise. */
5455
6223
  nodeSetExpanded: function(ctx, flag, opts) {
5456
- return this._super(ctx, flag, opts).always(function () {
6224
+ var dfd = new $.Deferred(),
6225
+ prevOpts = opts || {};
6226
+
6227
+ opts = $.extend({}, opts, {noEvents: true, noAnimation: true});
6228
+
6229
+ function _afterExpand(ok) {
5457
6230
  flag = (flag !== false);
5458
6231
  setChildRowVisibility(ctx.node, flag);
6232
+ if( !prevOpts.noEvents ) {
6233
+ ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
6234
+ }
6235
+ if( ok ) {
6236
+ dfd.resolveWith(ctx.node);
6237
+ } else {
6238
+ dfd.rejectWith(ctx.node);
6239
+ }
6240
+ }
6241
+ this._super(ctx, flag, opts).done(function () {
6242
+ _afterExpand(true);
6243
+ }).fail(function () {
6244
+ _afterExpand(false);
5459
6245
  });
6246
+ return dfd.promise();
5460
6247
  },
5461
6248
  nodeSetStatus: function(ctx, status, message, details) {
5462
6249
  if(status === "ok"){
5463
6250
  var node = ctx.node,
5464
6251
  firstChild = ( node.children ? node.children[0] : null );
5465
- if ( firstChild && firstChild.isStatusNode ) {
6252
+ if ( firstChild && firstChild.isStatusNode() ) {
5466
6253
  $(firstChild.tr).remove();
5467
6254
  }
5468
6255
  }
5469
6256
  this._super(ctx, status, message, details);
5470
- }/*,
6257
+ },
6258
+ treeClear: function(ctx) {
6259
+ this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
6260
+ return this._super(ctx);
6261
+ }
6262
+ /*,
5471
6263
  treeSetFocus: function(ctx, flag) {
5472
6264
  // alert("treeSetFocus" + ctx.tree.$container);
5473
6265
  ctx.tree.$container.focus();
@@ -5489,8 +6281,8 @@ $.ui.fancytree.registerExtension({
5489
6281
  * Released under the MIT license
5490
6282
  * https://github.com/mar10/fancytree/wiki/LicenseInfo
5491
6283
  *
5492
- * @version 2.0.0-6
5493
- * @date 2014-02-10T10:52
6284
+ * @version 2.0.0-11
6285
+ * @date 2014-04-27T22:28
5494
6286
  */
5495
6287
 
5496
6288
  ;(function($, window, document, undefined) {