diff --git a/source/Ox.UI/js/Core/Ox.Element.js b/source/Ox.UI/js/Core/Ox.Element.js index 8455c350..1c4fa35c 100644 --- a/source/Ox.UI/js/Core/Ox.Element.js +++ b/source/Ox.UI/js/Core/Ox.Element.js @@ -103,26 +103,7 @@ Ox.Element = function(options, self) { .addClass('OxElement') .mousedown(mousedown); - 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 - }); - } + setTooltip(); function bind(action, event, callback) { self.$eventHandler[action]('ox_' + event, function(event, data) { @@ -259,12 +240,40 @@ Ox.Element = function(options, self) { }).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(key, value) // is called when an option changes // (to be implemented by widget) if (key == 'tooltip') { - that.$tooltip && that.$tooltip.options({title: value}); + setTooltip(); } }; @@ -320,18 +329,6 @@ Ox.Element = function(options, self) { 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 Makes an element object gain focus () -> This element object @@ -378,43 +375,27 @@ Ox.Element = function(options, self) { 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 Removes an element object and its event handler () -> This element @*/ - that.remove = that.removeElement = function(remove) { - //remove !== false && (len = Ox.len(Ox.UI.elements)); - ///* + that.remove = function(remove) { remove !== false && 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); + element && element.remove(false); }); - //*/ Ox.Focus.remove(that.id); delete self.$eventHandler; delete Ox.UI.elements[that.id]; that.$tooltip && that.$tooltip.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; }; diff --git a/source/Ox.UI/js/Core/Ox.GarbageCollection.js b/source/Ox.UI/js/Core/Ox.GarbageCollection.js new file mode 100644 index 00000000..a17f57a6 --- /dev/null +++ b/source/Ox.UI/js/Core/Ox.GarbageCollection.js @@ -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; + +}()); \ No newline at end of file diff --git a/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js b/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js index 4a5a5306..b313515f 100644 --- a/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js +++ b/source/Ox.UI/js/Core/Ox.SyntaxHighlighter.js @@ -111,7 +111,7 @@ Ox.SyntaxHighlighter = function(options, self) { .html(Ox.repeat(' ', self.options.lineLength)) .appendTo(that); width = $line.width() + 4; // add padding - $line.removeElement(); + $line.remove(); ['moz', 'webkit'].forEach(function(browser) { $source.css({ background: '-' + browser + diff --git a/source/Ox.UI/js/Form/Ox.ArrayInput.js b/source/Ox.UI/js/Form/Ox.ArrayInput.js index dde0203b..90467a02 100644 --- a/source/Ox.UI/js/Form/Ox.ArrayInput.js +++ b/source/Ox.UI/js/Form/Ox.ArrayInput.js @@ -102,7 +102,7 @@ Ox.ArrayInput = function(options, self) { Ox.Log('Form', 'remove', i); ['input', 'removeButton', 'addButton', 'element'].forEach(function(element) { var key = '$' + element; - self[key][i].removeElement(); + self[key][i].remove(); self[key].splice(i, 1); }); self.$input.length == 1 && self.$removeButton[0].options({title: 'close'}); diff --git a/source/Ox.UI/js/Form/Ox.Button.js b/source/Ox.UI/js/Form/Ox.Button.js index db9df19e..88a466c5 100644 --- a/source/Ox.UI/js/Form/Ox.Button.js +++ b/source/Ox.UI/js/Form/Ox.Button.js @@ -72,12 +72,9 @@ Ox.Button = function(options, self) { setTitle(self.titles[self.selectedTitle].title); if (self.options.tooltip) { - self.tooltips = Ox.isArray(self.options.tooltip) ? self.options.tooltip : [self.options.tooltip]; - self.$tooltip = Ox.Tooltip({ - title: self.tooltips[self.selectedTitle] - }); - that.mouseenter(mouseenter) - .mouseleave(mouseleave); + self.tooltips = Ox.isArray(self.options.tooltip) + ? self.options.tooltip : [self.options.tooltip]; + that.options({tooltip: self.tooltips[self.selectedTitle]}); } 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) { self.title = title; if (self.options.type == 'image') { @@ -138,6 +127,8 @@ Ox.Button = function(options, self) { that.toggleClass('OxSelected'); } that.triggerEvent('change'); + } else if (key == 'tooltip') { + } else if (key == 'title') { setTitle(value); } else if (key == 'width') { @@ -145,7 +136,7 @@ Ox.Button = function(options, self) { width: (value - 14) + 'px' }); } - } + }; /*@ toggleDisabled @@ -180,7 +171,7 @@ Ox.Button = function(options, self) { setTitle(self.titles[self.selectedTitle].title); // fixme: if the tooltip is visible // we also need to call show() - self.$tooltip && self.$tooltip.options({ + that.$tooltip && that.$tooltip.options({ title: self.tooltips[self.selectedTitle] }); return that; diff --git a/source/Ox.UI/js/Form/Ox.Form.js b/source/Ox.UI/js/Form/Ox.Form.js index 97d3b019..6c45a855 100644 --- a/source/Ox.UI/js/Form/Ox.Form.js +++ b/source/Ox.UI/js/Form/Ox.Form.js @@ -123,7 +123,7 @@ Ox.Form = function(options, self) { that.removeItem = function(pos) { Ox.Log('Form', 'removeItem', pos); - self.$items[pos].removeElement(); + self.$items[pos].remove(); self.options.items.splice(pos, 1); self.$items.splice(pos, 1); } diff --git a/source/Ox.UI/js/Form/Ox.Select.js b/source/Ox.UI/js/Form/Ox.Select.js index 0af2a769..36ea4de3 100644 --- a/source/Ox.UI/js/Form/Ox.Select.js +++ b/source/Ox.UI/js/Form/Ox.Select.js @@ -195,6 +195,12 @@ Ox.Select = function(options, self) { 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() ??? /*@ diff --git a/source/Ox.UI/js/List/Ox.List.js b/source/Ox.UI/js/List/Ox.List.js index 06b029af..684d5236 100644 --- a/source/Ox.UI/js/List/Ox.List.js +++ b/source/Ox.UI/js/List/Ox.List.js @@ -763,7 +763,7 @@ Ox.List = function(options, self) { page == 0 && fillFirstPage(); // FIXME: why does emptyPage sometimes have no methods? //Ox.Log('List', 'emptyPage', $emptyPage) - $emptyPage && $emptyPage.removeElement && $emptyPage.removeElement(); + $emptyPage && $emptyPage.remove && $emptyPage.remove(); self.$pages[page].appendTo(that.$content); !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', 'page not undefined', !Ox.isUndefined(self.$pages[page])) if (!Ox.isUndefined(self.$pages[page])) { - self.$pages[page].removeElement(); + self.$pages[page].remove(); delete self.$pages[page]; } } @@ -1593,7 +1593,7 @@ Ox.List = function(options, self) { } else { // remove items from pos to pos+length Ox.range(pos, pos + length).forEach(function(i) { self.selected.indexOf(i) > -1 && deselect(i); - self.$items[i].removeElement(); + self.$items[i].remove(); }); self.options.items.splice(pos, length); self.$items.splice(pos, length); diff --git a/source/Ox.UI/js/List/Ox.ListItem.js b/source/Ox.UI/js/List/Ox.ListItem.js index d00d13d8..237c4d96 100644 --- a/source/Ox.UI/js/List/Ox.ListItem.js +++ b/source/Ox.UI/js/List/Ox.ListItem.js @@ -33,16 +33,17 @@ Ox.ListItem = function(options, self) { function constructItem(update) { var $element = self.options.construct(self.options.data) - .addClass('OxItem') + .addClass('OxItem LISTITEM') .data({ id: self.options.data[self.options.unique], position: self.options.position }); if (update) { 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) { diff --git a/source/Ox.UI/js/Map/Ox.Map.js b/source/Ox.UI/js/Map/Ox.Map.js index c3881df3..94f8ab5d 100644 --- a/source/Ox.UI/js/Map/Ox.Map.js +++ b/source/Ox.UI/js/Map/Ox.Map.js @@ -570,7 +570,7 @@ Ox.Map = function(options, self) { function constructZoomInput() { //Ox.Log('Map', 'constructZoomInput', self.minZoom, self.maxZoom) if (self.options.zoombar) { - self.$zoomInput && self.$zoomInput.removeElement(); + self.$zoomInput && self.$zoomInput.remove(); self.$zoomInput = Ox.Range({ arrows: true, max: self.maxZoom, diff --git a/source/Ox.UI/js/Menu/Ox.MainMenu.js b/source/Ox.UI/js/Menu/Ox.MainMenu.js index d4100785..1d1d79b7 100644 --- a/source/Ox.UI/js/Menu/Ox.MainMenu.js +++ b/source/Ox.UI/js/Menu/Ox.MainMenu.js @@ -119,7 +119,7 @@ Ox.MainMenu = function(options, self) { function removeMenu(position) { that.titles[position].remove(); - that.menus[position].removeElement(); + that.menus[position].remove(); } self.setOption = function(key, value) { diff --git a/source/Ox.UI/js/Menu/Ox.Menu.js b/source/Ox.UI/js/Menu/Ox.Menu.js index 84cb4300..4f51772a 100644 --- a/source/Ox.UI/js/Menu/Ox.Menu.js +++ b/source/Ox.UI/js/Menu/Ox.Menu.js @@ -200,11 +200,15 @@ Ox.Menu = function(options, self) { items.forEach(function(item) { var position; if ('id' in item) { - that.items.push(Ox.MenuItem(Ox.extend(item, { - maxWidth: self.options.maxWidth, - menu: that, - position: position = that.items.length - })).data('position', position).appendTo(that.$content)); // fixme: jquery bug when passing {position: position}? does not return the object?; + that.items.push( + Ox.MenuItem(Ox.extend(item, { + maxWidth: self.options.maxWidth, + menu: that, + 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) { that.submenus[item.id] = Ox.Menu({ element: that.items[position], @@ -665,6 +669,14 @@ Ox.Menu = function(options, self) { return that; }; + self.superRemove = that.remove; + that.remove = function() { + Ox.forEach(that.submenus, function(submenu) { + submenu.remove(); + }); + self.superRemove(); + }; + /*@ removeItem removeItem @*/ diff --git a/source/Ox.UI/js/Menu/Ox.MenuItem.js b/source/Ox.UI/js/Menu/Ox.MenuItem.js index ee89b348..08b3e58b 100644 --- a/source/Ox.UI/js/Menu/Ox.MenuItem.js +++ b/source/Ox.UI/js/Menu/Ox.MenuItem.js @@ -41,7 +41,7 @@ Ox.MenuItem = function(options, self) { keyboard: parseKeyboard(options.keyboard || self.defaults.keyboard), title: Ox.toArray(options.title || self.defaults.title) })) - .addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : '')) + .addClass('OxItem' + (self.options.disabled ? ' OxDisabled' : '') + ' MENUITEM') /* .attr({ id: Ox.toCamelCase(self.options.menu.options('id') + '/' + self.options.id) diff --git a/source/Ox.UI/js/Panel/Ox.SplitPanel.js b/source/Ox.UI/js/Panel/Ox.SplitPanel.js index 0909e88d..4a8bb2d7 100644 --- a/source/Ox.UI/js/Panel/Ox.SplitPanel.js +++ b/source/Ox.UI/js/Panel/Ox.SplitPanel.js @@ -195,16 +195,10 @@ Ox.SplitPanel = function(options, self) { 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.Log('Panel', 'REPLACE ELEMENT') self.options.elements[pos].element.replaceWith( self.options.elements[pos].element = element ); - //*/ setSizes(); self.$resizebars.forEach(function($resizebar, i) { $resizebar.options({ diff --git a/source/Ox.UI/js/Video/Ox.BlockTimeline.js b/source/Ox.UI/js/Video/Ox.BlockTimeline.js index 81624450..9f9a0576 100644 --- a/source/Ox.UI/js/Video/Ox.BlockTimeline.js +++ b/source/Ox.UI/js/Video/Ox.BlockTimeline.js @@ -341,7 +341,7 @@ Ox.BlockTimeline = function(options, self) { } }); while (self.$lines.length > self.lines) { - self.$lines[self.$lines.length - 1].removeElement(); + self.$lines[self.$lines.length - 1].remove(); self.$lines.pop(); } setMarker(); diff --git a/source/Ox.UI/js/Window/Ox.Dialog.js b/source/Ox.UI/js/Window/Ox.Dialog.js index 4f9d4233..359766d0 100644 --- a/source/Ox.UI/js/Window/Ox.Dialog.js +++ b/source/Ox.UI/js/Window/Ox.Dialog.js @@ -541,7 +541,7 @@ Ox.Dialog = function(options, self) { self.$content.animate({ opacity: 0 }, 250, function() { - $(this).remove() // fixme: removeElement? + $(this).remove(); }); self.options.content.css({ opacity: 0 @@ -649,7 +649,7 @@ Ox.Dialog = function(options, self) { that.animate({ opacity: 0 }, 250, function() { - // that.removeElement(); + // that.remove(); that.hide(); callback && callback(); }); diff --git a/source/Ox.UI/js/Window/Ox.Layer.js b/source/Ox.UI/js/Window/Ox.Layer.js index fff396d3..66a46b60 100644 --- a/source/Ox.UI/js/Window/Ox.Layer.js +++ b/source/Ox.UI/js/Window/Ox.Layer.js @@ -24,7 +24,7 @@ Ox.Layer = function(options, self) { }); function click() { - that.triggerEvent('click').removeElement(); + that.triggerEvent('click').remove(); } function mousedown() { @@ -39,7 +39,7 @@ Ox.Layer = function(options, self) { if (self.options.type == 'dialog') { Ox.UI.$window.unbind({mouseup: mouseup}); } - that.removeElement(); + that.remove(); }; that.show = function() {