509 lines
18 KiB
JavaScript
509 lines
18 KiB
JavaScript
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
|
/*@
|
|
Ox.SplitPanel <f:Ox.Element> SpliPanel Object
|
|
() -> <f> SpliPanel Object
|
|
(options) -> <f> SpliPanel Object
|
|
(options, self) -> <f> SpliPanel Object
|
|
options <o> Options object
|
|
elements <[o]|[]> Array of two or three element objects
|
|
collapsible <b|false> If true, can be collapsed (if outer element)
|
|
collapsed <b|false> If true, is collapsed (if collapsible)
|
|
element <o> Any Ox.Element
|
|
If any element is collapsible or resizable, all elements must
|
|
have an id.
|
|
resizable <b|false> If true, can be resized (if outer element)
|
|
resize <[n]|[]> Min size, optional snappy points, and max size
|
|
size <n|s|"auto"> Size in px (one element must be "auto")
|
|
tooltip <b|s|false> If true, show tooltip, if string, append it
|
|
orientation <s|"horizontal"> orientation ("horizontal" or "vertical")
|
|
self <o> 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, 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 ? getVisibleSize(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 <f> panel collapsed state
|
|
(id) -> <b> id or position of panel, returns collapsed state
|
|
id <i> 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 <f> replace panel element
|
|
(id, element) -> <f> replace element
|
|
id <s|i> The element's id or position
|
|
element <o> 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;
|
|
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 <f> replace panel elements
|
|
(elements) -> <f> replace elements
|
|
elements <a> 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 <f> Get or set size of an element
|
|
(id) -> <i> Returns size
|
|
(id, size) -> <o> Sets size, returns SplitPanel
|
|
(id, size, callback) -> <o> Sets size with animation, returns SplitPanel
|
|
id <i> The element's id or position
|
|
size <i> New size, in px
|
|
callback <b|f> 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 <f> get size of panel
|
|
(id) -> <i> id or position of panel, returns size
|
|
id <s|i> 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 <f> Toggles collapsed state of an outer element
|
|
(id) -> <o> The SplitPanel
|
|
id <s|i> 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
|
|
});
|
|
element = self.options.elements[pos == 0 ? 1 : pos - 1];
|
|
element.element.triggerEvent('resize', {
|
|
size: element.element[self.dimensions[0]]()
|
|
});
|
|
});
|
|
};
|
|
|
|
/*@
|
|
updateSize <f> update size of element
|
|
(pos, size) -> <o> update size of element
|
|
pos <i> position of element
|
|
size <n> 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($('<div>').addClass('OxSpace'))
|
|
.append($('<div>').addClass('OxLine'))
|
|
.append($('<div>').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;
|
|
|
|
};
|