some redesign for better garbage collection of elements

This commit is contained in:
j 2011-11-04 22:14:30 +00:00
parent 01f02d9730
commit 1d09d19423
17 changed files with 129 additions and 101 deletions

View file

@ -103,26 +103,7 @@ Ox.Element = function(options, self) {
.addClass('OxElement') .addClass('OxElement')
.mousedown(mousedown); .mousedown(mousedown);
if (self.options.tooltip) { setTooltip();
if (Ox.isString(self.options.tooltip)) {
that.$tooltip = Ox.Tooltip({
title: self.options.tooltip,
});
that.bind({
mouseenter: mouseenter
});
} else {
that.$tooltip = Ox.Tooltip({
animate: false
});
that.bind({
mousemove: mousemove
});
}
that.bind({
mouseleave: mouseleave
});
}
function bind(action, event, callback) { function bind(action, event, callback) {
self.$eventHandler[action]('ox_' + event, function(event, data) { self.$eventHandler[action]('ox_' + event, function(event, data) {
@ -259,12 +240,40 @@ Ox.Element = function(options, self) {
}).show(e); }).show(e);
} }
// FIXME: in other widgets, use this,
// rather than some self.$tooltip that
// will not get garbage collected
function setTooltip() {
if (self.options.tooltip) {
if (Ox.isString(self.options.tooltip)) {
that.$tooltip = Ox.Tooltip({
title: self.options.tooltip,
});
that.bind({
mouseenter: mouseenter
});
} else {
that.$tooltip = Ox.Tooltip({
animate: false
});
that.bind({
mousemove: mousemove
});
}
that.bind({
mouseleave: mouseleave
});
} else {
that.$tooltip && that.$tooltip.remove();
}
}
self.setOption = function(key, value) { self.setOption = function(key, value) {
// self.setOption(key, value) // self.setOption(key, value)
// is called when an option changes // is called when an option changes
// (to be implemented by widget) // (to be implemented by widget)
if (key == 'tooltip') { if (key == 'tooltip') {
that.$tooltip && that.$tooltip.options({title: value}); setTooltip();
} }
}; };
@ -320,18 +329,6 @@ Ox.Element = function(options, self) {
return that; return that;
}; };
that.empty = function() {
that.$element.find('.OxElement').each(function() {
var oxid = $(this).data('oxid'),
element = Ox.UI.elements[oxid];
//!element && Ox.Log('Core', 'NO ELEMENT FOR', oxid, this.className)
//element && Ox.Log('Core', 'OK', oxid, this.className)
element && element.removeElement(false);
});
that.$element.empty();
return that;
};
/*@ /*@
gainFocus <function> Makes an element object gain focus gainFocus <function> Makes an element object gain focus
() -> <obj> This element object () -> <obj> This element object
@ -378,43 +375,27 @@ Ox.Element = function(options, self) {
return Ox.getset(self.options, arguments, self.setOption, that); return Ox.getset(self.options, arguments, self.setOption, that);
}; };
that.setElement = function($element) {
that.$element.replaceWith(that.$element = $element);
that.$element.data({oxid: that.id});
that[0] = that.$element[0];
};
/*@ /*@
removeElement <function> Removes an element object and its event handler removeElement <function> Removes an element object and its event handler
() -> <obj> This element () -> <obj> This element
@*/ @*/
that.remove = that.removeElement = function(remove) { that.remove = function(remove) {
//remove !== false && (len = Ox.len(Ox.UI.elements));
///*
remove !== false && that.$element.find('.OxElement').each(function() { remove !== false && that.$element.find('.OxElement').each(function() {
var oxid = $(this).data('oxid'), var oxid = $(this).data('oxid'),
element = Ox.UI.elements[oxid]; element = Ox.UI.elements[oxid];
//!element && Ox.Log('Core', 'NO ELEMENT FOR', oxid, this.className) element && element.remove(false);
//element && Ox.Log('Core', 'OK', oxid, this.className)
element && element.removeElement(false);
}); });
//*/
Ox.Focus.remove(that.id); Ox.Focus.remove(that.id);
delete self.$eventHandler; delete self.$eventHandler;
delete Ox.UI.elements[that.id]; delete Ox.UI.elements[that.id];
that.$tooltip && that.$tooltip.remove(); that.$tooltip && that.$tooltip.remove();
remove !== false && that.$element.remove(); remove !== false && that.$element.remove();
/*
if (remove !== false) {
var orphaned = 0;
Ox.forEach(Ox.UI.elements, function(element) {
if (!element.parent().length) {
orphaned++;
}
});
Ox.Log('Core', 'LEN', len, '-->', Ox.len(Ox.UI.elements), 'orphaned:', orphaned);
}
*/
return that;
};
that.replaceWith = function($element) {
$element.insertAfter(that);
that.remove();
return that; return that;
}; };

View file

@ -0,0 +1,43 @@
// vim: et:ts=4:sw=4:sts=4:ft=javascript
Ox.GarbageCollection = (function() {
var that = function() {
collect();
};
setInterval(collect, 60000);
function collect() {
var len = Ox.len(Ox.UI.elements);
Object.keys(Ox.UI.elements).forEach(function(id) {
if (
Ox.UI.elements[id]
&& Ox.isUndefined(Ox.UI.elements[id].$element.data('oxid'))
) {
Ox.UI.elements[id].remove();
delete Ox.UI.elements[id];
}
});
Ox.Log('GC', len, '-->', Ox.len(Ox.UI.elements));
}
that.debug = function() {
var classNames = {}, sorted = [];
Ox.forEach(Ox.UI.elements, function(element, id) {
var className = element.$element[0].className;
classNames[className] = (classNames[className] || 0) + 1;
});
Ox.forEach(classNames, function(count, className) {
sorted.push({className: className, count: count});
})
return sorted.sort(function(a, b) {
return a.count - b.count;
}).map(function(v) {
return v.count + ' ' + v.className
}).join('\n');
};
return that;
}());

View file

@ -111,7 +111,7 @@ Ox.SyntaxHighlighter = function(options, self) {
.html(Ox.repeat('&nbsp;', self.options.lineLength)) .html(Ox.repeat('&nbsp;', self.options.lineLength))
.appendTo(that); .appendTo(that);
width = $line.width() + 4; // add padding width = $line.width() + 4; // add padding
$line.removeElement(); $line.remove();
['moz', 'webkit'].forEach(function(browser) { ['moz', 'webkit'].forEach(function(browser) {
$source.css({ $source.css({
background: '-' + browser + background: '-' + browser +

View file

@ -102,7 +102,7 @@ Ox.ArrayInput = function(options, self) {
Ox.Log('Form', 'remove', i); Ox.Log('Form', 'remove', i);
['input', 'removeButton', 'addButton', 'element'].forEach(function(element) { ['input', 'removeButton', 'addButton', 'element'].forEach(function(element) {
var key = '$' + element; var key = '$' + element;
self[key][i].removeElement(); self[key][i].remove();
self[key].splice(i, 1); self[key].splice(i, 1);
}); });
self.$input.length == 1 && self.$removeButton[0].options({title: 'close'}); self.$input.length == 1 && self.$removeButton[0].options({title: 'close'});

View file

@ -72,12 +72,9 @@ Ox.Button = function(options, self) {
setTitle(self.titles[self.selectedTitle].title); setTitle(self.titles[self.selectedTitle].title);
if (self.options.tooltip) { if (self.options.tooltip) {
self.tooltips = Ox.isArray(self.options.tooltip) ? self.options.tooltip : [self.options.tooltip]; self.tooltips = Ox.isArray(self.options.tooltip)
self.$tooltip = Ox.Tooltip({ ? self.options.tooltip : [self.options.tooltip];
title: self.tooltips[self.selectedTitle] that.options({tooltip: self.tooltips[self.selectedTitle]});
});
that.mouseenter(mouseenter)
.mouseleave(mouseleave);
} }
function click() { function click() {
@ -108,14 +105,6 @@ Ox.Button = function(options, self) {
} }
} }
function mouseenter(e) {
self.$tooltip.show(e.clientX, e.clientY);
}
function mouseleave() {
self.$tooltip.hide();
}
function setTitle(title) { function setTitle(title) {
self.title = title; self.title = title;
if (self.options.type == 'image') { if (self.options.type == 'image') {
@ -138,6 +127,8 @@ Ox.Button = function(options, self) {
that.toggleClass('OxSelected'); that.toggleClass('OxSelected');
} }
that.triggerEvent('change'); that.triggerEvent('change');
} else if (key == 'tooltip') {
} else if (key == 'title') { } else if (key == 'title') {
setTitle(value); setTitle(value);
} else if (key == 'width') { } else if (key == 'width') {
@ -145,7 +136,7 @@ Ox.Button = function(options, self) {
width: (value - 14) + 'px' width: (value - 14) + 'px'
}); });
} }
} };
/*@ /*@
toggleDisabled <f> toggleDisabled <f>
@ -180,7 +171,7 @@ Ox.Button = function(options, self) {
setTitle(self.titles[self.selectedTitle].title); setTitle(self.titles[self.selectedTitle].title);
// fixme: if the tooltip is visible // fixme: if the tooltip is visible
// we also need to call show() // we also need to call show()
self.$tooltip && self.$tooltip.options({ that.$tooltip && that.$tooltip.options({
title: self.tooltips[self.selectedTitle] title: self.tooltips[self.selectedTitle]
}); });
return that; return that;

View file

@ -123,7 +123,7 @@ Ox.Form = function(options, self) {
that.removeItem = function(pos) { that.removeItem = function(pos) {
Ox.Log('Form', 'removeItem', pos); Ox.Log('Form', 'removeItem', pos);
self.$items[pos].removeElement(); self.$items[pos].remove();
self.options.items.splice(pos, 1); self.options.items.splice(pos, 1);
self.$items.splice(pos, 1); self.$items.splice(pos, 1);
} }

View file

@ -195,6 +195,12 @@ Ox.Select = function(options, self) {
self.$menu.getItem(id).options({disabled: false}); self.$menu.getItem(id).options({disabled: false});
}; };
self.superRemove = that.remove;
that.remove = function() {
self.$menu.remove();
self.superRemove();
};
// FIXME: selected() _and_ selectItem() _and_ value() ??? // FIXME: selected() _and_ selectItem() _and_ value() ???
/*@ /*@

View file

@ -763,7 +763,7 @@ Ox.List = function(options, self) {
page == 0 && fillFirstPage(); page == 0 && fillFirstPage();
// FIXME: why does emptyPage sometimes have no methods? // FIXME: why does emptyPage sometimes have no methods?
//Ox.Log('List', 'emptyPage', $emptyPage) //Ox.Log('List', 'emptyPage', $emptyPage)
$emptyPage && $emptyPage.removeElement && $emptyPage.removeElement(); $emptyPage && $emptyPage.remove && $emptyPage.remove();
self.$pages[page].appendTo(that.$content); self.$pages[page].appendTo(that.$content);
!Ox.isUndefined(callback) && callback(); // fixme: callback necessary? why not bind to event? !Ox.isUndefined(callback) && callback(); // fixme: callback necessary? why not bind to event?
})); }));
@ -1271,7 +1271,7 @@ Ox.List = function(options, self) {
//Ox.Log('List', 'self.$pages', self.$pages) //Ox.Log('List', 'self.$pages', self.$pages)
//Ox.Log('List', 'page not undefined', !Ox.isUndefined(self.$pages[page])) //Ox.Log('List', 'page not undefined', !Ox.isUndefined(self.$pages[page]))
if (!Ox.isUndefined(self.$pages[page])) { if (!Ox.isUndefined(self.$pages[page])) {
self.$pages[page].removeElement(); self.$pages[page].remove();
delete self.$pages[page]; delete self.$pages[page];
} }
} }
@ -1593,7 +1593,7 @@ Ox.List = function(options, self) {
} else { // remove items from pos to pos+length } else { // remove items from pos to pos+length
Ox.range(pos, pos + length).forEach(function(i) { Ox.range(pos, pos + length).forEach(function(i) {
self.selected.indexOf(i) > -1 && deselect(i); self.selected.indexOf(i) > -1 && deselect(i);
self.$items[i].removeElement(); self.$items[i].remove();
}); });
self.options.items.splice(pos, length); self.options.items.splice(pos, length);
self.$items.splice(pos, length); self.$items.splice(pos, length);

View file

@ -33,16 +33,17 @@ Ox.ListItem = function(options, self) {
function constructItem(update) { function constructItem(update) {
var $element = self.options.construct(self.options.data) var $element = self.options.construct(self.options.data)
.addClass('OxItem') .addClass('OxItem LISTITEM')
.data({ .data({
id: self.options.data[self.options.unique], id: self.options.data[self.options.unique],
position: self.options.position position: self.options.position
}); });
if (update) { if (update) {
that.$element.hasClass('OxSelected') && $element.addClass('OxSelected'); that.$element.hasClass('OxSelected') && $element.addClass('OxSelected');
that.$element.replaceWith($element); //that.$element.replaceWith($element);
} }
that.$element = $element; //that.$element = $element;
that.setElement($element);
} }
self.setOption = function(key, value) { self.setOption = function(key, value) {

View file

@ -570,7 +570,7 @@ Ox.Map = function(options, self) {
function constructZoomInput() { function constructZoomInput() {
//Ox.Log('Map', 'constructZoomInput', self.minZoom, self.maxZoom) //Ox.Log('Map', 'constructZoomInput', self.minZoom, self.maxZoom)
if (self.options.zoombar) { if (self.options.zoombar) {
self.$zoomInput && self.$zoomInput.removeElement(); self.$zoomInput && self.$zoomInput.remove();
self.$zoomInput = Ox.Range({ self.$zoomInput = Ox.Range({
arrows: true, arrows: true,
max: self.maxZoom, max: self.maxZoom,

View file

@ -119,7 +119,7 @@ Ox.MainMenu = function(options, self) {
function removeMenu(position) { function removeMenu(position) {
that.titles[position].remove(); that.titles[position].remove();
that.menus[position].removeElement(); that.menus[position].remove();
} }
self.setOption = function(key, value) { self.setOption = function(key, value) {

View file

@ -200,11 +200,15 @@ Ox.Menu = function(options, self) {
items.forEach(function(item) { items.forEach(function(item) {
var position; var position;
if ('id' in item) { if ('id' in item) {
that.items.push(Ox.MenuItem(Ox.extend(item, { that.items.push(
maxWidth: self.options.maxWidth, Ox.MenuItem(Ox.extend(item, {
menu: that, maxWidth: self.options.maxWidth,
position: position = that.items.length menu: that,
})).data('position', position).appendTo(that.$content)); // fixme: jquery bug when passing {position: position}? does not return the object?; position: position = that.items.length
}))
.data('position', position)
.appendTo(that.$content)
); // fixme: jquery bug when passing {position: position}? does not return the object?;
if (item.items) { if (item.items) {
that.submenus[item.id] = Ox.Menu({ that.submenus[item.id] = Ox.Menu({
element: that.items[position], element: that.items[position],
@ -665,6 +669,14 @@ Ox.Menu = function(options, self) {
return that; return that;
}; };
self.superRemove = that.remove;
that.remove = function() {
Ox.forEach(that.submenus, function(submenu) {
submenu.remove();
});
self.superRemove();
};
/*@ /*@
removeItem <f> removeItem removeItem <f> removeItem
@*/ @*/

View file

@ -41,7 +41,7 @@ Ox.MenuItem = function(options, self) {
keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard), keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard),
title: Ox.toArray(options.title || self.defaults.title) title: Ox.toArray(options.title || self.defaults.title)
})) }))
.addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : '')) .addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : '') + ' MENUITEM')
/* /*
.attr({ .attr({
id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id) id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id)

View file

@ -195,16 +195,10 @@ Ox.SplitPanel = function(options, self) {
that.$elements[pos] = element that.$elements[pos] = element
.css(self.edges[2], (parseInt(element.css(self.edges[2])) || 0) + 'px') .css(self.edges[2], (parseInt(element.css(self.edges[2])) || 0) + 'px')
.css(self.edges[3], (parseInt(element.css(self.edges[3])) || 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.Log('Panel', 'REPLACE ELEMENT') Ox.Log('Panel', 'REPLACE ELEMENT')
self.options.elements[pos].element.replaceWith( self.options.elements[pos].element.replaceWith(
self.options.elements[pos].element = element self.options.elements[pos].element = element
); );
//*/
setSizes(); setSizes();
self.$resizebars.forEach(function($resizebar, i) { self.$resizebars.forEach(function($resizebar, i) {
$resizebar.options({ $resizebar.options({

View file

@ -341,7 +341,7 @@ Ox.BlockTimeline = function(options, self) {
} }
}); });
while (self.$lines.length > self.lines) { while (self.$lines.length > self.lines) {
self.$lines[self.$lines.length - 1].removeElement(); self.$lines[self.$lines.length - 1].remove();
self.$lines.pop(); self.$lines.pop();
} }
setMarker(); setMarker();

View file

@ -541,7 +541,7 @@ Ox.Dialog = function(options, self) {
self.$content.animate({ self.$content.animate({
opacity: 0 opacity: 0
}, 250, function() { }, 250, function() {
$(this).remove() // fixme: removeElement? $(this).remove();
}); });
self.options.content.css({ self.options.content.css({
opacity: 0 opacity: 0
@ -649,7 +649,7 @@ Ox.Dialog = function(options, self) {
that.animate({ that.animate({
opacity: 0 opacity: 0
}, 250, function() { }, 250, function() {
// that.removeElement(); // that.remove();
that.hide(); that.hide();
callback && callback(); callback && callback();
}); });

View file

@ -24,7 +24,7 @@ Ox.Layer = function(options, self) {
}); });
function click() { function click() {
that.triggerEvent('click').removeElement(); that.triggerEvent('click').remove();
} }
function mousedown() { function mousedown() {
@ -39,7 +39,7 @@ Ox.Layer = function(options, self) {
if (self.options.type == 'dialog') { if (self.options.type == 'dialog') {
Ox.UI.$window.unbind({mouseup: mouseup}); Ox.UI.$window.unbind({mouseup: mouseup});
} }
that.removeElement(); that.remove();
}; };
that.show = function() { that.show = function() {