Last active
November 30, 2015 17:19
-
-
Save kerbyfc/8fd9b8242ed0151c42a3 to your computer and use it in GitHub Desktop.
Backbone TreeCollection, FancyTree super class and it's inheritor ReportsTree ItemView class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
mousetrap = require "mousetrap" | |
module.exports = class FancyTreeBehavior extends Marionette.Behavior | |
###* | |
* Default options, that should be merged with | |
* constructor options argument, and then mixed to | |
* fancytree view | |
* @type {Object} | |
### | |
defaults: {} | |
###* | |
* Fancytree plugin dependencies dnd/filter/etc... | |
* @type {Array} | |
### | |
extensions: [] | |
### | |
* Methods to be defined in view | |
### | |
@methods = {} | |
hotkeys: {} | |
###* | |
* Merge passed and default options, create handlers | |
* @param {FancyTree} view | |
* @param {Object} options = {} | |
### | |
constructor: (options = {}, @view) -> | |
@options = _.merge {}, _.result(@, 'defaults'), options | |
# register hotkeys | |
for hotkeys, method of _.result(@, 'hotkeys') | |
mousetrap.bind hotkeys, @[method].bind @ | |
@on "destroy", @_unbindKeys | |
# register fancytree extensions | |
@view.options.extensions = _.union (@view.options.extensions or []), @extensions | |
# define view methods | |
for method, reqres of @methods | |
reqres = _.kebabCase(method).replace /\-/g, ":" | |
unless @[method] | |
throw new Error "#{@constructor.name}.#{method} implementation missed" | |
@view._defineMethod _.camelCase(method), reqres, @[method], @ | |
super | |
_unbindKeys: => | |
for hotkeys, method of _.result(@, 'hotkeys') | |
mousetrap.unbind hotkeys |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
FancyTreeBehavior = require "views/controls/fancytree/behavior.coffee" | |
module.exports = class ActivityManager extends FancyTreeBehavior | |
###* | |
* Css class selectors that affects on activity disabling | |
* @type {Array} | |
### | |
defaults: | |
###* | |
* Css class selectors that affects on activity disabling | |
* @override | |
* @type {Array} | |
### | |
resetTriggers: [ ".fancytree-container" ] | |
methods: { | |
"setActiveNode" | |
"getActiveNode" | |
"getActiveFolder" | |
"getActiveItem" | |
"resetNodesActivity" | |
} | |
onShow: -> | |
for trigger in @options.resetTriggers | |
el = @view.$ trigger | |
# it can be parent container, so we should find it | |
# only in current tree branch to avoid extra bugs | |
el = el.length and el or @view.$el.closest trigger | |
el.on "click", @_resetNodesActivity | |
###* | |
* Unregister event handlers | |
### | |
beforeDestroy: -> | |
for trigger in @options.resetTriggers | |
el = @$ trigger | |
el = el.length and el or @$el.closest trigger | |
el.off "click" | |
########################################################################### | |
# PRIVATE | |
###* | |
* Handle background clicks to reset active node | |
* @param {Event} e - event | |
### | |
_resetNodesActivity: (e) => | |
if @_isActivityResetTrigger e.target | |
@resetNodesActivity() | |
###* | |
* Check if node is matched to selector | |
* @param {Event} e | |
* @return {Boolean} | |
### | |
_isActivityResetTrigger: (node) => | |
el = $ node | |
_.any @options.resetTriggers, (trigger) -> | |
el.is trigger | |
########################################################################### | |
# INTERFACE | |
###* | |
* Deactive current node | |
* @param {FancytreeNode} root = @tree.rootNode | |
### | |
resetNodesActivity: (root = @view.tree?.rootNode) -> | |
if root | |
root.visit (node) -> | |
node.setFocus false | |
node.setActive false | |
@triggerMethod "reset:nodes:activity" | |
###* | |
* Return tree active node | |
* @return {FancytreeNode|Null} tree node or null | |
### | |
getActiveNode: -> | |
@view.tree?.getActiveNode() or null | |
###* | |
* Return active report | |
* @return {FancytreeNode|Null} node or null | |
### | |
getActiveItem: -> | |
if node = @getActiveNode() | |
unless node.parent | |
return node | |
null | |
###* | |
* Return active report | |
* @return {FancytreeNode|Null} node or null | |
### | |
getActiveFolder: -> | |
if node = @getActiveNode() | |
return switch | |
when @view.isFolder node | |
node | |
else | |
if not @view.isRootNode node.parent | |
node.parent | |
else | |
null | |
null | |
###* | |
* Activate/deactivate node | |
* @param {String} key - node key | |
* @param {Boolean} flag = true | |
* @param {Object} opts = {} - for example noEvents:true | |
### | |
setActiveNode: (key, flag = true, opts = {}) -> | |
if node = @view.getNode key | |
node.setActive flag, opts | |
return node | |
null |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
helpers = require "common/helpers.coffee" | |
FancyTreeBehavior = require "../behavior.coffee" | |
module.exports = class FancyTreeSearchBehavior extends FancyTreeBehavior | |
methods: { | |
"search" | |
"resetSearchQuery" | |
"getSearchQuery" | |
} | |
###* | |
* Default values for fancytree dnd extension | |
* @type {Object} | |
### | |
defaults: | |
quicksearch : true | |
container : false | |
input : "[data-search] > input" | |
template : "controls/fancytree/search" | |
value : "" | |
placeholder : "" | |
autoApply : true | |
autoExpand : true | |
mode : "hide" | |
hotkeys: | |
focus: "alt+e" | |
###* | |
* Options to mixin to fanctytree options | |
* @type {Array} | |
### | |
exportOptions: [ | |
"autoApply" | |
"autoExpand" | |
"mode" | |
"quicksearch" | |
] | |
hotkeys: -> | |
helpers.toObject @options.hotkeys.focus, "focus" | |
###* | |
* Fancytree plugin dependencies | |
* @type {Array} | |
### | |
extensions: ['filter'] | |
initialize: -> | |
unless @options.container | |
throw new Error "Search extension needs `container` option" | |
# mixin options to fancytree | |
_.merge @view.options, | |
filter: _.pick @options, @exportOptions... | |
onShow: => | |
@container = @view.$ @options.container | |
@container.html Marionette.Renderer.render @options.template, @options | |
@input = @container.find @options.input | |
@input.on "keyup", @_search | |
########################################################################### | |
# PRIVATE | |
###* | |
* Clear or change filter | |
### | |
_search: (e) => | |
if e.which is $.ui.keyCode.ESCAPE or | |
not $.trim @input.val() | |
@resetSearchQuery() | |
else | |
@search @input.val() | |
########################################################################### | |
# INTERFACE | |
focus: -> | |
@input.focus() | |
###* | |
* Filter nodes with search query | |
* @param {String} query | |
### | |
search: (query) => | |
if @query isnt query and @view.tree? | |
@query = query | |
@view.tree.filterNodes @query, @options | |
@input.val query | |
###* | |
* Clear search | |
### | |
resetSearchQuery: -> | |
@query = "" | |
@input.val @query | |
@view.tree.clearFilter() | |
###* | |
* Get or set value | |
* @param {String} value - value to set | |
### | |
getSearchQuery: -> | |
@input.val value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
require "fancytree" | |
behaviorClasses = | |
activity : require "./behaviors/activity.coffee" | |
dnd : require "./behaviors/dnd.coffee" | |
expander : require "./behaviors/expander.coffee" | |
node : require "./behaviors/node.coffee" | |
search : require "./behaviors/search.coffee" | |
selection : require "./behaviors/selection.coffee" | |
visitor : require "./behaviors/visitor.coffee" | |
stateful : require "./behaviors/stateful.coffee" | |
###* | |
* Universally light-weight tree view, that uses fancytree. | |
* | |
* Next methods might be implemented: | |
* - getSource | |
* | |
* Public methods are wrapped to be requirable by App.reqres | |
* - getActive[Item/Folder/Node] | |
* - getSelected[Item/Folder/Node] | |
* - others... | |
* | |
* @note Next constructor arguments are required: <String/jQuery> container | |
* @note Next properties must be implemented: <String> scope | |
* @note See methods might me defined to handle tree events se _buildTree | |
* | |
* @example extending | |
* class ReportsTreeView extends FancyTree | |
* | |
* # to be able to handle events by App.vent "reports:tree:<event>" | |
* scope: "reports" | |
* | |
* onNodeActivate: (node, data) -> | |
* console.log data.model.id, data.attrs.DISPLAY_NAME | |
* | |
* @example instantiating | |
* tree = new ReportsTreeView | |
* container: ".tree-view__container" | |
* | |
* @example add handlers | |
* tree.onFolderSelect = (node, data) -> ... | |
* | |
* @example listen events | |
* App.vent.on "reports:tree:item:select", (node, data) -> ... | |
* | |
* @see views/controls/fancytree/*.coffee | |
* @example Create extension | |
* | |
* # my_tree.coffee | |
* couter = require(...counter.coffee) | |
* | |
* class MyTree extends FancyTree | |
* | |
* extensions: _.extend FancyTree::extensions, | |
* counter: counter | |
* | |
* ... | |
* | |
* # counter.coffee | |
* | |
* class CounterFancyTreeExtention | |
* | |
* # you can pass options to extension via options.counter | |
* # and merge them with defaults | |
* defaults: | |
* counter: {} | |
* | |
* contructor: (view, options = {}) -> | |
* ... | |
* | |
* # use fancytree virtual methods to | |
* # handle fancytree events | |
* onNodeClick : -> ... | |
* onItemSelect : -> ... | |
* | |
* @note LayoutView is used as it's more extensible | |
* | |
### | |
class FancyTree extends Marionette.LayoutView | |
###* | |
* Default fancytree settings | |
* @type {Object} | |
### | |
defaults: | |
checkbox : true | |
icons : false | |
selectMode : 3 | |
paths: | |
# paths to model (in node data) | |
# to interract with | |
# @note required for some behaviors | |
model: "attrs.model" | |
methods: { | |
"rebuild" | |
} | |
behaviorClasses: behaviorClasses | |
# default behaviors | |
behaviors: -> | |
node : {} | |
visitor : {} | |
activity : {} | |
expander : {} | |
selection : {} | |
###* | |
* Properties to be checked while instantiation | |
* @type {Array} | |
### | |
requiredProps: [ | |
"scope" | |
"container" | |
] | |
###* | |
* Validate self, register handlers for application event bus, | |
* instantiate extensions | |
* @throws {Error} If required props are missing | |
* @param {Object} options = {} | |
### | |
constructor: (options = {}) -> | |
# extend & override default fancytree options | |
options = _.extend {}, (@options or {}), options | |
@options = _.defaults {}, options, _.result @, 'defaults' | |
# setup required props | |
for prop in @requiredProps | |
if options[prop] | |
@[prop] = options[prop] | |
unless @[prop] | |
throw new Error "FancyTree: `#{prop}` must be specified | |
or passed as option" | |
# define view methods | |
for method, reqres in @methods | |
reqres = _.kebabCase(method).replace /\-/g, ":" | |
view._defineMethod _.camelCase(method), reqres, @[method] | |
# make behaviors hash | |
@behaviors = _.reduce _.result(@, 'behaviors'), (acc, options, behavior) => | |
acc[behavior] = | |
if behaviorClass = @behaviorClasses[behavior] | |
_.extend {}, options, behaviorClass: behaviorClass | |
else | |
options | |
acc | |
, {} | |
super | |
###* | |
* Build tree on show | |
### | |
onShow: -> | |
@_rebuild() | |
########################################################################### | |
# PRIVATE | |
###* | |
* Create tree event handler | |
* @param {String} eventSign - "on/before:event" | |
### | |
_createEventHandler: (eventSign) => | |
(e, data, node = data.node) => | |
if e.type.match "click" | |
e.stopImmediatePropagation() | |
type = @isFolder(node) and "folder" or "item" | |
[ moment, event ] = eventSign.split ":" | |
@_triggerInteractionEvent moment, event, node, data, type, e | |
###* | |
* Trigger event with App.vent bus, call proper handler | |
* @example do things before and after item node selection | |
* tree.on "before:node:select" | |
* tree.on "before:item:select" | |
* tree.on "node:select" | |
* tree.on "node:item:select" | |
* @param {String} moment - "on"/"before" | |
* @param {Event} event | |
* @param {Array} args... | |
### | |
_triggerInteractionEvent: (moment, event, args...) -> | |
type = args[2] | |
prefix = moment is "before" and "before:" or "" | |
# trigger events | |
@triggerMethod "#{prefix}node:#{event.toLowerCase()}", args... | |
@triggerMethod "#{prefix}#{type}:#{event.toLowerCase()}", args... | |
triggerMethod: (args...) -> | |
super | |
App.vent.trigger "#{@scope}:tree:#{_.first args}" | |
###* | |
* Instantiate fancytree | |
### | |
_buildTree: -> | |
container = $ @container | |
return unless container.length | |
handlers = _.reduce [ | |
"on:select" # on[Node/Item/Folder]Select method | |
"on:activate" | |
"on:deactivate" | |
"on:focus" | |
"on:blur" | |
"on:expand" | |
"on:collapse" | |
"on:click" | |
"on:dblclick" | |
"on:lazyLoad" | |
], (acc, event) => | |
acc[_.last event.split ":"] = @_createEventHandler event | |
acc | |
, {} | |
# if handler was not specified by view, | |
# but registered by extension, | |
# then extend handlers with them | |
for extension in _.values @extensions | |
if _handlers = extension.registerHandlers?() | |
for handler, event of _handlers | |
unless handlers[handler] | |
handlers[handler] = @_createEventHandler event | |
options = _.extend @options, handlers, | |
source: => | |
source = @getSource() | |
@triggerMethod "getSource", source | |
source | |
# # special handler naming convention | |
removeNode : @_createEventHandler "on:remove" | |
renderNode : @_createEventHandler "on:render" | |
# # before[Node/Item/Folder]Select method | |
beforeSelect : @_createEventHandler "before:select" | |
beforeExpand : @_createEventHandler "before:expand" | |
beforeActivate : @_createEventHandler "before:activate" | |
@log ":options", options | |
container.fancytree options | |
if @options.autoSort | |
# this class disables arrows while drag'n'drop | |
container.children(":first").addClass "_autoSort" | |
@tree = container.fancytree 'getTree' | |
@triggerMethod "build", @tree | |
_defineMethod: (methodName, reqres, implementation, context = @) -> | |
# If method exists in view, it shouldn't be overriden by extension, | |
# instead, in this case methods, defined by view class | |
# overrites method that was defined by extension) | |
@[methodName] ?= -> | |
implementation.apply context, arguments | |
App.reqres.setHandler "#{@scope}:tree:#{reqres}", @[methodName] | |
###* | |
* Rebuild tree, reactivate currently active node | |
### | |
_rebuild: -> | |
unless @tree | |
@_buildTree() | |
else | |
active = @getActiveNode() | |
@tree.reload() | |
# try to activate node after rebuild | |
# if there are no active node right after reload | |
if active and not @getActiveNode() | |
if node = @getNode active.key | |
node.setActive true, noEvents: true | |
@trigger "rebuild", @tree | |
########################################################################### | |
# PUBLIC | |
###* | |
* Check if node is a folder node | |
* @param {FancytreeNode} node | |
* @return {Boolean} | |
### | |
isFolder: (node) -> | |
node.folder | |
rebuild: -> | |
@_rebuild() | |
getSource: -> [] | |
module.exports = FancyTree |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
FancyTree = require "views/controls/fancytree/view.coffee" | |
module.exports = class ReportsTree extends FancyTree | |
template : "reports/tree" | |
className : "sidebar__content" | |
scope : "reports" | |
options: | |
icons : true | |
checkbox : false | |
expanded : 2 | |
behaviors: -> | |
_.extend super, | |
dnd: {} | |
stateful: {} | |
search: | |
placeholder : App.t "reports.search_placeholder" | |
container : "[data-widget='fancyTreeSearch']" | |
activity: | |
resetTriggers: [ | |
".fancytree-container" | |
".sidebar__header" | |
".sidebar__indent" | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment