nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / jqTree / src / tree.jquery.coffee
1 __version__ = require './version'
2 drag_and_drop_handler = require './drag_and_drop_handler'
3 ElementsRenderer = require './elements_renderer'
4 KeyHandler = require './key_handler'
5 MouseWidget = require './mouse.widget'
6 SaveStateHandler = require './save_state_handler'
7 ScrollHandler = require './scroll_handler'
8 SelectNodeHandler = require './select_node_handler'
9 SimpleWidget = require './simple.widget'
10
11 node_module = require './node'
12 Node = node_module.Node
13 Position = node_module.Position
14
15 util_module = require './util'
16
17 {isFunction} = util_module
18
19 {BorderDropHint,FolderElement,GhostDropHint,NodeElement} = require './node_element'
20
21 {DragAndDropHandler, DragElement, HitAreasGenerator} = drag_and_drop_handler
22
23
24 $ = jQuery
25
26
27 class JqTreeWidget extends MouseWidget
28     BorderDropHint: BorderDropHint
29     DragElement: DragElement
30     DragAndDropHandler: DragAndDropHandler
31     ElementsRenderer: ElementsRenderer
32     GhostDropHint: GhostDropHint
33     HitAreasGenerator: HitAreasGenerator
34     Node: Node
35     SaveStateHandler: SaveStateHandler
36     ScrollHandler: ScrollHandler
37     SelectNodeHandler: SelectNodeHandler
38
39     defaults:
40         autoOpen: false  # true / false / int (open n levels starting at 0)
41         saveState: false  # true / false / string (cookie name)
42         dragAndDrop: false
43         selectable: true
44         useContextMenu: true
45         onCanSelectNode: null
46         onSetStateFromStorage: null
47         onGetStateFromStorage: null
48         onCreateLi: null
49         onIsMoveHandle: null
50         onCanMove: null  # Can this node be moved? function(node)
51         onCanMoveTo: null  # Can this node be moved to this position? function(moved_node, target_node, position)
52         onLoadFailed: null
53         autoEscape: true
54         dataUrl: null
55         closedIcon: null  # The symbol to use for a closed node - ► BLACK RIGHT-POINTING POINTER  http://www.fileformat.info/info/unicode/char/25ba/index.htm
56         openedIcon: '▼'  # The symbol to use for an open node - ▼ BLACK DOWN-POINTING TRIANGLE  http://www.fileformat.info/info/unicode/char/25bc/index.htm
57         slide: true  # must display slide animation?
58         nodeClass: Node
59         dataFilter: null
60         keyboardSupport: true
61         openFolderDelay: 500  # The delay for opening a folder during drag and drop; the value is in milliseconds
62         rtl: null  # right-to-left support; true / false (default)
63         onDragMove: null
64         onDragStop: null
65         buttonLeft: true
66         onLoading: null
67
68     toggle: (node, slide=null) ->
69         if slide == null
70             slide = @options.slide
71
72         if node.is_open
73             @closeNode(node, slide)
74         else
75             @openNode(node, slide)
76
77         return @element
78
79     getTree: ->
80         return @tree
81
82     selectNode: (node) ->
83         @_selectNode(node, false)
84         return @element
85
86     _selectNode: (node, must_toggle=false) ->
87         if not @select_node_handler
88             return
89
90         canSelect = =>
91             if @options.onCanSelectNode
92                 return @options.selectable and @options.onCanSelectNode(node)
93             else
94                 return @options.selectable
95
96         openParents = =>
97             parent = node.parent
98
99             if parent and parent.parent and not parent.is_open
100                 @openNode(parent, false)
101
102         saveState = =>
103             if @options.saveState
104                 @save_state_handler.saveState()
105
106         if not node
107             # Called with empty node -> deselect current node
108             @_deselectCurrentNode()
109             saveState()
110             return
111
112         if not canSelect()
113             return
114
115         if @select_node_handler.isNodeSelected(node)
116             if must_toggle
117                 @_deselectCurrentNode()
118                 @_triggerEvent(
119                     'tree.select',
120                     node: null,
121                     previous_node: node
122                 )
123         else
124             deselected_node = @getSelectedNode()
125             @_deselectCurrentNode()
126             @addToSelection(node)
127             @_triggerEvent('tree.select', node: node, deselected_node: deselected_node)
128             openParents()
129
130         saveState()
131
132     getSelectedNode: ->
133         if @select_node_handler
134             return @select_node_handler.getSelectedNode()
135         else
136             return null
137
138     toJson: ->
139         return JSON.stringify(
140             @tree.getData()
141         )
142
143     loadData: (data, parent_node) ->
144         @_loadData(data, parent_node)
145         return @element
146
147     ###
148     signatures:
149     - loadDataFromUrl(url, parent_node=null, on_finished=null)
150         loadDataFromUrl('/my_data');
151         loadDataFromUrl('/my_data', node1);
152         loadDataFromUrl('/my_data', node1, function() { console.log('finished'); });
153         loadDataFromUrl('/my_data', null, function() { console.log('finished'); });
154
155     - loadDataFromUrl(parent_node=null, on_finished=null)
156         loadDataFromUrl();
157         loadDataFromUrl(node1);
158         loadDataFromUrl(null, function() { console.log('finished'); });
159         loadDataFromUrl(node1, function() { console.log('finished'); });
160     ###
161     loadDataFromUrl: (param1, param2, param3) ->
162         if $.type(param1) == 'string'
163             # first parameter is url
164             @_loadDataFromUrl(param1, param2, param3)
165         else
166             # first parameter is not url
167             @_loadDataFromUrl(null, param1, param2)
168
169         return @element
170
171     reload: (on_finished) ->
172         @_loadDataFromUrl(null, null, on_finished)
173         return @element
174
175     _loadDataFromUrl: (url_info, parent_node, on_finished) ->
176         $el = null
177
178         addLoadingClass = =>
179             if parent_node
180                 $el = $(parent_node.element)
181             else
182                 $el = @element
183
184             $el.addClass('jqtree-loading')
185             @_notifyLoading(true, parent_node, $el)
186
187         removeLoadingClass = =>
188             if $el
189                 $el.removeClass('jqtree-loading')
190
191                 @_notifyLoading(false, parent_node, $el)
192
193         parseUrlInfo = ->
194             if $.type(url_info) == 'string'
195                 return url: url_info
196
197             if not url_info.method
198                 url_info.method = 'get'
199
200             return url_info
201
202         handeLoadData = (data) =>
203             removeLoadingClass()
204             @_loadData(data, parent_node)
205
206             if on_finished and $.isFunction(on_finished)
207                 on_finished()
208
209         handleSuccess = (response) =>
210             if $.isArray(response) or typeof response == 'object'
211                 data = response
212             else if data?
213                 data = $.parseJSON(response)
214             else
215                 data = []
216
217             if @options.dataFilter
218                 data = @options.dataFilter(data)
219
220             handeLoadData(data)
221
222         handleError = (response) =>
223             removeLoadingClass()
224
225             if @options.onLoadFailed
226                 @options.onLoadFailed(response)
227
228         loadDataFromUrlInfo = ->
229             url_info = parseUrlInfo()
230
231             $.ajax(
232                 $.extend(
233                     {},
234                     url_info,
235                     {
236                         method: if url_info.method? then url_info.method.toUpperCase() else 'GET',
237                         cache: false,
238                         dataType: 'json',
239                         success: handleSuccess,
240                         error: handleError
241                     }
242                 )
243             )
244
245         if not url_info
246             # Generate url for node
247             url_info = @_getDataUrlInfo(parent_node)
248
249         addLoadingClass()
250
251         if not url_info
252             removeLoadingClass()
253             return
254         else if $.isArray(url_info)
255             handeLoadData(url_info)
256             return
257         else
258             loadDataFromUrlInfo()
259             return
260
261     _loadData: (data, parent_node=null) ->
262         deselectNodes = =>
263             if @select_node_handler
264                 selected_nodes_under_parent = @select_node_handler.getSelectedNodesUnder(parent_node)
265                 for n in selected_nodes_under_parent
266                     @select_node_handler.removeFromSelection(n)
267
268             return null
269
270         loadSubtree = =>
271             parent_node.loadFromData(data)
272
273             parent_node.load_on_demand = false
274             parent_node.is_loading = false
275
276             @_refreshElements(parent_node)
277
278         if not data
279             return
280
281         @_triggerEvent('tree.load_data', tree_data: data)
282
283         if not parent_node
284             @_initTree(data)
285         else
286             deselectNodes()
287             loadSubtree()
288
289         if @isDragging()
290             @dnd_handler.refresh()
291
292     getNodeById: (node_id) ->
293         return @tree.getNodeById(node_id)
294
295     getNodeByName: (name) ->
296         return @tree.getNodeByName(name)
297
298     getNodesByProperty: (key, value) ->
299         return @tree.getNodesByProperty(key, value)
300
301     getNodeByHtmlElement: (element) ->
302         return @_getNode($(element))
303
304     getNodeByCallback: (callback) ->
305         return @tree.getNodeByCallback(callback)
306
307     openNode: (node, slide_param=null, on_finished_param=null) ->
308         parseParams = =>
309             if isFunction(slide_param)
310                 on_finished = slide_param
311                 slide = null
312             else
313                 slide = slide_param
314                 on_finished = on_finished_param
315
316             if slide == null
317                 slide = @options.slide
318
319             return [slide, on_finished]
320
321         [slide, on_finished] = parseParams()
322
323         if node
324             @_openNode(node, slide, on_finished)
325
326         return @element
327
328     _openNode: (node, slide=true, on_finished) ->
329         doOpenNode = (_node, _slide, _on_finished) =>
330             folder_element = new FolderElement(_node, this)
331             folder_element.open(_on_finished, _slide)
332
333         if node.isFolder()
334             if node.load_on_demand
335                 @_loadFolderOnDemand(node, slide, on_finished)
336             else
337                 parent = node.parent
338
339                 while parent
340                     # nb: do not open root element
341                     if parent.parent
342                         doOpenNode(parent, false, null)
343                     parent = parent.parent
344
345                 doOpenNode(node, slide, on_finished)
346                 @_saveState()
347
348     _loadFolderOnDemand: (node, slide=true, on_finished) ->
349         node.is_loading = true
350
351         @_loadDataFromUrl(
352             null,
353             node,
354             =>
355                 @_openNode(node, slide, on_finished)
356         )
357
358     closeNode: (node, slide=null) ->
359         if slide == null
360             slide = @options.slide
361
362         if node.isFolder()
363             new FolderElement(node, this).close(slide)
364
365             @_saveState()
366
367         return @element
368
369     isDragging: ->
370         if @dnd_handler
371             return @dnd_handler.is_dragging
372         else
373             return false
374
375     refreshHitAreas: ->
376         @dnd_handler.refresh()
377         return @element
378
379     addNodeAfter: (new_node_info, existing_node) ->
380         new_node = existing_node.addAfter(new_node_info)
381         @_refreshElements(existing_node.parent)
382         return new_node
383
384     addNodeBefore: (new_node_info, existing_node) ->
385         new_node = existing_node.addBefore(new_node_info)
386         @_refreshElements(existing_node.parent)
387         return new_node
388
389     addParentNode: (new_node_info, existing_node) ->
390         new_node = existing_node.addParent(new_node_info)
391         @_refreshElements(new_node.parent)
392         return new_node
393
394     removeNode: (node) ->
395         parent = node.parent
396         if parent
397             @select_node_handler.removeFromSelection(node, true)  # including children
398
399             node.remove()
400             @_refreshElements(parent)
401
402         return @element
403
404     appendNode: (new_node_info, parent_node) ->
405         parent_node = parent_node or @tree
406
407         node = parent_node.append(new_node_info)
408
409         @_refreshElements(parent_node)
410
411         return node
412
413     prependNode: (new_node_info, parent_node) ->
414         if not parent_node
415             parent_node = @tree
416
417         node = parent_node.prepend(new_node_info)
418
419         @_refreshElements(parent_node)
420
421         return node
422
423     updateNode: (node, data) ->
424         id_is_changed = data.id and data.id != node.id
425
426         if id_is_changed
427             @tree.removeNodeFromIndex(node)
428
429         node.setData(data)
430
431         if id_is_changed
432             @tree.addNodeToIndex(node)
433
434         if typeof data == 'object' and data.children
435             node.removeChildren()
436
437             if data.children.length
438                 node.loadFromData(data.children)
439
440         @renderer.renderFromNode(node)
441         @_selectCurrentNode()
442
443         return @element
444
445     moveNode: (node, target_node, position) ->
446         position_index = Position.nameToIndex(position)
447
448         @tree.moveNode(node, target_node, position_index)
449         @_refreshElements()
450         return @element
451
452     getStateFromStorage: ->
453         return @save_state_handler.getStateFromStorage()
454
455     addToSelection: (node) ->
456         if node
457             @select_node_handler.addToSelection(node)
458
459             @_getNodeElementForNode(node).select()
460             @_saveState()
461
462         return @element
463
464     getSelectedNodes: ->
465         return @select_node_handler.getSelectedNodes()
466
467     isNodeSelected: (node) ->
468         return @select_node_handler.isNodeSelected(node)
469
470     removeFromSelection: (node) ->
471         @select_node_handler.removeFromSelection(node)
472
473         @_getNodeElementForNode(node).deselect()
474         @_saveState()
475         return @element
476
477     scrollToNode: (node) ->
478         $element = $(node.element)
479         top = $element.offset().top - @$el.offset().top
480
481         @scroll_handler.scrollTo(top)
482         return @element
483
484     getState: ->
485         return @save_state_handler.getState()
486
487     setState: (state) ->
488         @save_state_handler.setInitialState(state)
489         @_refreshElements()
490         return @element
491
492     setOption: (option, value) ->
493         @options[option] = value
494         return @element
495
496     moveDown: ->
497         if @key_handler
498             @key_handler.moveDown()
499
500         return @element
501
502     moveUp: ->
503         if @key_handler
504             @key_handler.moveUp()
505
506         return @element
507
508     getVersion: ->
509         return __version__
510
511     _init: ->
512         super()
513
514         @element = @$el
515         @mouse_delay = 300
516         @is_initialized = false
517
518         @options.rtl = @_getRtlOption()
519
520         if !@options.closedIcon
521             @options.closedIcon = @_getDefaultClosedIcon()
522
523         @renderer = new ElementsRenderer(this)
524
525         if SaveStateHandler?
526             @save_state_handler = new SaveStateHandler(this)
527         else
528             @options.saveState = false
529
530         if SelectNodeHandler?
531             @select_node_handler = new SelectNodeHandler(this)
532
533         if DragAndDropHandler?
534             @dnd_handler = new DragAndDropHandler(this)
535         else
536             @options.dragAndDrop = false
537
538         if ScrollHandler?
539             @scroll_handler = new ScrollHandler(this)
540
541         if KeyHandler? and SelectNodeHandler?
542             @key_handler = new KeyHandler(this)
543
544         @_initData()
545
546         @element.click($.proxy(@_click, this))
547         @element.dblclick($.proxy(@_dblclick, this))
548
549         if @options.useContextMenu
550             @element.bind('contextmenu', $.proxy(@_contextmenu, this))
551
552     _deinit: ->
553         @element.empty()
554         @element.unbind()
555
556         if @key_handler
557             @key_handler.deinit()
558
559         @tree = null
560
561         super()
562
563     _initData: ->
564         if @options.data
565             @_loadData(@options.data)
566         else
567             data_url = @_getDataUrlInfo()
568
569             if data_url
570                 @_loadDataFromUrl()
571             else
572                 @_loadData([])
573
574     _getDataUrlInfo: (node) ->
575         data_url = @options.dataUrl or @element.data('url')
576
577         getUrlFromString = =>
578             url_info = url: data_url
579
580             if node and node.id
581                 # Load on demand of a subtree; add node parameter
582                 data = node: node.id
583                 url_info['data'] = data
584             else
585                 # Add selected_node parameter
586                 selected_node_id = @_getNodeIdToBeSelected()
587                 if selected_node_id
588                     data = selected_node: selected_node_id
589                     url_info['data'] = data
590
591             return url_info
592
593         if $.isFunction(data_url)
594             return data_url(node)
595         else if $.type(data_url) == 'string'
596             return getUrlFromString()
597         else
598             return data_url
599
600     _getNodeIdToBeSelected: ->
601         if @options.saveState
602             return @save_state_handler.getNodeIdToBeSelected()
603         else
604             return null
605
606     _initTree: (data) ->
607         doInit = =>
608             if not @is_initialized
609                 @is_initialized = true
610                 @_triggerEvent('tree.init')
611
612         @tree = new @options.nodeClass(null, true, @options.nodeClass)
613
614         if @select_node_handler
615             @select_node_handler.clear()
616
617         @tree.loadFromData(data)
618
619         must_load_on_demand = @_setInitialState()
620
621         @_refreshElements()
622
623         if not must_load_on_demand
624             doInit()
625         else
626             # Load data on demand and then init the tree
627             @_setInitialStateOnDemand(doInit)
628
629     # Set initial state, either by restoring the state or auto-opening nodes
630     # result: must load nodes on demand?
631     _setInitialState: ->
632         restoreState = =>
633             # result: is state restored, must load on demand?
634             if not (@options.saveState and @save_state_handler)
635                 return [false, false]
636             else
637                 state = @save_state_handler.getStateFromStorage()
638
639                 if not state
640                     return [false, false]
641                 else
642                     must_load_on_demand = @save_state_handler.setInitialState(state)
643
644                     # return true: the state is restored
645                     return [true, must_load_on_demand]
646
647         autoOpenNodes = =>
648             # result: must load on demand?
649             if @options.autoOpen is false
650                 return false
651
652             max_level = @_getAutoOpenMaxLevel()
653             must_load_on_demand = false
654
655             @tree.iterate (node, level) ->
656                 if node.load_on_demand
657                     must_load_on_demand = true
658                     return false
659                 else if not node.hasChildren()
660                     return false
661                 else
662                     node.is_open = true
663                     return (level != max_level)
664
665             return must_load_on_demand
666
667         [is_restored, must_load_on_demand] = restoreState()
668
669         if not is_restored
670             must_load_on_demand = autoOpenNodes()
671
672         return must_load_on_demand
673
674     # Set the initial state for nodes that are loaded on demand
675     # Call cb_finished when done
676     _setInitialStateOnDemand: (cb_finished) ->
677         restoreState = =>
678             if not (@options.saveState and @save_state_handler)
679                 return false
680             else
681                 state = @save_state_handler.getStateFromStorage()
682
683                 if not state
684                     return false
685                 else
686                     @save_state_handler.setInitialStateOnDemand(state, cb_finished)
687
688                     return true
689
690         autoOpenNodes = =>
691             max_level = @_getAutoOpenMaxLevel()
692             loading_count = 0
693
694             loadAndOpenNode = (node) =>
695                 loading_count += 1
696                 @_openNode(
697                     node,
698                     false,
699                     ->
700                         loading_count -= 1
701                         openNodes()
702                 )
703
704             openNodes = =>
705                 @tree.iterate (node, level) =>
706                     if node.load_on_demand
707                         if not node.is_loading
708                             loadAndOpenNode(node)
709
710                         return false
711                     else
712                         @_openNode(node, false)
713
714                         return (level != max_level)
715
716                 if loading_count == 0
717                     cb_finished()
718
719             openNodes()
720
721         if not restoreState()
722             autoOpenNodes()
723
724     _getAutoOpenMaxLevel: ->
725         if @options.autoOpen is true
726             return -1
727         else
728             return parseInt(@options.autoOpen)
729
730     ###
731     Redraw the tree or part of the tree.
732     # from_node: redraw this subtree
733     ###
734     _refreshElements: (from_node=null) ->
735         @renderer.render(from_node)
736
737         @_triggerEvent('tree.refresh')
738
739     _click: (e) ->
740         click_target = @_getClickTarget(e.target)
741
742         if click_target
743             if click_target.type == 'button'
744                 @toggle(click_target.node, @options.slide)
745
746                 e.preventDefault()
747                 e.stopPropagation()
748             else if click_target.type == 'label'
749                 node = click_target.node
750                 event = @_triggerEvent(
751                     'tree.click',
752                         node: node
753                         click_event: e
754                 )
755
756                 if not event.isDefaultPrevented()
757                     @_selectNode(node, true)
758
759     _dblclick: (e) ->
760         click_target = @_getClickTarget(e.target)
761
762         if click_target and click_target.type == 'label'
763             @_triggerEvent(
764                 'tree.dblclick',
765                     node: click_target.node
766                     click_event: e
767             )
768
769     _getClickTarget: (element) ->
770         $target = $(element)
771
772         $button = $target.closest('.jqtree-toggler')
773
774         if $button.length
775             node = @_getNode($button)
776
777             if node
778                 return {
779                     type: 'button',
780                     node: node
781                 }
782         else
783             $el = $target.closest('.jqtree-element')
784             if $el.length
785                 node = @_getNode($el)
786                 if node
787                     return {
788                         type: 'label',
789                         node: node
790                     }
791
792         return null
793
794     _getNode: ($element) ->
795         $li = $element.closest('li.jqtree_common')
796         if $li.length == 0
797             return null
798         else
799             return $li.data('node')
800
801     _getNodeElementForNode: (node) ->
802         if node.isFolder()
803             return new FolderElement(node, this)
804         else
805             return new NodeElement(node, this)
806
807     _getNodeElement: ($element) ->
808         node = @_getNode($element)
809         if node
810             return @_getNodeElementForNode(node)
811         else
812             return null
813
814     _contextmenu: (e) ->
815         $div = $(e.target).closest('ul.jqtree-tree .jqtree-element')
816         if $div.length
817             node = @_getNode($div)
818             if node
819                 e.preventDefault()
820                 e.stopPropagation()
821
822                 @_triggerEvent(
823                     'tree.contextmenu',
824                         node: node
825                         click_event: e
826                 )
827                 return false
828
829     _saveState: ->
830         if @options.saveState
831             @save_state_handler.saveState()
832
833     _mouseCapture: (position_info) ->
834         if @options.dragAndDrop
835             return @dnd_handler.mouseCapture(position_info)
836         else
837             return false
838
839     _mouseStart: (position_info) ->
840         if @options.dragAndDrop
841             return @dnd_handler.mouseStart(position_info)
842         else
843             return false
844
845     _mouseDrag: (position_info) ->
846         if @options.dragAndDrop
847             result = @dnd_handler.mouseDrag(position_info)
848
849             if @scroll_handler
850                 @scroll_handler.checkScrolling()
851             return result
852         else
853             return false
854
855     _mouseStop: (position_info) ->
856         if @options.dragAndDrop
857             return @dnd_handler.mouseStop(position_info)
858         else
859             return false
860
861     _triggerEvent: (event_name, values) ->
862         event = $.Event(event_name)
863         $.extend(event, values)
864
865         @element.trigger(event)
866         return event
867
868     testGenerateHitAreas: (moving_node) ->
869         @dnd_handler.current_item = @_getNodeElementForNode(moving_node)
870         @dnd_handler.generateHitAreas()
871         return @dnd_handler.hit_areas
872
873     _selectCurrentNode: ->
874         node = @getSelectedNode()
875         if node
876             node_element = @_getNodeElementForNode(node)
877             if node_element
878                 node_element.select()
879
880     _deselectCurrentNode: ->
881         node = @getSelectedNode()
882         if node
883             @removeFromSelection(node)
884
885     _getDefaultClosedIcon: ->
886         if @options.rtl
887             # triangle to the left
888             return '◀'
889         else
890             # triangle to the right
891             return '►'
892
893     _getRtlOption: ->
894         if @options.rtl != null
895             return @options.rtl
896         else
897             data_rtl = @element.data('rtl')
898
899             if data_rtl? and data_rtl != false
900                 return true
901             else
902                 return false
903
904     _notifyLoading: (is_loading, node, $el) ->
905         if @options.onLoading
906             @options.onLoading(is_loading, node, $el)
907
908
909 JqTreeWidget.getModule = (name) ->
910     modules =
911         'node': node_module
912         'util': util_module
913         'drag_and_drop_handler': drag_and_drop_handler
914
915     return modules[name]
916
917
918 SimpleWidget.register(JqTreeWidget, 'tree')