// 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) { self = self || {}; var that = Ox.Element({}, self) // fixme: Container? .defaults({ elements: [], orientation: 'horizontal' }) .options(options || {}) .addClass('OxSplitPanel'); $.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(v, i) { self.options.elements[i] = $.extend({ collapsible: false, collapsed: false, resizable: false, resize: [], size: 'auto' }, v); that.$elements[i] = v.element .css(self.edges[2], (parseInt(v.element.css(self.edges[2])) || 0) + 'px') .css(self.edges[3], (parseInt(v.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) //Ox.print('V: ', v, that.$elements[i]) var index = i == 0 ? 0 : 1; that.$elements[i].appendTo(that.$element); // fixme: that.$content if (element.collapsible || element.resizable) { //Ox.print('v.size', v.size) 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; } }); //Ox.print('getPositionById', id, position); 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) { 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 edges = [ (init && parseInt(that.$elements[i].css(self.edges[0]))) || 0, (init && parseInt(that.$elements[i].css(self.edges[1]))) || 0 ]; element.size != 'auto' && that.$elements[i].css(self.dimensions[0], element.size + 'px'); if (i == 0) { that.$elements[i].css( self.edges[0], edges[0] + 'px' ); that.$elements[i].css( self.edges[1], (getSize(self.options.elements[1]) + (length == 3 ? getSize(self.options.elements[2]) : 0)) + 'px' ); } else if (i == 1) { that.$elements[i].css( self.edges[0], self.options.elements[0].size == 'auto' ? 'auto' : edges[0] + getSize(self.options.elements[0]) + 'px' ); (self.options.elements[0].size != 'auto' || element.size != 'auto') && that.$elements[i].css( self.edges[1], (self.length == 3 ? getSize(self.options.elements[2]) : 0) + 'px' ); } else { that.$elements[i].css( self.edges[0], (self.options.elements[1].size == 'auto' || element.size == 'auto') ? 'auto' : (getVisibleSize(self.options.elements[0]) + getVisibleSize(self.options.elements[1])) + 'px' ); that.$elements[i].css( self.edges[1], edges[1] + 'px' ); } if (element.collapsible || element.resizable) { self.$resizebars[i == 0 ? 0 : 1].css(self.edges[i == 0 ? 0 : 1], element.size); } }); } that.isCollapsed = function(id) { var pos = Ox.isNumber(id) ? id : getPositionById(id); return self.options.elements[pos].collapsed; }; 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'); self.options.elements[pos].element.replaceWith(element.$element.$element || element.$element); 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; }; 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; }; that.size = function(id, size) { // one can pass pos instead of id var pos = Ox.isNumber(id) ? id : getPositionById(id), element = self.options.elements[pos]; if (arguments.length == 1) { return element.element[self.dimensions[0]]() * !that.isCollapsed(pos); } else { element.size = size; setSizes(); return that; } }; 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); }; 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, 200, function() { // fixme: 250? element.collapsed = !element.collapsed; element.element.triggerEvent('toggle', { 'collapsed': element.collapsed }); element = self.options.elements[pos == 0 ? 1 : pos - 1]; element.element.triggerEvent( 'resize', element.element[self.dimensions[0]]() ); }); }; 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(event, e) { dragstart(i, e); }, drag: function(event, e) { drag(i, e); }, dragend: function(event, e) { dragend(i, e); }, }) .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 }; Ox.print('self.drag', self.drag) } } 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; Ox.print('css', css); that.animate(css, 250, function() { element.element.triggerEvent('toggle', {collapsed: element.collapsed}); triggerEvents('resize', pos); }); } } return that; };