diff --git a/platform/commonUI/browse/src/creation/CreateWizard.js b/platform/commonUI/browse/src/creation/CreateWizard.js
index 7f60bdd883..29fe953e18 100644
--- a/platform/commonUI/browse/src/creation/CreateWizard.js
+++ b/platform/commonUI/browse/src/creation/CreateWizard.js
@@ -45,8 +45,9 @@ define(
properties = type.getProperties();
function validateLocation(locatingObject) {
- var locatingType = locatingObject.getCapability('type');
- return policyService.allow(
+ var locatingType = locatingObject &&
+ locatingObject.getCapability('type');
+ return locatingType && policyService.allow(
"composition",
locatingType,
type
diff --git a/platform/containment/bundle.json b/platform/containment/bundle.json
index e61085f66f..e6e24f0f79 100644
--- a/platform/containment/bundle.json
+++ b/platform/containment/bundle.json
@@ -12,6 +12,11 @@
"implementation": "CompositionMutabilityPolicy.js",
"message": "Objects of this type cannot be modified."
},
+ {
+ "category": "composition",
+ "implementation": "CompositionModelPolicy.js",
+ "message": "Objects of this type cannot contain other objects."
+ },
{
"category": "action",
"implementation": "ComposeActionPolicy.js",
diff --git a/platform/containment/src/CompositionModelPolicy.js b/platform/containment/src/CompositionModelPolicy.js
new file mode 100644
index 0000000000..74f1200530
--- /dev/null
+++ b/platform/containment/src/CompositionModelPolicy.js
@@ -0,0 +1,28 @@
+/*global define*/
+
+define(
+ [],
+ function () {
+ "use strict";
+
+ /**
+ * Policy allowing composition only for domain object types which
+ * have a composition property.
+ */
+ function CompositionModelPolicy() {
+ return {
+ /**
+ * Is the type identified by the candidate allowed to
+ * contain the type described by the context?
+ */
+ allow: function (candidate, context) {
+ return Array.isArray(
+ (candidate.getInitialModel() || {}).composition
+ );
+ }
+ };
+ }
+
+ return CompositionModelPolicy;
+ }
+);
\ No newline at end of file
diff --git a/platform/containment/test/CompositionModelPolicySpec.js b/platform/containment/test/CompositionModelPolicySpec.js
new file mode 100644
index 0000000000..bace49246d
--- /dev/null
+++ b/platform/containment/test/CompositionModelPolicySpec.js
@@ -0,0 +1,26 @@
+/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
+
+define(
+ ["../src/CompositionModelPolicy"],
+ function (CompositionModelPolicy) {
+ "use strict";
+
+ describe("The composition model policy", function () {
+ var mockType,
+ policy;
+
+ beforeEach(function () {
+ mockType = jasmine.createSpyObj('type', ['getInitialModel']);
+ policy = new CompositionModelPolicy();
+ });
+
+ it("only allows composition for types which will have a composition property", function () {
+ mockType.getInitialModel.andReturn({});
+ expect(policy.allow(mockType)).toBeFalsy();
+ mockType.getInitialModel.andReturn({ composition: [] });
+ expect(policy.allow(mockType)).toBeTruthy();
+ });
+ });
+
+ }
+);
diff --git a/platform/containment/test/CompositionMutabilityPolicySpec.js b/platform/containment/test/CompositionMutabilityPolicySpec.js
index c537a5000f..1f49883939 100644
--- a/platform/containment/test/CompositionMutabilityPolicySpec.js
+++ b/platform/containment/test/CompositionMutabilityPolicySpec.js
@@ -35,7 +35,7 @@ define(
policy = new CompositionMutabilityPolicy();
});
- it("only allows composition for types which will have a composition capability", function () {
+ it("only allows composition for types which can be created/modified", function () {
expect(policy.allow(mockType)).toBeFalsy();
mockType.hasFeature.andReturn(true);
expect(policy.allow(mockType)).toBeTruthy();
diff --git a/platform/containment/test/suite.json b/platform/containment/test/suite.json
index 987ef9a86c..81218a969e 100644
--- a/platform/containment/test/suite.json
+++ b/platform/containment/test/suite.json
@@ -1,6 +1,7 @@
[
"CapabilityTable",
"ComposeActionPolicy",
+ "CompositionModelPolicy",
"CompositionMutabilityPolicy",
"CompositionPolicy",
"ContainmentTable"
diff --git a/platform/core/src/capabilities/MutationCapability.js b/platform/core/src/capabilities/MutationCapability.js
index 431c8d5d19..c20c90deab 100644
--- a/platform/core/src/capabilities/MutationCapability.js
+++ b/platform/core/src/capabilities/MutationCapability.js
@@ -77,7 +77,8 @@ define(
// Get the object's model and clone it, so the
// mutator function has a temporary copy to work with.
var model = domainObject.getModel(),
- clone = JSON.parse(JSON.stringify(model));
+ clone = JSON.parse(JSON.stringify(model)),
+ useTimestamp = arguments.length > 1;
// Function to handle copying values to the actual
function handleMutation(mutationResult) {
@@ -94,8 +95,7 @@ define(
if (model !== result) {
copyValues(model, result);
}
- model.modified = (typeof timestamp === 'number') ?
- timestamp : now();
+ model.modified = useTimestamp ? timestamp : now();
}
// Report the result of the mutation
diff --git a/platform/forms/res/templates/controls/datetime.html b/platform/forms/res/templates/controls/datetime.html
index e2accf7c8f..6dae89eb8a 100644
--- a/platform/forms/res/templates/controls/datetime.html
+++ b/platform/forms/res/templates/controls/datetime.html
@@ -38,7 +38,7 @@
placeholder="YYYY-DDD"
ng-pattern="/\d\d\d\d-\d\d\d/"
ng-model='datetime.date'
- ng-required='true'/>
+ ng-required='ngRequired || partiallyComplete'/>
+ ng-required='ngRequired || partiallyComplete'/>
+ ng-required='ngRequired || partiallyComplete'/>
+ ng-required='ngRequired || partiallyComplete'/>
UTC
diff --git a/platform/forms/src/controllers/DateTimeController.js b/platform/forms/src/controllers/DateTimeController.js
index bf122c3f9c..c026e98935 100644
--- a/platform/forms/src/controllers/DateTimeController.js
+++ b/platform/forms/src/controllers/DateTimeController.js
@@ -52,15 +52,51 @@ define(
if (fullDateTime.isValid()) {
$scope.ngModel[$scope.field] = fullDateTime.valueOf();
}
+
+ // If anything is complete, say so in scope; there are
+ // ng-required usages that will update off of this (to
+ // allow datetime to be optional while still permitting
+ // incomplete input)
+ $scope.partiallyComplete =
+ Object.keys($scope.datetime).some(function (key) {
+ return $scope.datetime[key];
+ });
+
+ // Treat empty input as an undefined value
+ if (!$scope.partiallyComplete) {
+ $scope.ngModel[$scope.field] = undefined;
+ }
}
+ function updateDateTime(value) {
+ var m;
+ if (value !== undefined) {
+ m = moment.utc(value);
+ $scope.datetime = {
+ date: m.format(DATE_FORMAT),
+ hour: m.format("H"),
+ min: m.format("m"),
+ sec: m.format("s")
+ };
+ } else {
+ $scope.datetime = {};
+ }
+ }
+
+ // ...and update form values when actual field in model changes
+ $scope.$watch("ngModel[field]", updateDateTime);
+
// Update value whenever any field changes.
$scope.$watch("datetime.date", update);
$scope.$watch("datetime.hour", update);
$scope.$watch("datetime.min", update);
$scope.$watch("datetime.sec", update);
- $scope.datetime = {};
+ // Initialize forms values
+ updateDateTime(
+ ($scope.ngModel && $scope.field) ?
+ $scope.ngModel[$scope.field] : undefined
+ );
}
return DateTimeController;
diff --git a/platform/forms/test/controllers/DateTimeControllerSpec.js b/platform/forms/test/controllers/DateTimeControllerSpec.js
index e3e5c7b693..11c97ada07 100644
--- a/platform/forms/test/controllers/DateTimeControllerSpec.js
+++ b/platform/forms/test/controllers/DateTimeControllerSpec.js
@@ -57,6 +57,33 @@ define(
expect(mockScope.ngModel.test).toEqual(1417215313000);
});
+ it("reports when form input is partially complete", function () {
+ // This is needed to flag the control's state as invalid
+ // when it is partially complete without having it treated
+ // as required.
+ mockScope.ngModel = {};
+ mockScope.field = "test";
+ mockScope.datetime.date = "2014-332";
+ mockScope.datetime.hour = 22;
+ mockScope.datetime.min = 55;
+ // mockScope.datetime.sec = 13;
+
+ mockScope.$watch.mostRecentCall.args[1]();
+
+ expect(mockScope.partiallyComplete).toBeTruthy();
+ });
+
+ it("reports 'undefined' for empty input", function () {
+ mockScope.ngModel = { test: 12345 };
+ mockScope.field = "test";
+ mockScope.$watch.mostRecentCall.args[1]();
+ // Clear all inputs
+ mockScope.datetime = {};
+ mockScope.$watch.mostRecentCall.args[1]();
+
+ // Should have cleared out the time stamp
+ expect(mockScope.ngModel.test).toBeUndefined();
+ });
});
}
);
\ No newline at end of file