Compare commits

...

8 Commits

7 changed files with 422 additions and 16 deletions

View File

@ -33,6 +33,7 @@ define([
"./src/actions/SaveAndStopEditingAction",
"./src/actions/SaveAsAction",
"./src/actions/CancelAction",
"./src/actions/CreateNewFolderAction",
"./src/policies/EditActionPolicy",
"./src/policies/EditPersistableObjectsPolicy",
"./src/policies/EditableLinkPolicy",
@ -71,6 +72,7 @@ define([
SaveAndStopEditingAction,
SaveAsAction,
CancelAction,
CreateNewFolderAction,
EditActionPolicy,
EditPersistableObjectsPolicy,
EditableLinkPolicy,
@ -145,7 +147,10 @@ define([
"depends": [
"$scope",
"$timeout",
"objectService"
"objectService",
"typeService",
"policyService",
"instantiate"
]
}
],
@ -242,6 +247,14 @@ define([
"cssClass": "icon-x no-label",
"description": "Discard changes made to these objects.",
"depends": []
},
{
"key": "create-new-folder",
"implementation": CreateNewFolderAction,
"description": "Creates a new folder.",
"depends": [
"typeService"
]
}
],
"policies": [

View File

@ -0,0 +1,33 @@
<!--span
Open MCT, Copyright (c) 2014-2018, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
Open MCT is licensed under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
Open MCT includes source code licensed under additional open source
licenses. See the Open Source Licenses file (LICENSES.md) included with
this source code distribution or the Licensing information page available
at runtime from the About dialog for additional information.
-->
<div name="newFolder" ng-controller="LocatorController">
<div ng-if="!createNewFolder">
<a class="s-button icon-folder-new" ng-click="createNewFolderClickHandler()" >
<span class="title-label">New Folder</span>
</a>
</div>
<div ng-if="createNewFolder">
<span><input type="text" ng-model="newFolderName" name="newFolderName"></span>
<a class="s-button" ng-click="createClickHandler()">Create</a>
<a class="s-button icon-x" ng-click="cancelClickHandler()"></a>
</div>
</div>

View File

@ -1,5 +1,5 @@
<!--
Open MCT, Copyright (c) 2014-2018, United States Government
Open MCT, Copyright (c) 2014-2017, United States Government
as represented by the Administrator of the National Aeronautics and Space
Administration. All rights reserved.
@ -26,4 +26,22 @@
ng-model="treeModel">
</mct-representation>
</div>
<!-- Create New Folder Action -->
<div class="newFolderCreation" style="margin-top:10px; position:absolute; left:0px;">
<!-- New folder button, triggers create new folder action. -->
<div ng-show="!newFolderCreationTriggered">
<a class="s-button icon-folder-new" ng-class="{disabled: !validParent()}" ng-click="newFolderButtonClickHandler()">
<span class="text-label">New Folder</span>
</a>
</div>
<!-- Get folder name -->
<div ng-show="newFolderCreationTriggered">
<input type="text" ng-model="newFolderNameInput">
<a class="s-button" ng-class="{ disabled: !validParent() || !validFolderName() }" ng-click="newFolderCreateButtonClickHandler()">Create</a>
<a class="s-button icon-x" ng-click="newFolderCancelButtonClickHandler()"></a>
</div>
</div>
</div>

View File

@ -0,0 +1,89 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define([
],
function (
) {
/**
* The CreateNewFolderAction; action is triggered by the new folder button in the locator.
*
* @constructor
* @implements {Action}
* @memberof platform/commonUI/edit
*/
function CreateNewFolderAction(
typeService,
context
) {
this.parent = (context || {}).domainObject;
this.typeService = typeService;
this.type = typeService.getType('folder');
}
CreateNewFolderAction.prototype.perform = function (folderName) {
var parent = this.parent,
typeService = this.typeService,
newModel = this.type.getInitialModel(),
folderType = typeService.getType('folder');
newModel.type = folderType.getKey();
newModel.name = folderName;
function instantiateObject() {
var newObject = parent.useCapability('instantiation', newModel);
newObject.useCapability('mutation', function () {
newModel.location = parent.getId();
});
return addToParentAndReturn(newObject);
}
function addToParentAndReturn(newObject) {
return parent.getCapability('composition').add(newObject)
.then(function () {
return newObject;
});
}
return instantiateObject(newModel, parent);
};
/**
* Check if this action is applicable in a given context.
* This will ensure that a domain object is present in the context,
* and that this domain object is in Edit mode.
* @returns true if applicable
*/
CreateNewFolderAction.appliesTo = function (context) {
var parent = (context || {}).domainObject;
return parent && parent.hasCapability('editor');
};
return CreateNewFolderAction;
}
);

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2017, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -31,7 +31,7 @@ define(
* @memberof platform/commonUI/browse
* @constructor
*/
function LocatorController($scope, $timeout, objectService) {
function LocatorController($scope, $timeout, objectService, typeService, policyService, instantiate) {
// Populate values needed by the locator control. These are:
// * rootObject: The top-level object, since we want to show
// the full tree
@ -66,8 +66,8 @@ define(
// Restrict which locations can be selected
if (domainObject &&
$scope.structure &&
$scope.structure.validate) {
$scope.structure &&
$scope.structure.validate) {
if (!$scope.structure.validate(domainObject)) {
setLocatingObject(priorObject, undefined);
return;
@ -81,11 +81,64 @@ define(
!!$scope.treeModel.selectedObject
);
}
// Check if create new folder is a valid action for selected object
$scope.validParent = function () {
if ($scope.treeModel.selectedObject) {
return policyService.allow(
"composition",
$scope.treeModel.selectedObject,
instantiate(typeService.getType('folder').getInitialModel())
);
} else {
return false;
}
};
}
$scope.newFolderButtonClickHandler = function () {
$scope.newFolderCreationTriggered = true;
};
$scope.newFolderCancelButtonClickHandler = function () {
$scope.newFolderCreationTriggered = false;
resetNewFolderNameInput();
};
// Get expected input pattern for folder name
var folderNamePattern = new RegExp(
typeService.getType('folder').getProperties()[0].propertyDefinition.pattern
);
// Validate folder name externally to avoid affecting overall form validation
$scope.validFolderName = function () {
return $scope.newFolderNameInput && folderNamePattern.test($scope.newFolderNameInput);
};
function selectAndScrollToNewFolder(newFolder) {
$scope.treeModel.selectedObject = newFolder;
}
function resetNewFolderNameInput() {
$scope.newFolderNameInput = "Unnamed Folder";
$scope.newFolderCreationTriggered = false;
}
// Create new folder, update selection to new folder and reset new folder button
$scope.newFolderCreateButtonClickHandler = function () {
var createNewFolderAction = $scope.treeModel.selectedObject.getCapability('action').getActions('create-new-folder')[0];
createNewFolderAction.perform($scope.newFolderNameInput)
.then(selectAndScrollToNewFolder)
.then(resetNewFolderNameInput);
};
// Initial state for the tree's model
$scope.treeModel =
{ selectedObject: $scope.ngModel[$scope.field] };
$scope.treeModel = { selectedObject: $scope.ngModel[$scope.field] };
//Initial values for new folder action
$scope.newFolderNameInput = "Unnamed Folder";
$scope.newFolderCreationTriggered = false;
// Watch for changes from the tree
$scope.$watch("treeModel.selectedObject", setLocatingObject);

View File

@ -0,0 +1,117 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* Open MCT is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* Open MCT includes source code licensed under additional open source
* licenses. See the Open Source Licenses file (LICENSES.md) included with
* this source code distribution or the Licensing information page available
* at runtime from the About dialog for additional information.
*****************************************************************************/
define(
['../../src/actions/CreateNewFolderAction'],
function (CreateNewFolderAction) {
describe("The Create New Folder Action", function () {
var mockDomainObject,
mockNewObject,
mockType,
testModel,
mockFolderName,
mockTypeService,
mockActionContext,
mockCompositionCapability,
action;
function mockPromise(value) {
return (value && value.then) ? value : {
then: function (callback) {
return mockPromise(callback(value));
}
};
}
beforeEach(function () {
mockDomainObject = jasmine.createSpyObj(
"domainObject",
[
"getCapability",
"useCapability",
"hasCapability",
"getId"
]
);
mockNewObject = jasmine.createSpyObj(
"newObject",
[
"getCapability",
"useCapability",
"hasCapability",
"getId"
]
);
mockType = jasmine.createSpyObj(
"type",
[
"getKey",
"getInitialModel"
]
);
testModel = {
type: mockType,
name: "Name",
location: "someLocation"
};
mockFolderName = "Name";
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockActionContext = { domainObject: mockDomainObject };
mockCompositionCapability = jasmine.createSpyObj(
"composition",
["add"]
);
mockType.getKey.and.returnValue("test");
mockType.getInitialModel.and.returnValue(testModel);
mockDomainObject.getCapability.and.callFake(function (capability) {
return (capability === 'composition') && mockCompositionCapability;
});
mockDomainObject.hasCapability.and.returnValue(true);
mockCompositionCapability.add.and.returnValue(mockPromise(true));
mockDomainObject.useCapability.and.callFake(function (capability) {
return (capability === 'instantiation') && mockNewObject;
});
mockTypeService.getType.and.returnValue(mockType);
mockDomainObject.getId.and.returnValue("id");
action = new CreateNewFolderAction(mockTypeService, mockActionContext);
});
it("uses the instantiation capability when performed", function () {
action.perform(mockFolderName);
expect(mockDomainObject.useCapability)
.toHaveBeenCalledWith("instantiation", jasmine.any(Object));
});
it("adds new objects to the parent's composition", function () {
action.perform(mockFolderName);
expect(mockDomainObject.getCapability).toHaveBeenCalledWith("composition");
expect(mockCompositionCapability.add).toHaveBeenCalled();
});
it("is only applicable when a domain object is in context", function () {
expect(CreateNewFolderAction.appliesTo(mockActionContext)).toBeTruthy();
expect(CreateNewFolderAction.appliesTo({})).toBeFalsy();
expect(mockDomainObject.hasCapability).toHaveBeenCalledWith('editor');
});
});
}
);

View File

@ -31,26 +31,63 @@ define(
var mockScope,
mockTimeout,
mockDomainObject,
mockFolderObject,
mockRootObject,
mockContext,
mockActions,
mockObjectService,
mockTypeService,
mockType,
mockInstantiate,
mockPolicyService,
getObjectsPromise,
testModel,
capabilities,
mockCreateNewFolderAction,
mockActionCapability,
mockProperties,
controller;
beforeEach(function () {
mockScope = jasmine.createSpyObj(
"$scope",
["$watch"]
["$watch", "validParent"]
);
mockTimeout = jasmine.createSpy("$timeout");
mockInstantiate = jasmine.createSpy("instantiate");
mockDomainObject = jasmine.createSpyObj(
"domainObject",
["getCapability"]
[
"useCapability",
"getModel",
"getCapability"
]
);
mockFolderObject = jasmine.createSpyObj(
"folderObject",
[
"useCapability",
"getModel",
"getCapability"
]
);
mockCreateNewFolderAction = jasmine.createSpyObj(
"createNewFolderAction",
[
"perform"
]
);
mockRootObject = jasmine.createSpyObj(
"rootObject",
["getCapability"]
);
mockActionCapability = jasmine.createSpyObj(
"actionCapability",
[
"getActions",
"perform"
]
);
mockContext = jasmine.createSpyObj(
"context",
["getRoot"]
@ -59,25 +96,70 @@ define(
"objectService",
["getObjects"]
);
mockTypeService = jasmine.createSpyObj(
"typeService",
["getType"]
);
mockPolicyService = jasmine.createSpyObj(
"policyService",
["allow"]
);
getObjectsPromise = jasmine.createSpyObj(
"promise",
["then"]
);
mockDomainObject.getCapability.and.returnValue(mockContext);
mockType = jasmine.createSpyObj(
"type",
[
"getKey",
"getProperties",
"getInitialModel"
]
);
testModel = { someKey: "some value" };
mockProperties = ['a', 'b', 'c'].map(function (k) {
var mockProperty = jasmine.createSpyObj(
'property-' + k,
['propertyDefinition']
);
mockProperty.propertyDefinition = {
key: "name",
pattern: "test"
};
return mockProperty;
});
capabilities = {
"action" : mockActionCapability,
"context": mockContext
};
mockActions = [mockCreateNewFolderAction];
mockContext.getRoot.and.returnValue(mockRootObject);
mockObjectService.getObjects.and.returnValue(getObjectsPromise);
mockTypeService.getType.and.callFake(function (typename) {
return mockType;
});
mockInstantiate.and.returnValue(mockFolderObject);
mockType.getKey.and.returnValue("test");
mockType.getInitialModel.and.returnValue(testModel);
mockType.getProperties.and.returnValue(mockProperties);
mockDomainObject.getCapability.and.callFake(function (capability) {
return capabilities[capability];
});
mockDomainObject.useCapability.and.returnValue();
mockDomainObject.getModel.and.returnValue(testModel);
mockFolderObject.getCapability.and.returnValue(capabilities);
mockFolderObject.useCapability.and.returnValue();
mockFolderObject.getModel.and.returnValue(testModel);
mockScope.ngModel = {};
mockScope.field = "someField";
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
describe("when context is available", function () {
beforeEach(function () {
mockContext.getRoot.and.returnValue(mockRootObject);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
it("adds a treeModel to scope", function () {
@ -145,7 +227,7 @@ define(
getObjectsPromise.then.and.callFake(function (callback) {
callback({'ROOT': defaultRoot});
});
controller = new LocatorController(mockScope, mockTimeout, mockObjectService);
controller = new LocatorController(mockScope, mockTimeout, mockObjectService, mockTypeService, mockPolicyService, mockInstantiate);
});
it("provides a default context where none is available", function () {
@ -169,3 +251,4 @@ define(
});
}
);