winter/modules/backend/assets/js/october.treeview.js

438 lines
13 KiB
JavaScript
Raw Normal View History

2014-08-14 16:51:31 +11:00
/*
* TreeView Widget. Represents a sortable and draggable tree view. This widget was first used in the Pages plugin, for the sidebar page tree.
*
* Data attributes:
* - data-group-status-handler - AJAX handler to execute when an item is collapsed or expanded by a user
* - data-reorder-handler - AJAX handler to execute when items are reordered
*
* Events
* - open.oc.treeview - this event is triggered on the list element when an item is clicked.
*
2014-08-14 16:51:31 +11:00
* Dependences:
* - Tree list (october.treelist.js)
*
2014-08-14 16:51:31 +11:00
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
2014-08-14 16:51:31 +11:00
var TreeView = function (element, options) {
this.$el = $(element)
this.options = options
this.$allItems = null
this.$scrollbar = null
Base.call(this)
2014-08-14 16:51:31 +11:00
$.oc.foundation.controlUtils.markDisposable(element)
2014-08-14 16:51:31 +11:00
this.init()
}
TreeView.prototype = Object.create(BaseProto)
TreeView.prototype.constructor = TreeView
2014-08-14 16:51:31 +11:00
TreeView.prototype.init = function () {
this.$allItems = $('li', this.$el)
this.$scrollbar = this.$el.closest('[data-control=scrollbar]')
2014-08-14 16:51:31 +11:00
/*
* Init the sortable
2014-08-14 16:51:31 +11:00
*/
2014-08-28 19:03:47 +11:00
this.initSortable()
2014-08-14 16:51:31 +11:00
/*
* Create expand/collapse icons and drag handles
2014-08-14 16:51:31 +11:00
*/
this.createItemControls()
2014-08-14 16:51:31 +11:00
/*
* Bind the click events
2014-08-14 16:51:31 +11:00
*/
this.$el.on('click.treeview', 'li > div > ul.submenu li a', this.proxy(this.onOpenSubmenu))
this.$el.on('click.treeview', 'li > div > a', this.proxy(this.onOpen))
this.$el.on('click.treeview', 'li span.expand', this.proxy(this.onItemExpandClick))
2014-08-28 19:03:47 +11:00
/*
* Listen for the AJAX updates and dispose the widget
*/
this.$el.one('dispose-control', this.proxy(this.dispose))
2014-08-14 16:51:31 +11:00
/*
* Mark previously active item, if it was set
2014-08-14 16:51:31 +11:00
*/
var dataId = this.$el.data('oc.active-item')
if (dataId !== undefined) {
this.markActive(dataId)
}
this.$scrollbar.on('oc.scrollEnd', this.proxy(this.onScroll))
2014-08-14 16:51:31 +11:00
}
TreeView.prototype.dispose = function() {
this.unregisterHandlers()
this.clearScrollTimeout()
this.options = null
this.$el.removeData('oc.treeView')
this.$el = null
this.$allItems = null
this.$scrollbar = null
2014-08-14 16:51:31 +11:00
BaseProto.dispose.call(this)
}
TreeView.prototype.unregisterHandlers = function() {
2019-12-13 21:56:39 +11:00
this.$scrollbar.off('oc.scrollEnd', this.proxy(this.onScroll))
this.$el.off('.treeview')
this.$el.off('move.oc.treelist', this.proxy(this.onNodeMove))
this.$el.off('aftermove.oc.treelist', this.proxy(this.onAfterNodeMove))
this.$el.off('dispose-control', this.proxy(this.dispose))
}
TreeView.prototype.createItemControls = function() {
$('li', this.$el).each(function() {
var $container = $('> div', this),
$expand = $('> span.expand', $container)
2014-08-14 16:51:31 +11:00
if ($expand.length > 0)
return
2014-08-14 16:51:31 +11:00
$expand = $('<span class="expand">Expand</span>')
2014-08-14 16:51:31 +11:00
$container.prepend($expand)
2014-08-14 16:51:31 +11:00
2014-09-10 20:48:53 -07:00
if (!$('.drag-handle', $container).length)
$container.append($('<span class="drag-handle">Drag</span>'))
$container.append($('<span class="borders"></span>'))
if ($(this).attr('data-no-drag-mode') !== undefined)
$('span.drag-handle', this).attr('title', 'Dragging is disabled when the Search is active')
2014-08-14 16:51:31 +11:00
})
}
TreeView.prototype.collapseGroup = function($group) {
var $subitems = $('> ol', $group)
2014-08-14 16:51:31 +11:00
$subitems.css({
'overflow': 'hidden'
})
2014-08-14 16:51:31 +11:00
$subitems.animate({'height': 0}, { duration: 100, queue: false, complete: function() {
$subitems.css({
'overflow': 'visible',
'display': 'none',
'height' : 'auto'
2014-08-14 16:51:31 +11:00
})
$group.attr('data-status', 'collapsed')
2014-08-14 16:51:31 +11:00
$(window).trigger('resize')
} })
this.sendGroupStatusRequest($group, 0)
2014-08-14 16:51:31 +11:00
}
TreeView.prototype.expandGroup = function($group) {
var $subitems = $('> ol', $group)
2014-08-14 16:51:31 +11:00
$subitems.css({
'overflow': 'hidden',
'display': 'block',
'height': 0
})
$group.attr('data-status', 'expanded')
2014-08-14 16:51:31 +11:00
$subitems.animate({'height': $subitems[0].scrollHeight}, { duration: 100, queue: false, complete: function() {
$subitems.css({
'overflow': 'visible',
'height': 'auto'
})
$(window).trigger('resize')
} })
this.sendGroupStatusRequest($group, 1);
}
TreeView.prototype.fixSubItems = function() {
$('li', this.$el).each(function(){
var $li = $(this),
$subitems = $('> ol > li', $li)
$li.toggleClass('has-subitems', $subitems.length > 0)
})
2014-08-14 16:51:31 +11:00
}
TreeView.prototype.toggleGroup = function(group) {
var $group = $(group);
$group.attr('data-status') == 'expanded'
? this.collapseGroup($group)
: this.expandGroup($group)
}
TreeView.prototype.sendGroupStatusRequest = function($group, status) {
if (this.options.groupStatusHandler !== undefined) {
var groupId = $group.data('group-id')
$group.request(this.options.groupStatusHandler, {data: {group: groupId, status: status}})
}
}
TreeView.prototype.sendReorderRequest = function() {
2014-09-10 20:48:53 -07:00
if (this.options.reorderHandler === undefined)
return
var groups = {}
function iterator($container, node) {
$('> li', $container).each(function(){
var subnodes = {}
iterator($('> ol', this), subnodes)
node[$(this).data('groupId')] = subnodes
})
}
iterator($('> ol', this.$el), groups)
this.$el.request(this.options.reorderHandler, {data: {structure: JSON.stringify(groups)}})
}
2014-08-28 19:03:47 +11:00
TreeView.prototype.initSortable = function() {
var $noDragItems = $('[data-no-drag-mode]', this.$el)
if ($noDragItems.length > 0)
return
2014-08-28 19:03:47 +11:00
if (this.$el.data('oc.treelist'))
this.$el.treeListWidget('unbind')
this.$el.treeListWidget({
tweakCursorAdjustment: this.proxy(this.tweakCursorAdjustment),
isValidTarget: this.proxy(this.isValidTarget),
useAnimation: false,
usePlaceholderClone: true,
handle: 'span.drag-handle',
onDrag: this.proxy(this.onDrag),
tolerance: -20 // Give 20px of carry between containers
2014-08-28 19:03:47 +11:00
})
this.$el.on('move.oc.treelist', this.proxy(this.onNodeMove))
this.$el.on('aftermove.oc.treelist', this.proxy(this.onAfterNodeMove))
2014-08-28 19:03:47 +11:00
}
TreeView.prototype.markActive = function(dataId) {
$('li', this.$el).removeClass('active')
if (dataId)
$('li[data-id="'+dataId+'"]', this.$el).addClass('active')
this.$el.data('oc.active-item', dataId)
}
// It seems the method is not used anymore as we re-create the control
// instead of updating it. Remove later if nothing weird is noticed.
// -ab Apr 26 2015
//
TreeView.prototype.update = function() {
this.$allItems = $('li', this.$el)
this.createItemControls()
//this.initSortable()
var dataId = this.$el.data('oc.active-item')
if (dataId !== undefined) {
this.markActive(dataId)
}
}
TreeView.prototype.handleMovedNode = function() {
this.$el.trigger('change')
this.$allItems.removeClass('drop-target')
this.fixSubItems()
this.sendReorderRequest()
}
TreeView.prototype.tweakCursorAdjustment = function(adjustment) {
if (!adjustment) {
return adjustment
}
if (this.$scrollbar.length > 0) {
adjustment.top -= this.$scrollbar.scrollTop()
}
return adjustment
}
TreeView.prototype.isValidTarget = function($item, container) {
return $(container.el).closest('li').attr('data-status') != 'collapsed'
2014-08-14 16:51:31 +11:00
}
TreeView.DEFAULTS = {
2014-08-14 16:51:31 +11:00
}
// TREEVIEW EVENT HANDLERS
// ============================
TreeView.prototype.onOpenSubmenu = function(ev) {
var e = $.Event('submenu.oc.treeview', {relatedTarget: ev.currentTarget, clickEvent: ev})
this.$el.trigger(e, this)
return false
}
TreeView.prototype.onOpen = function(ev) {
var e = $.Event('open.oc.treeview', {relatedTarget: $(ev.currentTarget).closest('li').get(0), clickEvent: ev})
this.$el.trigger(e, ev.currentTarget)
return false
}
TreeView.prototype.onNodeMove = function() {
setTimeout(this.proxy(this.handleMovedNode), 50)
}
TreeView.prototype.onAfterNodeMove = function(ev, data) {
this.$allItems.removeClass('drop-target')
data.container.el.closest('li').addClass('drop-target')
}
TreeView.prototype.onItemExpandClick = function(ev) {
this.toggleGroup($(ev.currentTarget).closest('li'))
return false
}
// TREEVIEW SCROLL ON DRAG
// ============================
TreeView.prototype.onScroll = function () {
if (!$('body').hasClass('dragging')) {
return
}
var changed = this.lastScrollPos - this.$scrollbar.scrollTop()
this.$el.children('ol').each(function() {
var sortable = $(this).data('oc.sortable')
sortable.refresh()
sortable.cursorAdjustment.top += changed // Keep cursor adjustment in sync with scroll
});
this.dragCallback()
this.lastScrollPos = this.$scrollbar.scrollTop()
}
TreeView.prototype.onDrag = function ($item, position, _super, event) {
this.lastScrollPos = this.$scrollbar.scrollTop()
this.dragCallback = function() {
_super($item, position, null, event)
};
this.clearScrollTimeout()
this.dragCallback()
if (!this.$scrollbar || this.$scrollbar.length === 0)
return
if (position.top < 0) {
this.scrollOffset = -10 + Math.floor(position.top / 5)
}
else if (position.top > this.$scrollbar.height()) {
this.scrollOffset = 10 + Math.ceil((position.top - this.$scrollbar.height()) / 5)
}
else {
return
}
this.dragScroll()
}
TreeView.prototype.scrollMax = function() {
return this.$el.height() - this.$scrollbar.height()
}
TreeView.prototype.dragScroll = function() {
var startScrollTop = this.$scrollbar.scrollTop()
var changed
this.scrollTimeout = null
this.$scrollbar.scrollTop(Math.min(startScrollTop + this.scrollOffset, this.scrollMax()))
changed = this.$scrollbar.scrollTop() - startScrollTop
if (changed === 0) {
return
}
this.$el.children('ol').each(function() {
var sortable = $(this).data('oc.sortable')
sortable.refresh()
sortable.cursorAdjustment.top -= changed // Keep cursor adjustment in sync with scroll
});
this.dragCallback()
this.$scrollbar.data('oc.scrollbar').setThumbPosition() // Update scrollbar position
this.scrollTimeout = window.setTimeout(this.proxy(this.dragScroll), 100)
}
TreeView.prototype.clearScrollTimeout = function() {
if (this.scrollTimeout) {
window.clearTimeout(this.scrollTimeout)
this.scrollTimeout = null
}
}
2014-08-14 16:51:31 +11:00
// TREEVIEW PLUGIN DEFINITION
// ============================
var old = $.fn.treeView
$.fn.treeView = function (option) {
var args = arguments
2014-08-14 16:51:31 +11:00
return this.each(function () {
var $this = $(this)
var data = $this.data('oc.treeView')
var options = $.extend({}, TreeView.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.treeView', (data = new TreeView(this, options)))
if (typeof option == 'string' && data) {
var methodArgs = [];
for (var i=1; i<args.length; i++)
methodArgs.push(args[i])
if (data[option])
data[option].apply(data, methodArgs)
}
2014-08-14 16:51:31 +11:00
})
}
$.fn.treeView.Constructor = TreeView
// TREEVIEW NO CONFLICT
// =================
$.fn.treeView.noConflict = function () {
$.fn.treeView = old
return this
}
// TREEVIEW DATA-API
// ===============
2014-09-10 20:48:53 -07:00
// $(window).load(function(){
// $('[data-control=treeview]').treeView()
// })
$(document).render(function(){
2014-08-14 16:51:31 +11:00
$('[data-control=treeview]').treeView()
})
}(window.jQuery);