// 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'); 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(v, i) { self.options.elements[i] = Ox.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); } }); } /*@ isCollapsed panel collapsed state (id) -> id or position of panel, returns collapsed state id id or position of element @*/ 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 id or position of element 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'); 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; }; /*@ repleaseElements 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 (id) -> get size (id, size) -> set size id id or position of panel size size to set @*/ 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; } }; /*@ getSize get size of panel (id) -> id or position of panel, returns size id id or position of panel @*/ 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 toggle collapsed state of a panel (id) -> toggle panel id id or position of panel @*/ 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', {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 }; 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; 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; };