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'
11 node_module = require './node'
12 Node = node_module.Node
13 Position = node_module.Position
15 util_module = require './util'
17 {isFunction} = util_module
19 {BorderDropHint,FolderElement,GhostDropHint,NodeElement} = require './node_element'
21 {DragAndDropHandler, DragElement, HitAreasGenerator} = drag_and_drop_handler
27 class JqTreeWidget extends MouseWidget
28 BorderDropHint: BorderDropHint
29 DragElement: DragElement
30 DragAndDropHandler: DragAndDropHandler
31 ElementsRenderer: ElementsRenderer
32 GhostDropHint: GhostDropHint
33 HitAreasGenerator: HitAreasGenerator
35 SaveStateHandler: SaveStateHandler
36 ScrollHandler: ScrollHandler
37 SelectNodeHandler: SelectNodeHandler
40 autoOpen: false # true / false / int (open n levels starting at 0)
41 saveState: false # true / false / string (cookie name)
46 onSetStateFromStorage: null
47 onGetStateFromStorage: 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)
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?
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)
68 toggle: (node, slide=null) ->
70 slide = @options.slide
73 @closeNode(node, slide)
75 @openNode(node, slide)
83 @_selectNode(node, false)
86 _selectNode: (node, must_toggle=false) ->
87 if not @select_node_handler
91 if @options.onCanSelectNode
92 return @options.selectable and @options.onCanSelectNode(node)
94 return @options.selectable
99 if parent and parent.parent and not parent.is_open
100 @openNode(parent, false)
103 if @options.saveState
104 @save_state_handler.saveState()
107 # Called with empty node -> deselect current node
108 @_deselectCurrentNode()
115 if @select_node_handler.isNodeSelected(node)
117 @_deselectCurrentNode()
124 deselected_node = @getSelectedNode()
125 @_deselectCurrentNode()
126 @addToSelection(node)
127 @_triggerEvent('tree.select', node: node, deselected_node: deselected_node)
133 if @select_node_handler
134 return @select_node_handler.getSelectedNode()
139 return JSON.stringify(
143 loadData: (data, parent_node) ->
144 @_loadData(data, parent_node)
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'); });
155 - loadDataFromUrl(parent_node=null, on_finished=null)
157 loadDataFromUrl(node1);
158 loadDataFromUrl(null, function() { console.log('finished'); });
159 loadDataFromUrl(node1, function() { console.log('finished'); });
161 loadDataFromUrl: (param1, param2, param3) ->
162 if $.type(param1) == 'string'
163 # first parameter is url
164 @_loadDataFromUrl(param1, param2, param3)
166 # first parameter is not url
167 @_loadDataFromUrl(null, param1, param2)
171 reload: (on_finished) ->
172 @_loadDataFromUrl(null, null, on_finished)
175 _loadDataFromUrl: (url_info, parent_node, on_finished) ->
180 $el = $(parent_node.element)
184 $el.addClass('jqtree-loading')
185 @_notifyLoading(true, parent_node, $el)
187 removeLoadingClass = =>
189 $el.removeClass('jqtree-loading')
191 @_notifyLoading(false, parent_node, $el)
194 if $.type(url_info) == 'string'
197 if not url_info.method
198 url_info.method = 'get'
202 handeLoadData = (data) =>
204 @_loadData(data, parent_node)
206 if on_finished and $.isFunction(on_finished)
209 handleSuccess = (response) =>
210 if $.isArray(response) or typeof response == 'object'
213 data = $.parseJSON(response)
217 if @options.dataFilter
218 data = @options.dataFilter(data)
222 handleError = (response) =>
225 if @options.onLoadFailed
226 @options.onLoadFailed(response)
228 loadDataFromUrlInfo = ->
229 url_info = parseUrlInfo()
236 method: if url_info.method? then url_info.method.toUpperCase() else 'GET',
239 success: handleSuccess,
246 # Generate url for node
247 url_info = @_getDataUrlInfo(parent_node)
254 else if $.isArray(url_info)
255 handeLoadData(url_info)
258 loadDataFromUrlInfo()
261 _loadData: (data, parent_node=null) ->
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)
271 parent_node.loadFromData(data)
273 parent_node.load_on_demand = false
274 parent_node.is_loading = false
276 @_refreshElements(parent_node)
281 @_triggerEvent('tree.load_data', tree_data: data)
290 @dnd_handler.refresh()
292 getNodeById: (node_id) ->
293 return @tree.getNodeById(node_id)
295 getNodeByName: (name) ->
296 return @tree.getNodeByName(name)
298 getNodesByProperty: (key, value) ->
299 return @tree.getNodesByProperty(key, value)
301 getNodeByHtmlElement: (element) ->
302 return @_getNode($(element))
304 getNodeByCallback: (callback) ->
305 return @tree.getNodeByCallback(callback)
307 openNode: (node, slide_param=null, on_finished_param=null) ->
309 if isFunction(slide_param)
310 on_finished = slide_param
314 on_finished = on_finished_param
317 slide = @options.slide
319 return [slide, on_finished]
321 [slide, on_finished] = parseParams()
324 @_openNode(node, slide, on_finished)
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)
334 if node.load_on_demand
335 @_loadFolderOnDemand(node, slide, on_finished)
340 # nb: do not open root element
342 doOpenNode(parent, false, null)
343 parent = parent.parent
345 doOpenNode(node, slide, on_finished)
348 _loadFolderOnDemand: (node, slide=true, on_finished) ->
349 node.is_loading = true
355 @_openNode(node, slide, on_finished)
358 closeNode: (node, slide=null) ->
360 slide = @options.slide
363 new FolderElement(node, this).close(slide)
371 return @dnd_handler.is_dragging
376 @dnd_handler.refresh()
379 addNodeAfter: (new_node_info, existing_node) ->
380 new_node = existing_node.addAfter(new_node_info)
381 @_refreshElements(existing_node.parent)
384 addNodeBefore: (new_node_info, existing_node) ->
385 new_node = existing_node.addBefore(new_node_info)
386 @_refreshElements(existing_node.parent)
389 addParentNode: (new_node_info, existing_node) ->
390 new_node = existing_node.addParent(new_node_info)
391 @_refreshElements(new_node.parent)
394 removeNode: (node) ->
397 @select_node_handler.removeFromSelection(node, true) # including children
400 @_refreshElements(parent)
404 appendNode: (new_node_info, parent_node) ->
405 parent_node = parent_node or @tree
407 node = parent_node.append(new_node_info)
409 @_refreshElements(parent_node)
413 prependNode: (new_node_info, parent_node) ->
417 node = parent_node.prepend(new_node_info)
419 @_refreshElements(parent_node)
423 updateNode: (node, data) ->
424 id_is_changed = data.id and data.id != node.id
427 @tree.removeNodeFromIndex(node)
432 @tree.addNodeToIndex(node)
434 if typeof data == 'object' and data.children
435 node.removeChildren()
437 if data.children.length
438 node.loadFromData(data.children)
440 @renderer.renderFromNode(node)
441 @_selectCurrentNode()
445 moveNode: (node, target_node, position) ->
446 position_index = Position.nameToIndex(position)
448 @tree.moveNode(node, target_node, position_index)
452 getStateFromStorage: ->
453 return @save_state_handler.getStateFromStorage()
455 addToSelection: (node) ->
457 @select_node_handler.addToSelection(node)
459 @_getNodeElementForNode(node).select()
465 return @select_node_handler.getSelectedNodes()
467 isNodeSelected: (node) ->
468 return @select_node_handler.isNodeSelected(node)
470 removeFromSelection: (node) ->
471 @select_node_handler.removeFromSelection(node)
473 @_getNodeElementForNode(node).deselect()
477 scrollToNode: (node) ->
478 $element = $(node.element)
479 top = $element.offset().top - @$el.offset().top
481 @scroll_handler.scrollTo(top)
485 return @save_state_handler.getState()
488 @save_state_handler.setInitialState(state)
492 setOption: (option, value) ->
493 @options[option] = value
498 @key_handler.moveDown()
504 @key_handler.moveUp()
516 @is_initialized = false
518 @options.rtl = @_getRtlOption()
520 if !@options.closedIcon
521 @options.closedIcon = @_getDefaultClosedIcon()
523 @renderer = new ElementsRenderer(this)
526 @save_state_handler = new SaveStateHandler(this)
528 @options.saveState = false
530 if SelectNodeHandler?
531 @select_node_handler = new SelectNodeHandler(this)
533 if DragAndDropHandler?
534 @dnd_handler = new DragAndDropHandler(this)
536 @options.dragAndDrop = false
539 @scroll_handler = new ScrollHandler(this)
541 if KeyHandler? and SelectNodeHandler?
542 @key_handler = new KeyHandler(this)
546 @element.click($.proxy(@_click, this))
547 @element.dblclick($.proxy(@_dblclick, this))
549 if @options.useContextMenu
550 @element.bind('contextmenu', $.proxy(@_contextmenu, this))
557 @key_handler.deinit()
565 @_loadData(@options.data)
567 data_url = @_getDataUrlInfo()
574 _getDataUrlInfo: (node) ->
575 data_url = @options.dataUrl or @element.data('url')
577 getUrlFromString = =>
578 url_info = url: data_url
581 # Load on demand of a subtree; add node parameter
583 url_info['data'] = data
585 # Add selected_node parameter
586 selected_node_id = @_getNodeIdToBeSelected()
588 data = selected_node: selected_node_id
589 url_info['data'] = data
593 if $.isFunction(data_url)
594 return data_url(node)
595 else if $.type(data_url) == 'string'
596 return getUrlFromString()
600 _getNodeIdToBeSelected: ->
601 if @options.saveState
602 return @save_state_handler.getNodeIdToBeSelected()
608 if not @is_initialized
609 @is_initialized = true
610 @_triggerEvent('tree.init')
612 @tree = new @options.nodeClass(null, true, @options.nodeClass)
614 if @select_node_handler
615 @select_node_handler.clear()
617 @tree.loadFromData(data)
619 must_load_on_demand = @_setInitialState()
623 if not must_load_on_demand
626 # Load data on demand and then init the tree
627 @_setInitialStateOnDemand(doInit)
629 # Set initial state, either by restoring the state or auto-opening nodes
630 # result: must load nodes on demand?
633 # result: is state restored, must load on demand?
634 if not (@options.saveState and @save_state_handler)
635 return [false, false]
637 state = @save_state_handler.getStateFromStorage()
640 return [false, false]
642 must_load_on_demand = @save_state_handler.setInitialState(state)
644 # return true: the state is restored
645 return [true, must_load_on_demand]
648 # result: must load on demand?
649 if @options.autoOpen is false
652 max_level = @_getAutoOpenMaxLevel()
653 must_load_on_demand = false
655 @tree.iterate (node, level) ->
656 if node.load_on_demand
657 must_load_on_demand = true
659 else if not node.hasChildren()
663 return (level != max_level)
665 return must_load_on_demand
667 [is_restored, must_load_on_demand] = restoreState()
670 must_load_on_demand = autoOpenNodes()
672 return must_load_on_demand
674 # Set the initial state for nodes that are loaded on demand
675 # Call cb_finished when done
676 _setInitialStateOnDemand: (cb_finished) ->
678 if not (@options.saveState and @save_state_handler)
681 state = @save_state_handler.getStateFromStorage()
686 @save_state_handler.setInitialStateOnDemand(state, cb_finished)
691 max_level = @_getAutoOpenMaxLevel()
694 loadAndOpenNode = (node) =>
705 @tree.iterate (node, level) =>
706 if node.load_on_demand
707 if not node.is_loading
708 loadAndOpenNode(node)
712 @_openNode(node, false)
714 return (level != max_level)
716 if loading_count == 0
721 if not restoreState()
724 _getAutoOpenMaxLevel: ->
725 if @options.autoOpen is true
728 return parseInt(@options.autoOpen)
731 Redraw the tree or part of the tree.
732 # from_node: redraw this subtree
734 _refreshElements: (from_node=null) ->
735 @renderer.render(from_node)
737 @_triggerEvent('tree.refresh')
740 click_target = @_getClickTarget(e.target)
743 if click_target.type == 'button'
744 @toggle(click_target.node, @options.slide)
748 else if click_target.type == 'label'
749 node = click_target.node
750 event = @_triggerEvent(
756 if not event.isDefaultPrevented()
757 @_selectNode(node, true)
760 click_target = @_getClickTarget(e.target)
762 if click_target and click_target.type == 'label'
765 node: click_target.node
769 _getClickTarget: (element) ->
772 $button = $target.closest('.jqtree-toggler')
775 node = @_getNode($button)
783 $el = $target.closest('.jqtree-element')
785 node = @_getNode($el)
794 _getNode: ($element) ->
795 $li = $element.closest('li.jqtree_common')
799 return $li.data('node')
801 _getNodeElementForNode: (node) ->
803 return new FolderElement(node, this)
805 return new NodeElement(node, this)
807 _getNodeElement: ($element) ->
808 node = @_getNode($element)
810 return @_getNodeElementForNode(node)
815 $div = $(e.target).closest('ul.jqtree-tree .jqtree-element')
817 node = @_getNode($div)
830 if @options.saveState
831 @save_state_handler.saveState()
833 _mouseCapture: (position_info) ->
834 if @options.dragAndDrop
835 return @dnd_handler.mouseCapture(position_info)
839 _mouseStart: (position_info) ->
840 if @options.dragAndDrop
841 return @dnd_handler.mouseStart(position_info)
845 _mouseDrag: (position_info) ->
846 if @options.dragAndDrop
847 result = @dnd_handler.mouseDrag(position_info)
850 @scroll_handler.checkScrolling()
855 _mouseStop: (position_info) ->
856 if @options.dragAndDrop
857 return @dnd_handler.mouseStop(position_info)
861 _triggerEvent: (event_name, values) ->
862 event = $.Event(event_name)
863 $.extend(event, values)
865 @element.trigger(event)
868 testGenerateHitAreas: (moving_node) ->
869 @dnd_handler.current_item = @_getNodeElementForNode(moving_node)
870 @dnd_handler.generateHitAreas()
871 return @dnd_handler.hit_areas
873 _selectCurrentNode: ->
874 node = @getSelectedNode()
876 node_element = @_getNodeElementForNode(node)
878 node_element.select()
880 _deselectCurrentNode: ->
881 node = @getSelectedNode()
883 @removeFromSelection(node)
885 _getDefaultClosedIcon: ->
887 # triangle to the left
890 # triangle to the right
894 if @options.rtl != null
897 data_rtl = @element.data('rtl')
899 if data_rtl? and data_rtl != false
904 _notifyLoading: (is_loading, node, $el) ->
905 if @options.onLoading
906 @options.onLoading(is_loading, node, $el)
909 JqTreeWidget.getModule = (name) ->
913 'drag_and_drop_handler': drag_and_drop_handler
918 SimpleWidget.register(JqTreeWidget, 'tree')