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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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) {