From 835c4c92b75b5a3d26d7ebf793ec98fbe0dc6499 Mon Sep 17 00:00:00 2001 From: rolux Date: Thu, 21 Aug 2014 12:14:30 +0200 Subject: [PATCH] new SplitPanel implementation --- source/Ox.UI/js/Bar/Resizebar.js | 233 ++++++++++---------- source/Ox.UI/js/Panel/SplitPanel.js | 327 ++++++++++++---------------- 2 files changed, 249 insertions(+), 311 deletions(-) diff --git a/source/Ox.UI/js/Bar/Resizebar.js b/source/Ox.UI/js/Bar/Resizebar.js index 5ca1768b..6613b9d0 100644 --- a/source/Ox.UI/js/Bar/Resizebar.js +++ b/source/Ox.UI/js/Bar/Resizebar.js @@ -8,6 +8,7 @@ Ox.Resizebar Resizebar elements Elements of the bar orientation Orientation ('horizontal' or 'vertical') panel Panel object + resizeable If true, can be resetted to default or original size resizeable If true, can be resized resize Array of sizes size Default size @@ -19,114 +20,58 @@ Ox.Resizebar = function(options, self) { self = self || {}; var that = Ox.Element({}, self) - .defaults({ - collapsed: false, - collapsible: true, - edge: 'left', - elements: [], - orientation: 'horizontal', - parent: null, - resizable: true, - resize: [], - size: 0, - tooltip: false - }) - .options(options || {}) - .update({ - collapsed: function() { - that.css({cursor: getCursor()}); - self.$tooltip && self.$tooltip.options({title: getTitle()}); - } - }) - .addClass('OxResizebar Ox' + Ox.toTitleCase(self.options.orientation)) - .bindEvent({ - // singleclick: toggle, - // doubleclick: reset, - anyclick: toggle, - dragstart: dragstart, - drag: drag, - dragend: dragend, - mousedown: function() { - triggerEvents('resizestart'); - }, - mouseup: function() { - triggerEvents('resizeend'); - } - }) - .append($('
').addClass('OxSpace')) - .append($('
').addClass('OxLine')) - .append($('
').addClass('OxSpace')); + .defaults({ + collapsed: false, + collapsible: false, + defaultSize: null, + edge: 'left', + orientation: 'horizontal', + resettable: false, + resizable: false, + resize: [], + size: 0, + tooltip: false + }) + .options(options || {}) + .update({ + collapsed: function() { + that.css({cursor: getCursor()}); + self.$tooltip && self.$tooltip.options({title: getTooltipTitle()}); + } + }) + .addClass('OxResizebar Ox' + Ox.toTitleCase(self.options.orientation)) + .bindEvent(Ox.extend({ + dragstart: onDragstart, + drag: onDrag, + dragpause: onDragpause, + dragend: onDragend + }, self.options.resettable ? { + doubleclick: reset, + singleclick: toggle + } : { + anyclick: toggle + })) + .append(Ox.$('
').addClass('OxSpace')) + .append(Ox.$('
').addClass('OxLine')) + .append(Ox.$('
').addClass('OxSpace')); - if (self.options.tooltip) { + if (Ox.isString(self.options.tooltip)) { // FIXME: Use Ox.Element's tooltip - self.$tooltip = Ox.Tooltip({title: getTitle()}); + self.$tooltip = Ox.Tooltip({title: getTooltipTitle()}); that.on({ mouseenter: self.$tooltip.show, mouseleave: self.$tooltip.hide }); } - Ox.extend(self, { - clientXY: self.options.orientation == 'horizontal' ? 'clientY' : 'clientX', - dimensions: Ox.UI.DIMENSIONS[self.options.orientation], // fixme: should orientation be the opposite orientation here? - edges: Ox.UI.EDGES[self.options.orientation], - isLeftOrTop: self.options.edge == 'left' || self.options.edge == 'top' - }); + self.clientXY = self.options.orientation == 'horizontal' + ? 'clientY' : 'clientX'; + self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation]; + self.edges = Ox.UI.EDGES[self.options.orientation]; + self.isLeftOrTop = self.options.edge == 'left' || self.options.edge == 'top'; that.css({cursor: getCursor()}); - function dragstart(data) { - if (self.options.resizable && !self.options.collapsed) { - Ox.$body.addClass('OxDragging'); - self.drag = { - startPos: data[self.clientXY], - startSize: self.options.size - } - } - } - - function drag(data) { - if (self.options.resizable && !self.options.collapsed) { - var d = data[self.clientXY] - self.drag.startPos, - size = self.options.size; - self.options.size = Ox.limit( - self.drag.startSize + d * (self.isLeftOrTop ? 1 : -1), - self.options.resize[0], - self.options.resize[self.options.resize.length - 1] - ); - Ox.forEach(self.options.resize, function(v) { - if (self.options.size >= v - 8 && self.options.size <= v + 8) { - self.options.size = v; - return false; // break - } - }); - if (self.options.size != size) { - that.css(self.edges[self.isLeftOrTop ? 2 : 3], self.options.size + 'px'); - // fixme: send {size: x}, not x - if (self.isLeftOrTop) { - self.options.elements[0] - .css(self.dimensions[1], self.options.size + 'px'); - self.options.elements[1] - .css(self.edges[2], (self.options.size + 1) + 'px'); - } else { - self.options.elements[0] - .css(self.edges[3], (self.options.size + 1) + 'px'); - self.options.elements[1] - .css(self.dimensions[1], self.options.size + 'px'); - } - triggerEvents('resize'); - self.options.parent.updateSize(self.isLeftOrTop ? 0 : 1, self.options.size); // fixme: listen to event instead? - } - } - } - - function dragend() { - if (self.options.resizable && !self.options.collapsed) { - Ox.$body.removeClass('OxDragging'); - self.options.size != self.drag.startSize && triggerEvents('resizeend'); - } - } - function getCursor() { var cursor = ''; if (self.options.collapsed) { @@ -146,7 +91,7 @@ Ox.Resizebar = function(options, self) { return cursor + '-resize'; } - function getTitle() { + function getTooltipTitle() { var title = ''; if (self.options.collapsed) { title = Ox._('Click to show'); @@ -156,54 +101,94 @@ Ox.Resizebar = function(options, self) { } if (self.options.collapsible) { title = title - ? Ox._('{0} or click to hide', [title]) + ? Ox._('{0}{1} click to hide', [ + title, self.options.resettable ? ',' : ' or' + ]) : Ox._('Click to hide'); } + if (self.options.resettable) { + title += ' or doubleclick to reset' + } } - if (title && Ox.isString(self.options.tooltip)) { + if (title && self.options.tooltip) { title += ' ' + self.options.tooltip; } return title; } + function onDragstart(data) { + if (self.options.resizable && !self.options.collapsed) { + Ox.$body.addClass('OxDragging'); + self.drag = { + startPos: data[self.clientXY], + startSize: self.options.size + } + } + that.triggerEvent('resizestart', {size: self.options.size}); + } + + function onDrag(data) { + if (self.options.resizable && !self.options.collapsed) { + var delta = data[self.clientXY] - self.drag.startPos, + size = self.options.size; + self.options.size = Ox.limit( + self.drag.startSize + delta * (self.isLeftOrTop ? 1 : -1), + self.options.resize[0], + self.options.resize[self.options.resize.length - 1] + ); + Ox.forEach(self.options.resize, function(value) { + if ( + self.options.size >= value - 8 + && self.options.size <= value + 8 + ) { + self.options.size = value; + return false; // break + } + }); + if (self.options.size != size) { + that.css( + self.edges[self.isLeftOrTop ? 2 : 3], + self.options.size + 'px' + ) + .triggerEvent('resize', {size: self.options.size}); + } + } + } + + function onDragpause() { + if (self.options.resizable && !self.options.collapsed) { + if (self.options.size != self.drag.startSize) { + that.triggerEvent('resizepause', {size: self.options.size}); + } + } + } + + function onDragend() { + if (self.options.resizable && !self.options.collapsed) { + Ox.$body.removeClass('OxDragging'); + if (self.options.size != self.drag.startSize) { + that.triggerEvent('resizeend', {size: self.options.size}); + } + } + } + function reset() { if (self.options.resizable && !self.options.collapsed) { - // fixme: silly, pass an option - self.options.parent.reset( - self.isLeftOrTop ? 0 - : self.options.parent.options('elements').length - 1 - ); + that.triggerEvent('reset'); } } function toggle() { if (self.options.collapsible) { - // fixme: silly, pass an option - self.options.parent.toggle( - self.isLeftOrTop ? 0 - : self.options.parent.options('elements').length - 1 - ); self.options.collapsed = !self.options.collapsed; that.css({cursor: getCursor()}); self.$tooltip && self.$tooltip.hide(function() { - self.$tooltip.options({title: getTitle()}); + self.$tooltip.options({title: getTooltipTitle()}); }); + that.triggerEvent('toggle', {collapsed: self.options.collapsed}); } } - function triggerEvents(event) { - self.options.elements[0].triggerEvent(event, { - size: self.isLeftOrTop - ? self.options.size - : self.options.elements[0][self.dimensions[1]]() - }); - self.options.elements[1].triggerEvent(event, { - size: self.isLeftOrTop - ? self.options.elements[1][self.dimensions[1]]() - : self.options.size - }); - } - return that; }; diff --git a/source/Ox.UI/js/Panel/SplitPanel.js b/source/Ox.UI/js/Panel/SplitPanel.js index a11e3d2a..3be3bf13 100644 --- a/source/Ox.UI/js/Panel/SplitPanel.js +++ b/source/Ox.UI/js/Panel/SplitPanel.js @@ -6,10 +6,14 @@ Ox.SplitPanel SpliPanel Object elements <[o]|[]> Array of two or three element objects collapsible If true, can be collapsed (if outer element) collapsed If true, is collapsed (if collapsible) - defaultSize Default size in px (restorable via reset) + defaultSize Default size in px (restorable via reset) element Any Ox.Element If any element is collapsible or resizable, all elements must have an id. + resettable If true, can be resetted (if outer element) + Note that reset fires on doubleclick, and if the element is also + collapsible, toggle now fires on singleclick, no longer on click. + Singleclick happens 250 ms later. resizable If true, can be resized (if outer element) resize <[n]|[]> Min size, optional snappy points, and max size size Size in px (one element must be "auto") @@ -19,15 +23,16 @@ Ox.SplitPanel SpliPanel Object ([options[, self]]) -> SpliPanel Object resize resize Fires on resize, on both elements being resized + resizeend resizeend + Fires on resize, on both elements being resized + resizepause resizepause + Fires on resize, on both elements being resized toggle toggle Fires on collapse or expand, on the element being toggled @*/ Ox.SplitPanel = function(options, self) { - // fixme: doubleclick on resizebar should reset to initial size - // (but anyclick would become singleclick, i.e. less responsive) - self = self || {}; var that = Ox.Element({}, self) .defaults({ @@ -37,83 +42,77 @@ Ox.SplitPanel = function(options, self) { .options(options || {}) .addClass('OxSplitPanel'); - Ox.extend(self, { - dimensions: Ox.UI.DIMENSIONS[self.options.orientation], - edges: Ox.UI.EDGES[self.options.orientation], - defaultSize: self.options.elements.map(function(element) { - return !Ox.isUndefined(element.defaultSize) - ? element.defaultSize : element.size; - }), - length: self.options.elements.length, - resizebarElements: [], - $resizebars: [] + self.defaultSize = self.options.elements.map(function(element) { + return !Ox.isUndefined(element.defaultSize) + ? element.defaultSize : element.size; }); + self.dimensions = Ox.UI.DIMENSIONS[self.options.orientation]; + self.edges = Ox.UI.EDGES[self.options.orientation]; + self.initialized = false; + self.length = self.options.elements.length; - // create elements - // fixme: attach to self instead? - that.$elements = Ox.map(self.options.elements, function(element, i) { - self.options.elements[i] = Ox.extend({ + self.$elements = []; + self.$resizebars = []; + self.options.elements.forEach(function(element, index) { + var elementIndices = index == 0 ? [0, 1] : index == 1 ? [1, 0] : [2, 1], + resizebarIndex = self.$resizebars.length; + self.options.elements[index] = Ox.extend({ collapsible: false, collapsed: false, + defaultSize: 'auto', + resettable: false, resizable: false, resize: [], - size: 'auto' + size: 'auto', + tooltip: false }, element); // top and bottom (horizontal) or left and right (vertical) self.edges.slice(2).forEach(function(edge) { - element.element.css(edge, (parseInt(element.element.css(edge)) || 0) + 'px'); + if (!element.element.css(edge)) { + element.element.css(edge, 0); + } }); - return element.element; - }); - - // create resizebars - self.options.elements.forEach(function(element, i) { - var index = i == 0 ? 0 : 1; - that.$elements[i].appendTo(that.$element); // fixme: that.$content + if (element.collapsed) { + // left/right (horizontal) or top/bottom (vertical) + that.css(self.edges[index == 0 ? 0 : 1], -element.size + 'px'); + } + self.$elements[index] = element.element.appendTo(that); if (element.collapsible || element.resizable) { - self.resizebarElements[index] = i < 2 ? [0, 1] : [1, 2]; - self.$resizebars[index] = Ox.Resizebar({ + self.$resizebars[resizebarIndex] = Ox.Resizebar({ collapsed: element.collapsed, collapsible: element.collapsible, - edge: self.edges[index], - elements: [ - that.$elements[self.resizebarElements[index][0]], - that.$elements[self.resizebarElements[index][1]] - ], - id: element.element.options('id'), + edge: self.edges[index == 0 ? 0 : 1], orientation: self.options.orientation == 'horizontal' ? 'vertical' : 'horizontal', - parent: that, // fixme: that.$content + resettable: element.resettable, resizable: element.resizable, resize: element.resize, size: element.size, - tooltip: element.tooltip - }); - self.$resizebars[index][ - i == 0 ? 'insertAfter' : 'insertBefore' - ](that.$elements[i]); + tooltip: element.tooltip === true ? '' : element.tooltip + }) + .bindEvent({ + reset: function() { + that.resetElement(index); + }, + resize: function(data) { + onResize(elementIndices, data.size); + triggerEvents(elementIndices, 'resize', data); + }, + resizepause: function(data) { + triggerEvents(elementIndices, 'resizepause', data); + }, + resizeend: function(data) { + triggerEvents(elementIndices, 'resizeend', data); + }, + toggle: function(data) { + that.toggleElement(index); + } + }) + [index == 0 ? 'insertAfter' : 'insertBefore'](self.$elements[index]); } }); - self.options.elements.forEach(function(element, i) { - element.collapsed && that.css( - // left/right (horizontal) or top/bottom (vertical) - self.edges[i == 0 ? 0 : 1], -element.size + 'px' - ); - }); - - setSizes(true); - - function getIndexById(id) { - var index = -1; - Ox.forEach(self.options.elements, function(element, i) { - if (element.element.options('id') == id) { - index = i; - return false; // break - } - }); - return index; - } + setSizes(); function getSize(index) { var element = self.options.elements[index]; @@ -125,41 +124,50 @@ Ox.SplitPanel = function(options, self) { return getSize(index) * !element.collapsed; } - function setSizes(init, animate) { + function onResize(elementIndices, size) { + var dimension = self.dimensions[0], + edge = self.edges[elementIndices[0] == 0 ? 0 : 1]; + self.options.elements[elementIndices[0]].size = size; + elementIndices.forEach(function(elementIndex, index) { + self.$elements[elementIndex].css( + index == 0 ? dimension : edge, + index == 0 ? size : size + 1 + ); + }); + } + + function setSizes(animate) { // will animate if animate is truthy and call animate if it's a function - self.options.elements.forEach(function(element, i) { - // fixme: maybe we can add a conditional here, since init - // is about elements that are collapsed splitpanels - var css = {}, + self.options.elements.forEach(function(element, index) { + var $resizebar, + css = {}, edges = self.edges.slice(0, 2).map(function(edge) { // left/right (horizontal) or top/bottom (vertical) - return init && parseInt(that.$elements[i].css(edge)) || 0; + var value = parseInt(self.$elements[index].css(edge)); + return !self.initialized && value || 0; }); if (element.size != 'auto') { // width (horizontal) or height (vertical) css[self.dimensions[0]] = element.size + 'px'; } - if (i == 0) { + if (index == 0) { // left (horizontal) or top (vertical) css[self.edges[0]] = edges[0] + 'px'; // right (horizontal) or bottom (vertical) if (element.size == 'auto') { - css[self.edges[1]] = getSize(1) - + ( + css[self.edges[1]] = getSize(1) + ( self.length == 3 ? getVisibleSize(2) : 0 ) + 'px'; } - } else if (i == 1) { + } else if (index == 1) { // left (horizontal) or top (vertical) if (self.options.elements[0].size != 'auto') { - css[self.edges[0]] = edges[0] + getSize(0) + 'px' + css[self.edges[0]] = edges[0] + getSize(0) + 'px'; } else { css[self.edges[0]] = 'auto'; // fixme: why is this needed? } // right (horizontal) or bottom (vertical) - css[self.edges[1]] = ( - self.length == 3 ? getSize(2) : 0 - ) + 'px'; + css[self.edges[1]] = (self.length == 3 ? getSize(2) : 0) + 'px'; } else { // left (horizontal) or top (vertical) if (element.size == 'auto') { @@ -171,117 +179,74 @@ Ox.SplitPanel = function(options, self) { css[self.edges[1]] = edges[1] + 'px'; } if (animate) { - that.$elements[i].animate(css, 250, function() { - i == 0 && Ox.isFunction(animate) && animate(); + self.$elements[index].animate(css, 250, function() { + index == 0 && Ox.isFunction(animate) && animate(); }); } else { - that.$elements[i].css(css); + self.$elements[index].css(css); } if (element.collapsible || element.resizable) { - css = {}; + $resizebar = self.$resizebars[ + index < 2 ? 0 : self.$resizebars.length - 1 + ]; // left or right (horizontal) or top or bottom (vertical) - css[self.edges[i == 0 ? 0 : 1]] = element.size + 'px' + css = Ox.extend( + {}, self.edges[index == 0 ? 0 : 1], element.size + 'px' + ); if (animate) { - self.$resizebars[i == 0 ? 0 : 1].animate(css, 250); + $resizebar.animate(css, 250); } else { - self.$resizebars[i == 0 ? 0 : 1].css(css); + $resizebar.css(css); } - self.$resizebars[i == 0 ? 0 : 1].options({size: element.size}); + $resizebar.options({size: element.size}); } }); + self.initialized = true; + } + + function triggerEvents(elementIndices, event, data) { + elementIndices.forEach(function(elementIndex, index) { + var $element = self.$elements[elementIndex], + size = index == 0 ? data.size : $element[self.dimensions[0]](); + $element.triggerEvent(event, {size: size}); + }); } /*@ - getSize get size of panel - (id) -> id or index of element, returns size - id The element's id or index + isCollapsed Tests if an outer element is collapsed + (index) -> True if collapsed + index The element's index @*/ - // fixme: what is this? there is that.size() - that.getSize = function(id) { - var index = Ox.isNumber(id) ? id : getIndexById(id), - element = self.options.elements[index]; - return element.element[self.dimensions[0]]() * !that.isCollapsed(index); - }; - - /*@ - isCollapsed panel collapsed state - (id) -> id or index of element, returns collapsed state - id The element's id or index - @*/ - that.isCollapsed = function(id) { - var index = Ox.isNumber(id) ? id : getIndexById(id); + that.isCollapsed = function(index) { return self.options.elements[index].collapsed; }; /*@ - replaceElement Replace panel element - (id, element) -> replace element - id The element's id or index + replaceElement Replaces an element + (index, element) -> replace element + index The element's index element New element @*/ - that.replaceElement = function(id, element) { - // one can pass index instead of id - var index = Ox.isNumber(id) ? id : getIndexById(id); + that.replaceElement = function(index, element) { // top and bottom (horizontal) or left and right (vertical) self.edges.slice(2).forEach(function(edge) { element.css(edge, (parseInt(element.css(edge)) || 0) + 'px'); }); - that.$elements[index] = element; + self.$elements[index] = element; self.options.elements[index].element.replaceWith( self.options.elements[index].element = element ); setSizes(); - self.$resizebars.forEach(function($resizebar, i) { - $resizebar.options({ - elements: [ - that.$elements[self.resizebarElements[i][0]], - that.$elements[self.resizebarElements[i][1]] - ] - }); - }); return that; }; /*@ - replaceElements replace panel elements - (elements) -> replace elements - elements array of new elements + resetElement Resets an outer element to its initial size @*/ - that.replaceElements = function(elements) { - elements.forEach(function(element, i) { - if (Ox.isNumber(element.size)) { - that.size(i, element.size); - if (element.collapsible || element.resizable) { - self.$resizebars[i == 0 ? 0 : 1].options({ - collapsible: element.collapsible, - resizable: element.resizable, - size: element.size - }); - } - } - that.replace(i, element.element); - }); - self.options.elements = elements; - self.$resizebars.forEach(function($resizebar, i) { - $resizebar.options({ - elements: [ - that.$elements[self.resizebarElements[i][0]], - that.$elements[self.resizebarElements[i][1]] - ] - }); - }); - return that; - }; - - /*@ - reset Reset an outer element to its initial size - @*/ - that.reset = function(id) { - // one can pass index instead of id - var index = Ox.isNumber(id) ? id : getIndexById(id), - element = self.options.elements[index]; + that.resetElement = function(index) { + var element = self.options.elements[index]; element.size = self.defaultSize[index]; - setSizes(false, function() { + setSizes(function() { element.element.triggerEvent('resize', { size: element.size }); @@ -290,72 +255,60 @@ Ox.SplitPanel = function(options, self) { size: element.element[self.dimensions[0]]() }); }); + return that; }; /*@ size Get or set size of an element - (id) -> Returns size - (id, size) -> Sets size, returns SplitPanel - (id, size, callback) -> Sets size with animation, returns SplitPanel - id The element's id or index + (index) -> Returns size + (index, size) -> Sets size, returns SplitPanel + (index, size, callback) -> Sets size with animation, returns SplitPanel + index The element's index size New size, in px callback Callback function (passing true animates w/o callback) @*/ - that.size = function(id, size, callback) { - // one can pass index instead of id - var index = Ox.isNumber(id) ? id : getIndexById(id), - element = self.options.elements[index]; + that.resizeElement = that.size = function(index, size, callback) { + var element = self.options.elements[index]; if (arguments.length == 1) { - return element.element[self.dimensions[0]]() * !that.isCollapsed(index); + return element.element[self.dimensions[0]]() + * !that.isCollapsed(index); } else { element.size = size; - setSizes(false, callback); + setSizes(callback); return that; } }; /*@ - toggle Toggles collapsed state of an outer element - (id) -> The SplitPanel - id The element's id or index + toggleElement Toggles collapsed state of an outer element + (index) -> The SplitPanel + index The element's index @*/ - // FIXME: 'toggle' is reserved by jQuery - that.toggle = function(id) { - // one can pass index instead of id + that.toggleElement = function(index) { if (self.toggling) { - return false; + return that; } - var index = Ox.isNumber(id) ? id : getIndexById(id), - element = self.options.elements[index], - value = parseInt(that.css(self.edges[index == 0 ? 0 : 1]), 10) - + element.element[self.dimensions[0]]() * (element.collapsed ? 1 : -1), - animate = {}; + var element = self.options.elements[index], + value = parseInt(that.css(self.edges[index == 0 ? 0 : 1])) + + element.element[self.dimensions[0]]() + * (element.collapsed ? 1 : -1), + animate = Ox.extend({}, self.edges[index == 0 ? 0 : 1], value); self.toggling = true; - animate[self.edges[index == 0 ? 0 : 1]] = value; that.animate(animate, 250, function() { element.collapsed = !element.collapsed; element.element.triggerEvent('toggle', { collapsed: element.collapsed }); - self.$resizebars[index == 0 ? 0 : 1].options({collapsed: element.collapsed}); + self.$resizebars[index < 2 ? 0 : self.$resizebars.length - 1].options({ + collapsed: element.collapsed + }); element = self.options.elements[index == 0 ? 1 : index - 1]; element.element.triggerEvent('resize', { size: element.element[self.dimensions[0]]() }); self.toggling = false; }); - }; - - /*@ - updateSize update size of element - (index, size) -> update size of element - index index of element - size new size - @*/ - that.updateSize = function(index, size) { - // this is called from resizebar - index = index == 0 ? 0 : self.options.elements.length - 1; // fixme: silly that 0 or 1 is passed, and not index - self.options.elements[index].size = size; + return that; }; return that;