Merge pull request #1834 from nasa/summary-widgets-ml

Summary widgets Memory Leak Fix
This commit is contained in:
Deep Tailor 2017-12-07 14:19:16 -08:00 committed by GitHub
commit 284dec4903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 193 additions and 44 deletions

View File

@ -48,6 +48,7 @@ define([
this.unlisteners.forEach(function (unlisten) {
unlisten();
});
this.unlisteners = [];
};
/**

View File

@ -3,6 +3,7 @@ define([
'./input/ObjectSelect',
'./input/KeySelect',
'./input/OperationSelect',
'./eventHelpers',
'EventEmitter',
'zepto'
], function (
@ -10,10 +11,10 @@ define([
ObjectSelect,
KeySelect,
OperationSelect,
eventHelpers,
EventEmitter,
$
) {
/**
* Represents an individual condition for a summary widget rule. Manages the
* associated inputs and view.
@ -25,6 +26,7 @@ define([
* selects with configuration data
*/
function Condition(conditionConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = conditionConfig;
this.index = index;
this.conditionManager = conditionManager;
@ -71,15 +73,17 @@ define([
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber),
inputIndex = self.valueInputs.indexOf(elem);
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', {
value: value,
property: 'values[' + inputIndex + ']',
index: self.index
});
}
}
this.deleteButton.on('click', this.remove);
this.duplicateButton.on('click', this.duplicate);
this.listenTo(this.deleteButton, 'click', this.remove, this);
this.listenTo(this.duplicateButton, 'click', this.duplicate, this);
this.selects.object = new ObjectSelect(this.config, this.conditionManager, [
['any', 'any telemetry'],
@ -105,7 +109,7 @@ define([
$('.t-configuration', self.domElement).append(select.getDOM());
});
$(this.domElement).on('input', 'input', onValueInput);
this.listenTo($(this.domElement), 'input', onValueInput);
}
Condition.prototype.getDOM = function (container) {
@ -139,6 +143,14 @@ define([
*/
Condition.prototype.remove = function () {
this.eventEmitter.emit('remove', this.index);
this.destroy();
};
Condition.prototype.destroy = function () {
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
/**

View File

@ -3,6 +3,7 @@ define([
'./Condition',
'./input/ColorPalette',
'./input/IconPalette',
'./eventHelpers',
'EventEmitter',
'lodash',
'zepto'
@ -11,11 +12,11 @@ define([
Condition,
ColorPalette,
IconPalette,
eventHelpers,
EventEmitter,
_,
$
) {
/**
* An object representing a summary widget rule. Maintains a set of text
* and css properties for output, and a set of conditions for configuring
@ -29,6 +30,7 @@ define([
* @param {element} container The DOM element which cotains this summary widget
*/
function Rule(ruleConfig, domainObject, openmct, conditionManager, widgetDnD, container) {
eventHelpers.extend(this);
var self = this;
this.config = ruleConfig;
@ -196,24 +198,24 @@ define([
Object.keys(this.textInputs).forEach(function (inputKey) {
self.textInputs[inputKey].prop('value', self.config[inputKey] || '');
self.textInputs[inputKey].on('input', function () {
self.listenTo(self.textInputs[inputKey], 'input', function () {
onTextInput(this, inputKey);
});
});
this.deleteButton.on('click', this.remove);
this.duplicateButton.on('click', this.duplicate);
this.addConditionButton.on('click', function () {
this.listenTo(this.deleteButton, 'click', this.remove);
this.listenTo(this.duplicateButton, 'click', this.duplicate);
this.listenTo(this.addConditionButton, 'click', function () {
self.initCondition();
});
this.toggleConfigButton.on('click', toggleConfig);
this.trigger.on('change', onTriggerInput);
this.listenTo(this.toggleConfigButton, 'click', toggleConfig);
this.listenTo(this.trigger, 'change', onTriggerInput);
this.title.html(self.config.name);
this.description.html(self.config.description);
this.trigger.prop('value', self.config.trigger);
this.grippy.on('mousedown', onDragStart);
this.listenTo(this.grippy, 'mousedown', onDragStart);
this.widgetDnD.on('drop', function () {
this.domElement.show();
$('.t-drag-indicator').hide();
@ -258,6 +260,10 @@ define([
palette.destroy();
});
this.iconInput.destroy();
this.stopListening();
this.conditions.forEach(function (condition) {
condition.destroy();
});
};
/**

View File

@ -4,6 +4,7 @@ define([
'./ConditionManager',
'./TestDataManager',
'./WidgetDnD',
'./eventHelpers',
'lodash',
'zepto'
], function (
@ -12,6 +13,7 @@ define([
ConditionManager,
TestDataManager,
WidgetDnD,
eventHelpers,
_,
$
) {
@ -32,6 +34,8 @@ define([
* @param {MCT} openmct An MCT instance
*/
function SummaryWidget(domainObject, openmct) {
eventHelpers.extend(this);
this.domainObject = domainObject;
this.openmct = openmct;
@ -86,7 +90,7 @@ define([
self.outerWrapper.toggleClass('expanded-widget-test-data');
self.toggleTestDataControl.toggleClass('expanded');
}
this.toggleTestDataControl.on('click', toggleTestData);
this.listenTo(this.toggleTestDataControl, 'click', toggleTestData);
/**
* Toggles the configuration area for rules in the view
@ -96,7 +100,7 @@ define([
self.outerWrapper.toggleClass('expanded-widget-rules');
self.toggleRulesControl.toggleClass('expanded');
}
this.toggleRulesControl.on('click', toggleRules);
this.listenTo(this.toggleRulesControl, 'click', toggleRules);
openmct.$injector.get('objectService')
.getObjects([id])
@ -160,13 +164,15 @@ define([
this.widgetDnD = new WidgetDnD(this.domElement, this.domainObject.configuration.ruleOrder, this.rulesById);
this.initRule('default', 'Default');
this.domainObject.configuration.ruleOrder.forEach(function (ruleId) {
self.initRule(ruleId);
if (ruleId !== 'default') {
self.initRule(ruleId);
}
});
this.refreshRules();
this.updateWidget();
this.updateView();
this.addRuleButton.on('click', this.addRule);
this.listenTo(this.addRuleButton, 'click', this.addRule);
this.conditionManager.on('receiveTelemetry', this.executeRules, this);
this.widgetDnD.on('drop', this.reorder, this);
};
@ -178,11 +184,14 @@ define([
SummaryWidget.prototype.destroy = function (container) {
this.editListenerUnsubscribe();
this.conditionManager.destroy();
this.testDataManager.destroy();
this.widgetDnD.destroy();
this.watchForChangesUnsubscribe();
Object.values(this.rulesById).forEach(function (rule) {
rule.destroy();
});
this.stopListening();
};
/**

View File

@ -2,12 +2,14 @@ define([
'text!../res/testDataItemTemplate.html',
'./input/ObjectSelect',
'./input/KeySelect',
'./eventHelpers',
'EventEmitter',
'zepto'
], function (
itemTemplate,
ObjectSelect,
KeySelect,
eventHelpers,
EventEmitter,
$
) {
@ -24,6 +26,7 @@ define([
* @constructor
*/
function TestDataItem(itemConfig, index, conditionManager) {
eventHelpers.extend(this);
this.config = itemConfig;
this.index = index;
this.conditionManager = conditionManager;
@ -70,16 +73,17 @@ define([
function onValueInput(event) {
var elem = event.target,
value = (isNaN(elem.valueAsNumber) ? elem.value : elem.valueAsNumber);
self.eventEmitter.emit('change', {
value: value,
property: 'value',
index: self.index
});
if (elem.tagName.toUpperCase() === 'INPUT') {
self.eventEmitter.emit('change', {
value: value,
property: 'value',
index: self.index
});
}
}
this.deleteButton.on('click', this.remove);
this.duplicateButton.on('click', this.duplicate);
this.listenTo(this.deleteButton, 'click', this.remove);
this.listenTo(this.duplicateButton, 'click', this.duplicate);
this.selects.object = new ObjectSelect(this.config, this.conditionManager);
this.selects.key = new KeySelect(
@ -97,8 +101,7 @@ define([
Object.values(this.selects).forEach(function (select) {
$('.t-configuration', self.domElement).append(select.getDOM());
});
$(this.domElement).on('input', 'input', onValueInput);
this.listenTo(this.domElement, 'input', onValueInput);
}
/**
@ -137,6 +140,11 @@ define([
TestDataItem.prototype.remove = function () {
var self = this;
this.eventEmitter.emit('remove', self.index);
this.stopListening();
Object.values(this.selects).forEach(function (select) {
select.destroy();
});
};
/**

View File

@ -1,9 +1,11 @@
define([
'./eventHelpers',
'text!../res/testDataTemplate.html',
'./TestDataItem',
'zepto',
'lodash'
], function (
eventHelpers,
testDataTemplate,
TestDataItem,
$,
@ -18,6 +20,7 @@ define([
* @param {MCT} openmct and MCT instance
*/
function TestDataManager(domainObject, conditionManager, openmct) {
eventHelpers.extend(this);
var self = this;
this.domainObject = domainObject;
@ -45,10 +48,10 @@ define([
self.updateTestCache();
}
this.addItemButton.on('click', function () {
this.listenTo(this.addItemButton, 'click', function () {
self.initItem();
});
this.testDataInput.on('change', toggleTestData);
this.listenTo(this.testDataInput, 'change', toggleTestData);
this.evaluator.setTestDataCache(this.testCache);
this.evaluator.useTestData(false);
@ -186,5 +189,12 @@ define([
this.openmct.objects.mutate(this.domainObject, 'configuration.testDataConfig', this.config);
};
TestDataManager.prototype.destroy = function () {
this.items.forEach(function (item) {
item.remove();
});
this.stopListening();
};
return TestDataManager;
});

View File

@ -0,0 +1,74 @@
var listenersCount = 0;
/*global define*/
// jscs:disable disallowDanglingUnderscores
define([], function () {
var helperFunctions = {
listenTo: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
var listener = {
object: object,
event: event,
callback: callback,
context: context,
_cb: !!context ? callback.bind(context) : callback
};
if (object.$watch && event.indexOf('change:') === 0) {
var scopePath = event.replace('change:', '');
listener.unlisten = object.$watch(scopePath, listener._cb, true);
} else if (object.$on) {
listener.unlisten = object.$on(event, listener._cb);
} else if (object.addEventListener) {
object.addEventListener(event, listener._cb);
} else {
object.on(event, listener._cb);
}
this._listeningTo.push(listener);
listenersCount++;
},
stopListening: function (object, event, callback, context) {
if (!this._listeningTo) {
this._listeningTo = [];
}
this._listeningTo.filter(function (listener) {
if (object && object !== listener.object) {
return false;
}
if (event && event !== listener.event) {
return false;
}
if (callback && callback !== listener.callback) {
return false;
}
if (context && context !== listener.context) {
return false;
}
return true;
})
.map(function (listener) {
if (listener.unlisten) {
listener.unlisten();
} else if (listener.object.removeEventListener) {
listener.object.removeEventListener(listener.event, listener._cb);
} else {
listener.object.off(listener.event, listener._cb);
}
listenersCount--;
return listener;
})
.forEach(function (listener) {
this._listeningTo.splice(this._listeningTo.indexOf(listener), 1);
}, this);
},
extend: function (object) {
object.listenTo = helperFunctions.listenTo;
object.stopListening = helperFunctions.stopListening;
}
};
return helperFunctions;
});

View File

@ -1,4 +1,8 @@
define(['./Select'], function (Select) {
define([
'./Select'
], function (
Select
) {
/**
* Create a {Select} element whose composition is dynamically updated with
@ -62,7 +66,7 @@ define(['./Select'], function (Select) {
onMetadataLoad();
}
this.objectSelect.on('change', onObjectChange);
this.objectSelect.on('change', onObjectChange, this);
this.manager.on('metadata', onMetadataLoad);
return this.select;
@ -85,6 +89,10 @@ define(['./Select'], function (Select) {
}
};
KeySelect.prototype.destroy = function () {
this.objectSelect.destroy();
};
return KeySelect;
});

View File

@ -1,4 +1,10 @@
define(['./Select'], function (Select) {
define([
'./Select',
'../eventHelpers'
], function (
Select,
eventHelpers
) {
/**
* Create a {Select} element whose composition is dynamically updated with
@ -17,6 +23,7 @@ define(['./Select'], function (Select) {
var NULLVALUE = '- Select Comparison -';
function OperationSelect(config, keySelect, manager, changeCallback) {
eventHelpers.extend(this);
var self = this;
this.config = config;
@ -31,7 +38,7 @@ define(['./Select'], function (Select) {
this.select.hide();
this.select.addOption('', NULLVALUE);
if (changeCallback) {
this.select.on('change', changeCallback);
this.listenTo(this.select, 'change', changeCallback);
}
/**
@ -63,7 +70,6 @@ define(['./Select'], function (Select) {
}
self.select.setSelected(self.config.operation);
}
this.keySelect.on('change', onKeyChange);
this.manager.on('metadata', onMetadataLoad);
@ -109,6 +115,10 @@ define(['./Select'], function (Select) {
});
};
OperationSelect.prototype.destroy = function () {
this.stopListening();
};
return OperationSelect;
});

View File

@ -1,13 +1,14 @@
define([
'../eventHelpers',
'text!../../res/input/paletteTemplate.html',
'EventEmitter',
'zepto'
], function (
eventHelpers,
paletteTemplate,
EventEmitter,
$
) {
/**
* Instantiates a new Open MCT Color Palette input
* @constructor
@ -19,6 +20,8 @@ define([
* up to the descendent class
*/
function Palette(cssClass, container, items) {
eventHelpers.extend(this);
var self = this;
this.cssClass = cssClass;
@ -49,8 +52,8 @@ define([
$('.menu', self.domElement).hide();
$(document).on('click', this.hideMenu);
$('.l-click-area', self.domElement).on('click', function (event) {
this.listenTo($(document), 'click', this.hideMenu);
this.listenTo($('.l-click-area', self.domElement), 'click', function (event) {
event.stopPropagation();
$('.menu', self.container).hide();
$('.menu', self.domElement).show();
@ -69,7 +72,7 @@ define([
$('.menu', self.domElement).hide();
}
$('.s-palette-item', self.domElement).on('click', handleItemClick);
this.listenTo($('.s-palette-item', self.domElement), 'click', handleItemClick);
}
/**
@ -83,7 +86,7 @@ define([
* Clean up any event listeners registered to DOM elements external to the widget
*/
Palette.prototype.destroy = function () {
$(document).off('click', this.hideMenu);
this.stopListening();
};
Palette.prototype.hideMenu = function () {

View File

@ -1,8 +1,10 @@
define([
'../eventHelpers',
'text!../../res/input/selectTemplate.html',
'EventEmitter',
'zepto'
], function (
eventHelpers,
selectTemplate,
EventEmitter,
$
@ -14,6 +16,8 @@ define([
* @constructor
*/
function Select() {
eventHelpers.extend(this);
var self = this;
this.domElement = $(selectTemplate);
@ -36,7 +40,7 @@ define([
self.eventEmitter.emit('change', value[0]);
}
$('select', this.domElement).on('change', onChange);
this.listenTo($('select', this.domElement), 'change', onChange, this);
}
/**
@ -140,5 +144,9 @@ define([
$('.equal-to').removeClass('hidden');
};
Select.prototype.destroy = function () {
this.stopListening();
};
return Select;
});