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