diff --git a/platform/features/layout/res/templates/frame.html b/platform/features/layout/res/templates/frame.html
index 808603a84a..df4e3390ad 100644
--- a/platform/features/layout/res/templates/frame.html
+++ b/platform/features/layout/res/templates/frame.html
@@ -35,7 +35,7 @@
+ mct-object="representation.selected.key && domainObject">
\ No newline at end of file
diff --git a/platform/features/layout/src/LayoutController.js b/platform/features/layout/src/LayoutController.js
index 4e9de17618..fc6c8aafba 100644
--- a/platform/features/layout/src/LayoutController.js
+++ b/platform/features/layout/src/LayoutController.js
@@ -90,6 +90,10 @@ define(
function lookupPanels(ids) {
var configuration = $scope.configuration || {};
+ // ids is read from model.composition and may be undefined;
+ // fall back to an array if that occurs
+ ids = ids || [];
+
// Pull panel positions from configuration
rawPositions = shallowCopy(configuration.panels || {}, ids);
diff --git a/platform/features/plot/src/MCTChart.js b/platform/features/plot/src/MCTChart.js
index b8350e9171..2ca51b2309 100644
--- a/platform/features/plot/src/MCTChart.js
+++ b/platform/features/plot/src/MCTChart.js
@@ -155,6 +155,16 @@ define(
}
}
+ // Switch from WebGL to plain 2D if context is lost
+ function fallbackFromWebGL() {
+ element.html(TEMPLATE);
+ canvas = element.find("canvas")[0];
+ chart = getChart([Canvas2DChart], canvas);
+ if (chart) {
+ doDraw(scope.draw);
+ }
+ }
+
// Try to initialize a chart.
chart = getChart([GLChart, Canvas2DChart], canvas);
@@ -164,6 +174,11 @@ define(
return;
}
+ // WebGL is a bit of a special case; it may work, then fail
+ // later for various reasons, so we need to listen for this
+ // and fall back to plain canvas drawing when it occurs.
+ canvas.addEventListener("webglcontextlost", fallbackFromWebGL);
+
// Check for resize, on a timer
activeInterval = $interval(drawIfResized, 1000);
@@ -192,4 +207,4 @@ define(
return MCTChart;
}
-);
\ No newline at end of file
+);
diff --git a/platform/features/plot/test/MCTChartSpec.js b/platform/features/plot/test/MCTChartSpec.js
index 93be797114..9c60b034a6 100644
--- a/platform/features/plot/test/MCTChartSpec.js
+++ b/platform/features/plot/test/MCTChartSpec.js
@@ -36,6 +36,7 @@ define(
mockElement,
mockCanvas,
mockGL,
+ mockC2d,
mockPromise,
mctChart;
@@ -47,13 +48,14 @@ define(
mockScope =
jasmine.createSpyObj("$scope", ["$watchCollection", "$on"]);
mockElement =
- jasmine.createSpyObj("element", ["find"]);
+ jasmine.createSpyObj("element", ["find", "html"]);
mockInterval.cancel = jasmine.createSpy("cancelInterval");
mockPromise = jasmine.createSpyObj("promise", ["then"]);
// mct-chart uses GLChart, so it needs WebGL API
- mockCanvas = jasmine.createSpyObj("canvas", [ "getContext" ]);
+ mockCanvas =
+ jasmine.createSpyObj("canvas", [ "getContext", "addEventListener" ]);
mockGL = jasmine.createSpyObj(
"gl",
[
@@ -81,6 +83,7 @@ define(
"drawArrays"
]
);
+ mockC2d = jasmine.createSpyObj('c2d', ['clearRect']);
mockGL.ARRAY_BUFFER = "ARRAY_BUFFER";
mockGL.DYNAMIC_DRAW = "DYNAMIC_DRAW";
mockGL.TRIANGLE_FAN = "TRIANGLE_FAN";
@@ -93,7 +96,9 @@ define(
});
mockElement.find.andReturn([mockCanvas]);
- mockCanvas.getContext.andReturn(mockGL);
+ mockCanvas.getContext.andCallFake(function (type) {
+ return { webgl: mockGL, '2d': mockC2d }[type];
+ });
mockInterval.andReturn(mockPromise);
mctChart = new MCTChart(mockInterval, mockLog);
@@ -169,6 +174,15 @@ define(
expect(mockLog.warn).toHaveBeenCalled();
});
+ it("falls back to Canvas 2d API if WebGL context is lost", function () {
+ mctChart.link(mockScope, mockElement);
+ expect(mockCanvas.addEventListener)
+ .toHaveBeenCalledWith("webglcontextlost", jasmine.any(Function));
+ expect(mockCanvas.getContext).not.toHaveBeenCalledWith('2d');
+ mockCanvas.addEventListener.mostRecentCall.args[1]();
+ expect(mockCanvas.getContext).toHaveBeenCalledWith('2d');
+ });
+
it("logs nothing in nominal situations (WebGL available)", function () {
// Complement the previous test
mctChart.link(mockScope, mockElement);
@@ -197,4 +211,4 @@ define(
});
}
-);
\ No newline at end of file
+);
diff --git a/platform/representation/src/MCTRepresentation.js b/platform/representation/src/MCTRepresentation.js
index 6008e515e3..d93e42b957 100644
--- a/platform/representation/src/MCTRepresentation.js
+++ b/platform/representation/src/MCTRepresentation.js
@@ -93,15 +93,18 @@ define(
function link($scope, element, attrs) {
var activeRepresenters = representers.map(function (Representer) {
- return new Representer($scope, element, attrs);
- });
+ return new Representer($scope, element, attrs);
+ }),
+ toClear = [], // Properties to clear out of scope on change
+ counter = 0;
// Populate scope with any capabilities indicated by the
// representation's extension definition
function refreshCapabilities() {
var domainObject = $scope.domainObject,
representation = lookup($scope.key, domainObject),
- uses = ((representation || {}).uses || []);
+ uses = ((representation || {}).uses || []),
+ myCounter = counter;
if (domainObject) {
// Update model
@@ -115,10 +118,16 @@ define(
" for representation ",
$scope.key
].join(""));
+
$q.when(
domainObject.useCapability(used)
).then(function (c) {
- $scope[used] = c;
+ // Avoid clobbering capabilities from
+ // subsequent representations;
+ // Angular reuses scopes.
+ if (counter === myCounter) {
+ $scope[used] = c;
+ }
});
});
}
@@ -130,8 +139,7 @@ define(
function refresh() {
var domainObject = $scope.domainObject,
representation = lookup($scope.key, domainObject),
- uses = ((representation || {}).uses || []),
- gestureKeys = ((representation || {}).gestures || []);
+ uses = ((representation || {}).uses || []);
// Create an empty object named "representation", for this
// representation to store local variables into.
@@ -152,9 +160,19 @@ define(
$log.warn("No representation found for " + $scope.key);
}
+ // Clear out the scope from the last representation
+ toClear.forEach(function (property) {
+ delete $scope[property];
+ });
+
// Populate scope with fields associated with the current
// domain object (if one has been passed in)
if (domainObject) {
+ // Track how many representations we've made in this scope,
+ // to ensure that the correct representations are matched to
+ // the correct object/key pairs.
+ counter += 1;
+
// Initialize any capabilities
refreshCapabilities();
@@ -168,6 +186,10 @@ define(
activeRepresenters.forEach(function (representer) {
representer.represent(representation, domainObject);
});
+
+ // Track which properties we want to clear from scope
+ // next change object/key pair changes
+ toClear = uses.concat(['model']);
}
}
diff --git a/platform/representation/test/MCTRepresentationSpec.js b/platform/representation/test/MCTRepresentationSpec.js
index cb5820a065..43c9104a94 100644
--- a/platform/representation/test/MCTRepresentationSpec.js
+++ b/platform/representation/test/MCTRepresentationSpec.js
@@ -181,6 +181,25 @@ define(
// Should have gotten a warning - that's an unknown key
expect(mockLog.warn).toHaveBeenCalled();
});
+
+ it("clears out obsolete peroperties from scope", function () {
+ mctRepresentation.link(mockScope, mockElement);
+
+ mockScope.key = "def";
+ mockScope.domainObject = mockDomainObject;
+ mockDomainObject.useCapability.andReturn("some value");
+
+ // Trigger the watch
+ mockScope.$watch.calls[0].args[1]();
+ expect(mockScope.testCapability).toBeDefined();
+
+ // Change the view
+ mockScope.key = "xyz";
+
+ // Trigger the watch again; should clear capability from scope
+ mockScope.$watch.calls[0].args[1]();
+ expect(mockScope.testCapability).toBeUndefined();
+ });
});
}
);
\ No newline at end of file