// vim: et:ts=4:sw=4:sts=4:ft=javascript /*@ Ox.SplitPanel SpliPanel Object () -> SpliPanel Object (options) -> SpliPanel Object (options, self) -> SpliPanel Object options Options 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) element Any Ox.Element If any element is collapsible or resizable, all elements must have an id. 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") tooltip If true, show tooltip, if string, append it orientation orientation ("horizontal" or "vertical") self shared private variable resize resize 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 self = self || {}; var that = Ox.Element({}, self) // fixme: Container? .defaults({ elements: [], orientation: 'horizontal' }) .options(options || {}) .addClass('OxSplitPanel'); Ox.extend(self, { dimensions: Ox.UI.DIMENSIONS[self.options.orientation], edges: Ox.UI.EDGES[self.options.orientation], length: self.options.elements.length, resizebarElements: [], $resizebars: [] }); // create elements that.$elements = []; self.options.elements.forEach(function(element, i) { self.options.elements[i] = Ox.extend({ collapsible: false, collapsed: false, resizable: false, resize: [], size: 'auto' }, element); that.$elements[i] = element.element .css(self.edges[2], (parseInt(element.element.css(self.edges[2])) || 0) + 'px') .css(self.edges[3], (parseInt(element.element.css(self.edges[3])) || 0) + 'px'); //alert(v.element.css(self.edges[3])) }); // create resizebars self.options.elements.forEach(function(element, i) { //that.append(element) var index = i == 0 ? 0 : 1; that.$elements[i].appendTo(that.$element); // fixme: that.$content if (element.collapsible || element.resizable) { self.resizebarElements[index] = i < 2 ? [0, 1] : [1, 2]; self.$resizebars[index] = 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'), orientation: self.options.orientation == 'horizontal' ? 'vertical' : 'horizontal', parent: that, // fixme: that.$content resizable: element.resizable, resize: element.resize, size: element.size, tooltip: element.tooltip }); self.$resizebars[index][i == 0 ? 'insertAfter' : 'insertBefore'](that.$elements[i]); } }); self.options.elements.forEach(function(element, i) { element.collapsed && that.css( self.edges[i == 0 ? 0 : 1], -self.options.elements[i].size + 'px' ); }); setSizes(true); function getPositionById(id) { var position = -1; Ox.forEach(self.options.elements, function(element, i) { if (element.element.options('id') == id) { position = i; return false; } }); return position; } function getSize(element) { return element.size + (element.collapsible || element.resizable); //return (element.size + (element.collapsible || element.resizable)) * !element.collapsed; } function getVisibleSize(element) { return getSize(element) * !element.collapsed; } function setSizes(init, animate) { 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 = {}, edges = [ (init && parseInt(that.$elements[i].css(self.edges[0]))) || 0, (init && parseInt(that.$elements[i].css(self.edges[1]))) || 0 ]; if (element.size != 'auto') { css[self.dimensions[0]] = element.size + 'px'; } if (i == 0) { css[self.edges[0]] = edges[0] + 'px'; if (element.size == 'auto') { css[self.edges[1]] = getSize(self.options.elements[1]) + ( self.length == 3 ? getVisibleSize(self.options.elements[2]) : 0 ) + 'px'; } } else if (i == 1) { if (self.options.elements[0].size != 'auto') { css[self.edges[0]] = edges[0] + getSize(self.options.elements[0]) + 'px' } else { css[self.edges[0]] = 'auto'; // fixme: why is this needed? } css[self.edges[1]] = ( self.length == 3 ? getSize(self.options.elements[2]) : 0 ) + 'px'; } else { if (element.size == 'auto') { css[self.edges[0]] = getVisibleSize(self.options.elements[0]) + getSize(self.options.elements[1]) + 'px'; } else { css[self.edges[0]] = 'auto'; // fixme: why is this needed? } css[self.edges[1]] = edges[1] + 'px'; } if (animate) { that.$elements[i].animate(css, 250, function() { i == 0 && Ox.isFunction(animate) && animate(); }); } else { that.$elements[i].css(css); } if (element.collapsible || element.resizable) { css = {}; css[self.edges[i == 0 ? 0 : 1]] = element.size + 'px' if (animate) { self.$resizebars[i == 0 ? 0 : 1].animate(css, 250); } else { self.$resizebars[i == 0 ? 0 : 1].css(css); } self.$resizebars[i == 0 ? 0 : 1].options({size: element.size}); } }); } /*@ isCollapsed panel collapsed state (id) -> id or position of panel, returns collapsed state id The element's id or position @*/ that.isCollapsed = function(id) { var pos = Ox.isNumber(id) ? id : getPositionById(id); return self.options.elements[pos].collapsed; }; /*@ repleaseElement replace panel element (id, element) -> replace element id The element's id or position element new element @*/ that.replaceElement = function(id, element) { // one can pass pos instead of id var pos = Ox.isNumber(id) ? id : getPositionById(id); that.$elements[pos] = element .css(self.edges[2], (parseInt(element.css(self.edges[2])) || 0) + 'px') .css(self.edges[3], (parseInt(element.css(self.edges[3])) || 0) + 'px'); // fixme: it would be better to call removeElement here, // or have a custom replaceElement function that removes event handlers //self.options.elements[pos].element.replaceWith(element.$element.$element || element.$element) //self.options.elements[pos].element = element; ///* Ox.print('REPLACE ELEMENT') self.options.elements[pos].element.replaceWith( self.options.elements[pos].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 @*/ 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; }; /*@ 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 position size New size, in px callback Callback function (passing true animates w/o callback) @*/ that.size = function(id, size, callback) { // one can pass pos instead of id var pos = Ox.isNumber(id) ? id : getPositionById(id), element = self.options.elements[pos], animate = {}; if (arguments.length == 1) { return element.element[self.dimensions[0]]() * !that.isCollapsed(pos); } else { element.size = size; setSizes(false, callback); return that; } }; /*@ getSize get size of panel (id) -> id or position of panel, returns size id The element's id or position @*/ // fixme: what is this? there is that.size() that.getSize = function(id) { var pos = Ox.isNumber(id) ? id : getPositionById(id), element = self.options.elements[pos]; return element.element[self.dimensions[0]]() * !that.isCollapsed(pos); }; /*@ toggle Toggles collapsed state of an outer element (id) -> The SplitPanel id The element's id or position @*/ that.toggle = function(id) { // one can pass pos instead of id var pos = Ox.isNumber(id) ? id : getPositionById(id), element = self.options.elements[pos], value = parseInt(that.css(self.edges[pos == 0 ? 0 : 1])) + element.element[self.dimensions[0]]() * (element.collapsed ? 1 : -1), animate = {}; animate[self.edges[pos == 0 ? 0 : 1]] = value; that.animate(animate, 250, function() { element.collapsed = !element.collapsed; element.element.triggerEvent('toggle', { 'collapsed': element.collapsed }); self.$resizebars[pos == 0 ? 0 : 1].options({collapsed: element.collapsed}); element = self.options.elements[pos == 0 ? 1 : pos - 1]; element.element.triggerEvent('resize', { size: element.element[self.dimensions[0]]() }); }); }; /*@ updateSize update size of element (pos, size) -> update size of element pos position of element size new size @*/ that.updateSize = function(pos, size) { // this is called from resizebar pos = pos == 0 ? 0 : self.options.elements.length - 1; // fixme: silly that 0 or 1 is passed, and not pos self.options.elements[pos].size = size; }; return that; }; // fixme: remove! /* Ox.SplitPanel_ = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ elements: [], orientation: 'horizontal' }) .options(options) .addClass( 'OxSplitPanel_ Ox' + Ox.toTitleCase(self.options.orientation) ); Ox.extend(self, { $separators: [], clientXY: self.options.orientation == 'horizontal' ? 'clientX' : 'clientY', dimensions: Ox.UI.DIMENSIONS[self.options.orientation], edges: Ox.UI.EDGES[self.options.orientation] }); self.options.elements.forEach(function(element, i) { self.options.elements[i] = Ox.extend({ collapsible: false, collapsed: false, resizable: false, resize: [], size: 'auto' }, element); }); self.autoPercent = (100 - self.options.elements.reduce(function(val, element) { return val + (Ox.endsWith(element.size, '%') ? parseFloat(element.size) : 0); }, 0)) / self.options.elements.filter(function(element) { return element.size == 'auto'; }).length + '%'; self.options.elements.forEach(function(element, i) { var flex, index = i == 0 ? 0 : 1; if (Ox.isNumber(element.size)) { element.element.css(self.dimensions[0], element.size + 'px'); } else { flex = ( element.size == 'auto' ? self.autoPercent : element.size ).replace('%', ''); element.element.css({ boxFlex: flex, MozBoxFlex: flex, WebkitBoxFlex: flex }); } element.element.appendTo(that); if (element.collapsible || element.resizable) { self.$separators.push( Ox.Element() .addClass('OxSeparator') .bindEvent({ anyclick: function() { that.toggle(i); }, dragstart: function(data) { dragstart(i, data); }, drag: function(data) { drag(i, data); }, dragend: function(data) { dragend(i, data); }, }) .append($('
').addClass('OxSpace')) .append($('
').addClass('OxLine')) .append($('
').addClass('OxSpace')) ['insert' + (index ? 'Before' : 'After')](element.element) ); } }); function dragstart(pos, e) { var element = self.options.elements[pos], size = element.element[self.dimensions[0]](); if (element.resizable && !element.collapsed) { self.drag = { size: size, startPos: e[self.clientXY], startSize: size }; } } function drag(pos, e) { var data = {}, element = self.options.elements[pos], index = pos == 0 ? 0 : 1; if (element.resizable && !element.collapsed) { var d = e[self.clientXY] - self.drag.startPos, size = Ox.limit( self.drag.startSize + d * (index ? -1 : 1), element.resize[0], element.resize[element.resize.length - 1] ); Ox.forEach(element.resize, function(v) { if (size >= v - 8 && size <= v + 8) { size = v; return false; } }); if (size != self.drag.size) { self.drag.size = size; data[self.dimensions[0]] = size; element.element .css(self.dimensions[0], size + 'px') .triggerEvent('resize', data); triggerEvents('resize', pos); } } } function dragend(pos, e) { var data = {}, element = self.options.elements[pos]; if (element.resizable && !element.collapsed) { data[self.dimensions[0]] = self.drag.size element.element.triggerEvent('resizeend', data); triggerEvents('resizeend', pos); } } function triggerEvents(event, pos) { var data = {}; self.options.elements.forEach(function(element, i) { if (i != pos && element.size == 'auto') { data[self.dimensions[0]] = element.element[self.dimensions[0]](); element.element.triggerEvent(event, data); } }); } that.replaceElement = function(pos, element) { var $element = self.options.elements[pos].element, size = self.options.elements[pos].size; $element.replaceWith(self.options.elements[pos].element = element); if (size == 'auto') { $element.css(self.boxFlexCSS); } else { $element.css(self.dimensions[0], size + 'px') } return that; }; that.size = function(pos, size) { var element = self.options.elements[pos], ret; if (Ox.isUndefined(size)) { ret = element.element[self.dimensions[0]](); } else { element.size = size; element.element.css(self.dimensions[0], size + 'px') ret = that; } return that; } that.toggle = function(pos) { var css = {}, element = self.options.elements[pos], flex, index = pos == 0 ? 0 : 1, size = element.element[self.dimensions[0]](); if (element.collapsible) { element.collapsed = !element.collapsed; css['margin' + Ox.toTitleCase(self.edges[0][index])] = element.collapsed ? -size : 0; that.animate(css, 250, function() { element.element.triggerEvent('toggle', {collapsed: element.collapsed}); triggerEvents('resize', pos); }); self.$resizebars[pos == 0 ? 0 : 1].options({collapsed: element.collapsed}); } } return that; }; */