mirror of
https://github.com/nasa/openmct.git
synced 2025-06-25 10:44:21 +00:00
Compare commits
3 Commits
plot-wait-
...
open627_re
Author | SHA1 | Date | |
---|---|---|---|
a515ccfb61 | |||
560897fe81 | |||
e1d20e7275 |
@ -80,7 +80,6 @@ define([
|
||||
"$scope",
|
||||
"$route",
|
||||
"$location",
|
||||
"$q",
|
||||
"objectService",
|
||||
"navigationService",
|
||||
"urlService"
|
||||
@ -202,6 +201,10 @@ define([
|
||||
{
|
||||
"key": "inspector-region",
|
||||
"templateUrl": "templates/browse/inspector-region.html"
|
||||
},
|
||||
{
|
||||
"key": "view-region",
|
||||
"templateUrl": "templates/view-region.html"
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
|
@ -47,11 +47,6 @@
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-toolbar name="mctToolbar"
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state"
|
||||
class="flex-elem grows">
|
||||
</mct-toolbar>
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
|
@ -63,7 +63,7 @@
|
||||
<mct-split-pane class='l-object-and-inspector contents abs' anchor='right'>
|
||||
<div class='split-pane-component t-object pane primary-pane left'>
|
||||
<mct-representation mct-object="navigatedObject"
|
||||
key="'browse-object'"
|
||||
key="'view-region'"
|
||||
class="abs holder holder-object">
|
||||
</mct-representation>
|
||||
</div>
|
||||
@ -87,4 +87,3 @@
|
||||
</div>
|
||||
<mct-include key="'bottombar'"></mct-include>
|
||||
</div>
|
||||
|
||||
|
@ -19,14 +19,12 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<div content="jquery-wrapper"
|
||||
class="abs holder-all edit-mode"
|
||||
ng-controller="EditController as editMode"
|
||||
mct-before-unload="editMode.getUnloadWarning()">
|
||||
|
||||
<mct-representation key="'edit-object'" mct-object="editMode.navigatedObject()">
|
||||
</mct-representation>
|
||||
|
||||
<mct-include key="'bottombar'"></mct-include>
|
||||
|
||||
</div>
|
||||
<div ng-controller="RegionController as regionController">
|
||||
<div ng-repeat="part in regions.view.parts">
|
||||
<mct-representation
|
||||
key="part.content.key"
|
||||
mct-object="domainObject"
|
||||
ng-model="ngModel">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</div><!--/ PaneController -->
|
@ -27,10 +27,9 @@
|
||||
*/
|
||||
define(
|
||||
[
|
||||
'../../../representation/src/gestures/GestureConstants',
|
||||
'../../edit/src/objects/EditableDomainObject'
|
||||
'../../../representation/src/gestures/GestureConstants'
|
||||
],
|
||||
function (GestureConstants, EditableDomainObject) {
|
||||
function (GestureConstants) {
|
||||
"use strict";
|
||||
|
||||
var ROOT_ID = "ROOT",
|
||||
@ -47,18 +46,11 @@ define(
|
||||
* @memberof platform/commonUI/browse
|
||||
* @constructor
|
||||
*/
|
||||
function BrowseController($scope, $route, $location, $q, objectService, navigationService, urlService) {
|
||||
function BrowseController($scope, $route, $location, objectService, navigationService, urlService) {
|
||||
var path = [ROOT_ID].concat(
|
||||
($route.current.params.ids || DEFAULT_PATH).split("/")
|
||||
);
|
||||
|
||||
function isDirty(){
|
||||
var editorCapability = $scope.navigatedObject &&
|
||||
$scope.navigatedObject.getCapability("editor"),
|
||||
hasChanges = editorCapability && editorCapability.dirty();
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
function updateRoute(domainObject) {
|
||||
var priorRoute = $route.current,
|
||||
// Act as if params HADN'T changed to avoid page reload
|
||||
@ -75,9 +67,7 @@ define(
|
||||
// urlService.urlForLocation used to adjust current
|
||||
// path to new, addressed, path based on
|
||||
// domainObject
|
||||
$location.path(urlService.urlForLocation("browse",
|
||||
domainObject.hasCapability('editor') ?
|
||||
domainObject.getOriginalObject() : domainObject));
|
||||
$location.path(urlService.urlForLocation("browse", domainObject));
|
||||
|
||||
}
|
||||
|
||||
@ -89,17 +79,15 @@ define(
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDirty() && !confirm(CONFIRM_MSG)) {
|
||||
$scope.treeModel.selectedObject = $scope.navigatedObject;
|
||||
navigationService.setNavigation($scope.navigatedObject);
|
||||
} else {
|
||||
if ($scope.navigatedObject && $scope.navigatedObject.hasCapability("editor")){
|
||||
$scope.navigatedObject.getCapability("editor").cancel();
|
||||
}
|
||||
if (navigationService.setNavigation(domainObject)) {
|
||||
$scope.navigatedObject = domainObject;
|
||||
$scope.treeModel.selectedObject = domainObject;
|
||||
navigationService.setNavigation(domainObject);
|
||||
updateRoute(domainObject);
|
||||
} else {
|
||||
//If navigation was unsuccessful (ie. blocked), reset
|
||||
// the selected object in the tree to the currently
|
||||
// navigated object
|
||||
$scope.treeModel.selectedObject = $scope.navigatedObject ;
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,18 +158,13 @@ define(
|
||||
selectedObject: navigationService.getNavigation()
|
||||
};
|
||||
|
||||
$scope.beforeUnloadWarning = function() {
|
||||
return isDirty() ?
|
||||
"Unsaved changes will be lost if you leave this page." :
|
||||
undefined;
|
||||
};
|
||||
|
||||
// Listen for changes in navigation state.
|
||||
navigationService.addListener(setNavigation);
|
||||
|
||||
// Also listen for changes which come from the tree
|
||||
// Also listen for changes which come from the tree. Changes in
|
||||
// the tree will trigger a change in browse navigation state.
|
||||
$scope.$watch("treeModel.selectedObject", setNavigation);
|
||||
|
||||
|
||||
// Clean up when the scope is destroyed
|
||||
$scope.$on("$destroy", function () {
|
||||
navigationService.removeListener(setNavigation);
|
||||
@ -191,4 +174,3 @@ define(
|
||||
return BrowseController;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -22,11 +22,8 @@
|
||||
/*global define,Promise*/
|
||||
|
||||
define(
|
||||
[
|
||||
'../../../representation/src/gestures/GestureConstants',
|
||||
'../../edit/src/objects/EditableDomainObject'
|
||||
],
|
||||
function (GestureConstants, EditableDomainObject) {
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -57,10 +54,9 @@ define(
|
||||
|
||||
function updateQueryParam(viewKey) {
|
||||
var unlisten,
|
||||
priorRoute = $route.current,
|
||||
isEditMode = $scope.domainObject && $scope.domainObject.hasCapability('editor');
|
||||
priorRoute = $route.current;
|
||||
|
||||
if (viewKey && !isEditMode) {
|
||||
if (viewKey) {
|
||||
$location.search('view', viewKey);
|
||||
unlisten = $scope.$on('$locationChangeSuccess', function () {
|
||||
// Checks path to make sure /browse/ is at front
|
||||
@ -76,10 +72,6 @@ define(
|
||||
$scope.$watch('domainObject', setViewForDomainObject);
|
||||
$scope.$watch('representation.selected.key', updateQueryParam);
|
||||
|
||||
$scope.cancelEditing = function() {
|
||||
navigationService.setNavigation($scope.domainObject.getDomainObject());
|
||||
};
|
||||
|
||||
$scope.doAction = function (action){
|
||||
return $scope[action] && $scope[action]();
|
||||
};
|
||||
@ -89,4 +81,3 @@ define(
|
||||
return BrowseObjectController;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -23,9 +23,10 @@
|
||||
|
||||
define(
|
||||
[
|
||||
'./InspectorRegion'
|
||||
'./InspectorRegion',
|
||||
'./ViewRegion'
|
||||
],
|
||||
function (InspectorRegion) {
|
||||
function (InspectorRegion, ViewRegion) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -53,6 +54,7 @@ define(
|
||||
var regions = type.getDefinition().regions || {};
|
||||
|
||||
regions.inspector = regions.inspector || new InspectorRegion();
|
||||
regions.view = regions.view || new ViewRegion();
|
||||
|
||||
type.getDefinition().regions = regions;
|
||||
|
||||
|
73
platform/commonUI/browse/src/ViewRegion.js
Normal file
73
platform/commonUI/browse/src/ViewRegion.js
Normal file
@ -0,0 +1,73 @@
|
||||
/*****************************************************************************
|
||||
* Open MCT Web, Copyright (c) 2014-2015, United States Government
|
||||
* as represented by the Administrator of the National Aeronautics and Space
|
||||
* Administration. All rights reserved.
|
||||
*
|
||||
* Open MCT Web 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 Web 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.
|
||||
*****************************************************************************/
|
||||
/*global define,window*/
|
||||
|
||||
define(
|
||||
[
|
||||
'../../regions/src/Region'
|
||||
],
|
||||
function (Region) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Defines the default View region. Captured in a class to
|
||||
* allow for modular extension and customization of regions based on
|
||||
* the typical case.
|
||||
* @memberOf platform/commonUI/regions
|
||||
* @constructor
|
||||
*/
|
||||
function ViewRegion() {
|
||||
Region.call(this);
|
||||
|
||||
this.buildRegion();
|
||||
}
|
||||
|
||||
ViewRegion.prototype = Object.create(Region.prototype);
|
||||
ViewRegion.prototype.constructor = Region;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ViewRegion.prototype.buildRegion = function() {
|
||||
var browseViewPart = {
|
||||
name: 'browse-view',
|
||||
title: 'Browse Object View',
|
||||
modes: ['browse'],
|
||||
content: {
|
||||
key: 'browse-object'
|
||||
}
|
||||
},
|
||||
editViewPart = {
|
||||
name: 'edit-view',
|
||||
title: 'Edit Object View',
|
||||
modes: ['edit'],
|
||||
content: {
|
||||
key: 'edit-object'
|
||||
}
|
||||
};
|
||||
this.addPart(browseViewPart);
|
||||
this.addPart(editViewPart);
|
||||
};
|
||||
|
||||
return ViewRegion;
|
||||
}
|
||||
);
|
@ -37,7 +37,7 @@ define(
|
||||
*/
|
||||
function NavigationService() {
|
||||
this.navigated = undefined;
|
||||
this.callbacks = [];
|
||||
this.callbacks = {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,26 +50,48 @@ define(
|
||||
|
||||
/**
|
||||
* Set the current navigation state. This will invoke listeners.
|
||||
* Changing the navigation state will be blocked if any of the
|
||||
* 'before' navigation state change listeners return 'false'.
|
||||
* @param {DomainObject} domainObject the domain object to navigate to
|
||||
*/
|
||||
NavigationService.prototype.setNavigation = function (value) {
|
||||
var canNavigate = true;
|
||||
if (this.navigated !== value) {
|
||||
this.navigated = value;
|
||||
this.callbacks.forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
canNavigate = (this.callbacks.before || [])
|
||||
.reduce(function (previous, callback) {
|
||||
//Check whether the callback returned a value of
|
||||
// 'false' indicating that navigation should not
|
||||
// continue. All other return values will allow
|
||||
// navigation to continue
|
||||
return (callback(value)!==false) && previous;
|
||||
}, true);
|
||||
if (canNavigate) {
|
||||
this.navigated = value;
|
||||
(this.callbacks.after || []).forEach(function (callback) {
|
||||
callback(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
return canNavigate;
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for changes in navigation. The passed callback will
|
||||
* be invoked with the new domain object of navigation when
|
||||
* this changes.
|
||||
* this changes. Callbacks can be registered to listen to pre or
|
||||
* post-navigation events. The event to listen to is specified using
|
||||
* the event parameter. In the case of pre-navigation events
|
||||
* returning a false value will prevent the navigation event from
|
||||
* going ahead.
|
||||
* @param {function} callback the callback to invoke when
|
||||
* navigation state changes
|
||||
* @param {string} [event=after] the navigation event to listen to.
|
||||
* One of 'before' or 'after'.
|
||||
*/
|
||||
NavigationService.prototype.addListener = function (callback) {
|
||||
this.callbacks.push(callback);
|
||||
NavigationService.prototype.addListener = function (callback, event) {
|
||||
event = event || 'after';
|
||||
this.callbacks[event] = this.callbacks[event] || [];
|
||||
this.callbacks[event].push(callback);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -77,9 +99,12 @@ define(
|
||||
* @param {function} callback the callback which should
|
||||
* no longer be invoked when navigation state
|
||||
* changes
|
||||
* @param {string} [event=after] the navigation event that the
|
||||
* callback is registered to. One of 'before' or 'after'.
|
||||
*/
|
||||
NavigationService.prototype.removeListener = function (callback) {
|
||||
this.callbacks = this.callbacks.filter(function (cb) {
|
||||
NavigationService.prototype.removeListener = function (callback, event) {
|
||||
event = event || 'after';
|
||||
this.callbacks[event] = this.callbacks[event].filter(function (cb) {
|
||||
return cb !== callback;
|
||||
});
|
||||
};
|
||||
|
@ -29,8 +29,7 @@ define(
|
||||
function (BrowseController) {
|
||||
"use strict";
|
||||
|
||||
//TODO: Disabled for NEM Beta
|
||||
xdescribe("The browse controller", function () {
|
||||
describe("The browse controller", function () {
|
||||
var mockScope,
|
||||
mockRoute,
|
||||
mockLocation,
|
||||
@ -230,7 +229,10 @@ define(
|
||||
// prior to setting $route.current
|
||||
mockLocation.path.andReturn("/browse/");
|
||||
|
||||
mockNavigationService.setNavigation.andReturn(true);
|
||||
|
||||
// Exercise the Angular workaround
|
||||
mockNavigationService.addListener.mostRecentCall.args[0]();
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
expect(mockUnlisten).toHaveBeenCalled();
|
||||
|
||||
@ -241,6 +243,32 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
it("after successful navigation event sets the selected tree " +
|
||||
"object", function () {
|
||||
mockScope.navigatedObject = mockDomainObject;
|
||||
mockNavigationService.setNavigation.andReturn(true);
|
||||
|
||||
//Simulate a change in selected tree object
|
||||
mockScope.treeModel = {selectedObject: mockDomainObject};
|
||||
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
|
||||
|
||||
expect(mockScope.treeModel.selectedObject).toBe(mockNextObject);
|
||||
expect(mockScope.treeModel.selectedObject).not.toBe(mockDomainObject);
|
||||
});
|
||||
|
||||
it("after failed navigation event resets the selected tree" +
|
||||
" object", function () {
|
||||
mockScope.navigatedObject = mockDomainObject;
|
||||
mockNavigationService.setNavigation.andReturn(false);
|
||||
|
||||
//Simulate a change in selected tree object
|
||||
mockScope.treeModel = {selectedObject: mockDomainObject};
|
||||
mockScope.$watch.mostRecentCall.args[1](mockNextObject);
|
||||
|
||||
expect(mockScope.treeModel.selectedObject).not.toBe(mockNextObject);
|
||||
expect(mockScope.treeModel.selectedObject).toBe(mockDomainObject);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -84,6 +84,24 @@ define(
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds listeners to the 'after' state by default", function(){
|
||||
expect(navigationService.callbacks.after).toBeUndefined();
|
||||
navigationService.addListener(function(){});
|
||||
expect(navigationService.callbacks.after).toBeDefined();
|
||||
expect(navigationService.callbacks.after.length).toBe(1);
|
||||
});
|
||||
|
||||
it("allows navigationService events to be prevented", function(){
|
||||
var callback = jasmine.createSpy("callback"),
|
||||
navigationResult;
|
||||
callback.andReturn(false);
|
||||
navigationService.addListener(callback, "before");
|
||||
navigationResult = navigationService.setNavigation({});
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(navigationResult).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -22,10 +22,10 @@
|
||||
/*global define*/
|
||||
|
||||
define([
|
||||
"./src/controllers/EditController",
|
||||
"./src/controllers/EditActionController",
|
||||
"./src/controllers/EditPanesController",
|
||||
"./src/controllers/ElementsController",
|
||||
"./src/controllers/EditObjectController",
|
||||
"./src/directives/MCTBeforeUnload",
|
||||
"./src/actions/LinkAction",
|
||||
"./src/actions/EditAction",
|
||||
@ -38,10 +38,10 @@ define([
|
||||
"./src/representers/EditToolbarRepresenter",
|
||||
'legacyRegistry'
|
||||
], function (
|
||||
EditController,
|
||||
EditActionController,
|
||||
EditPanesController,
|
||||
ElementsController,
|
||||
EditObjectController,
|
||||
MCTBeforeUnload,
|
||||
LinkAction,
|
||||
EditAction,
|
||||
@ -58,22 +58,7 @@ define([
|
||||
|
||||
legacyRegistry.register("platform/commonUI/edit", {
|
||||
"extensions": {
|
||||
"routes": [
|
||||
{
|
||||
"when": "/edit",
|
||||
"templateUrl": "templates/edit.html"
|
||||
}
|
||||
],
|
||||
"controllers": [
|
||||
{
|
||||
"key": "EditController",
|
||||
"implementation": EditController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$q",
|
||||
"navigationService"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "EditActionController",
|
||||
"implementation": EditActionController,
|
||||
@ -94,6 +79,14 @@ define([
|
||||
"depends": [
|
||||
"$scope"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "EditObjectController",
|
||||
"implementation": EditObjectController,
|
||||
"depends": [
|
||||
"$scope",
|
||||
"$location"
|
||||
]
|
||||
}
|
||||
],
|
||||
"directives": [
|
||||
@ -101,7 +94,8 @@ define([
|
||||
"key": "mctBeforeUnload",
|
||||
"implementation": MCTBeforeUnload,
|
||||
"depends": [
|
||||
"$window"
|
||||
"$window",
|
||||
"navigationService"
|
||||
]
|
||||
}
|
||||
],
|
||||
@ -197,6 +191,9 @@ define([
|
||||
"templateUrl": "templates/edit-object.html",
|
||||
"uses": [
|
||||
"view"
|
||||
],
|
||||
"gestures": [
|
||||
"drop"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -19,50 +19,51 @@
|
||||
this source code distribution or the Licensing information page available
|
||||
at runtime from the About dialog for additional information.
|
||||
-->
|
||||
<mct-representation key="'topbar-edit'"
|
||||
mct-object="domainObject"
|
||||
ng-model="representation">
|
||||
</mct-representation>
|
||||
<div class="holder edit-area abs">
|
||||
<mct-split-pane class='contents abs' anchor='right'>
|
||||
<div class='split-pane-component pane left edit-main'>
|
||||
<mct-toolbar name="mctToolbar"
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state">
|
||||
</mct-toolbar>
|
||||
<mct-representation key="representation.selected.key"
|
||||
toolbar="toolbar"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="holder abs object-holder work-area">
|
||||
<div class="abs l-flex-col" ng-controller="EditObjectController as EditObjectController">
|
||||
<div mct-before-unload="EditObjectController.getUnloadWarning()"
|
||||
class="holder flex-elem l-flex-row object-browse-bar ">
|
||||
<div class="items-select left flex-elem l-flex-row grows">
|
||||
<mct-representation key="'back-arrow'"
|
||||
mct-object="domainObject"
|
||||
class="flex-elem l-back"></mct-representation>
|
||||
<mct-representation key="'object-header'"
|
||||
mct-object="domainObject"
|
||||
class="l-flex-row flex-elem grows object-header">
|
||||
</mct-representation>
|
||||
</div>
|
||||
<mct-splitter></mct-splitter>
|
||||
<div
|
||||
class='split-pane-component pane right edit-objects menus-to-left'
|
||||
ng-controller='EditPanesController as editPanes'
|
||||
>
|
||||
<mct-split-pane class='contents abs' anchor='bottom'>
|
||||
<div
|
||||
class="abs pane top accordion"
|
||||
ng-controller="ToggleController as toggle"
|
||||
>
|
||||
<mct-container key="accordion" label="Library">
|
||||
<mct-representation key="'tree'"
|
||||
mct-object="editPanes.getRoot()">
|
||||
</mct-representation>
|
||||
</mct-container>
|
||||
</div>
|
||||
<mct-splitter></mct-splitter>
|
||||
<div
|
||||
class="abs pane bottom accordion"
|
||||
ng-controller="ToggleController as toggle"
|
||||
>
|
||||
<mct-container key="accordion" label="Elements">
|
||||
<mct-representation key="'edit-elements'" mct-object="domainObject">
|
||||
</mct-representation>
|
||||
</mct-container>
|
||||
</div>
|
||||
</mct-split-pane>
|
||||
<div class="btn-bar right l-flex-row flex-elem flex-justify-end flex-fixed">
|
||||
<mct-representation key="'switcher'"
|
||||
mct-object="domainObject"
|
||||
ng-model="representation">
|
||||
</mct-representation>
|
||||
<!-- Temporarily, on mobile, the action buttons are hidden-->
|
||||
<mct-representation key="'action-group'"
|
||||
mct-object="domainObject"
|
||||
parameters="{ category: 'view-control' }"
|
||||
class="mobile-hide">
|
||||
</mct-representation>
|
||||
</div>
|
||||
</mct-split-pane>
|
||||
</div>
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper">
|
||||
<div class="holder l-flex-col flex-elem grows l-object-wrapper-inner">
|
||||
<!-- Toolbar and Save/Cancel buttons -->
|
||||
<div class="l-edit-controls flex-elem l-flex-row flex-align-end">
|
||||
<mct-toolbar name="mctToolbar"
|
||||
structure="toolbar.structure"
|
||||
ng-model="toolbar.state"
|
||||
class="flex-elem grows">
|
||||
</mct-toolbar>
|
||||
<mct-representation key="'edit-action-buttons'"
|
||||
mct-object="domainObject"
|
||||
class='flex-elem conclude-editing'>
|
||||
</mct-representation>
|
||||
|
||||
</div>
|
||||
<mct-representation key="representation.selected.key"
|
||||
mct-object="representation.selected.key && domainObject"
|
||||
class="abs flex-elem grows object-holder-main scroll"
|
||||
toolbar="toolbar">
|
||||
</mct-representation>
|
||||
</div><!--/ l-object-wrapper-inner -->
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,13 +72,26 @@ define(
|
||||
* Enter edit mode.
|
||||
*/
|
||||
EditAction.prototype.perform = function () {
|
||||
var editableObject;
|
||||
var self = this;
|
||||
if (!this.domainObject.hasCapability("editor")) {
|
||||
editableObject = new EditableDomainObject(this.domainObject, this.$q);
|
||||
editableObject.getCapability('status').set('editing', true);
|
||||
this.navigationService.setNavigation(editableObject);
|
||||
//TODO: This is only necessary because the drop gesture is
|
||||
// wrapping the object itself, need to refactor this later.
|
||||
// All responsibility for switching into edit mode should be
|
||||
// in the edit action, and not duplicated in the gesture
|
||||
this.domainObject = new EditableDomainObject(this.domainObject, this.$q);
|
||||
}
|
||||
//this.$location.path("/edit");
|
||||
this.navigationService.setNavigation(this.domainObject);
|
||||
this.domainObject.getCapability('status').set('editing', true);
|
||||
|
||||
//Register a listener to automatically cancel this edit action
|
||||
//if the user navigates away from this object.
|
||||
function cancelEditing(navigatedTo){
|
||||
if (!navigatedTo || navigatedTo.getId() !== self.domainObject.getId()) {
|
||||
self.domainObject.getCapability('editor').cancel();
|
||||
self.navigationService.removeListener(cancelEditing);
|
||||
}
|
||||
}
|
||||
this.navigationService.addListener(cancelEditing);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -26,41 +26,44 @@
|
||||
* @namespace platform/commonUI/edit
|
||||
*/
|
||||
define(
|
||||
["../objects/EditableDomainObject"],
|
||||
function (EditableDomainObject) {
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Controller which is responsible for populating the scope for
|
||||
* Edit mode; introduces an editable version of the currently
|
||||
* navigated domain object into the scope.
|
||||
* Edit mode
|
||||
* @memberof platform/commonUI/edit
|
||||
* @constructor
|
||||
*/
|
||||
function EditController($scope, $q, navigationService) {
|
||||
var self = this;
|
||||
function EditObjectController($scope, $location) {
|
||||
this.scope = $scope;
|
||||
|
||||
function setNavigation(domainObject) {
|
||||
// Wrap the domain object such that all mutation is
|
||||
// confined to edit mode (until Save)
|
||||
self.navigatedDomainObject =
|
||||
domainObject && new EditableDomainObject(domainObject, $q);
|
||||
var navigatedObject;
|
||||
function setViewForDomainObject(domainObject) {
|
||||
|
||||
var locationViewKey = $location.search().view;
|
||||
|
||||
function selectViewIfMatching(view) {
|
||||
if (view.key === locationViewKey) {
|
||||
$scope.representation = $scope.representation || {};
|
||||
$scope.representation.selected = view;
|
||||
}
|
||||
}
|
||||
|
||||
if (locationViewKey) {
|
||||
((domainObject && domainObject.useCapability('view')) || [])
|
||||
.forEach(selectViewIfMatching);
|
||||
}
|
||||
navigatedObject = domainObject;
|
||||
}
|
||||
|
||||
setNavigation(navigationService.getNavigation());
|
||||
navigationService.addListener(setNavigation);
|
||||
$scope.$on("$destroy", function () {
|
||||
navigationService.removeListener(setNavigation);
|
||||
});
|
||||
}
|
||||
$scope.$watch('domainObject', setViewForDomainObject);
|
||||
|
||||
/**
|
||||
* Get the domain object which is navigated-to.
|
||||
* @returns {DomainObject} the domain object that is navigated-to
|
||||
*/
|
||||
EditController.prototype.navigatedObject = function () {
|
||||
return this.navigatedDomainObject;
|
||||
};
|
||||
$scope.doAction = function (action){
|
||||
return $scope[action] && $scope[action]();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the warning to show if the user attempts to navigate
|
||||
@ -68,17 +71,20 @@ define(
|
||||
* @returns {string} the warning to show, or undefined if
|
||||
* there are no unsaved changes
|
||||
*/
|
||||
EditController.prototype.getUnloadWarning = function () {
|
||||
var navigatedObject = this.navigatedDomainObject,
|
||||
EditObjectController.prototype.getUnloadWarning = function () {
|
||||
var navigatedObject = this.scope.domainObject,
|
||||
editorCapability = navigatedObject &&
|
||||
navigatedObject.getCapability("editor"),
|
||||
hasChanges = editorCapability && editorCapability.dirty();
|
||||
statusCapability = navigatedObject &&
|
||||
navigatedObject.getCapability("status"),
|
||||
hasChanges = statusCapability && statusCapability.get('editing')
|
||||
&& editorCapability && editorCapability.dirty();
|
||||
|
||||
return hasChanges ?
|
||||
"Unsaved changes will be lost if you leave this page." :
|
||||
undefined;
|
||||
};
|
||||
|
||||
return EditController;
|
||||
return EditObjectController;
|
||||
}
|
||||
);
|
@ -35,7 +35,7 @@ define(
|
||||
* @constructor
|
||||
* @param $window the window
|
||||
*/
|
||||
function MCTBeforeUnload($window) {
|
||||
function MCTBeforeUnload($window, navigationService) {
|
||||
var unloads = [],
|
||||
oldBeforeUnload = $window.onbeforeunload;
|
||||
|
||||
@ -55,8 +55,23 @@ define(
|
||||
return scope.$eval(attrs.mctBeforeUnload);
|
||||
}
|
||||
|
||||
function shouldAllowNavigation(){
|
||||
// Get an unload message (if any)
|
||||
var warning = unload();
|
||||
// Prompt the user if there's an unload message
|
||||
return !warning || $window.confirm(warning);
|
||||
}
|
||||
|
||||
// Show a dialog before allowing a location change
|
||||
function checkNavigationEvent(event) {
|
||||
// Return a false value to the navigationService to
|
||||
// indicate that the navigation event should be prevented
|
||||
return shouldAllowNavigation();
|
||||
}
|
||||
|
||||
// Stop using this unload expression
|
||||
function removeUnload() {
|
||||
navigationService.removeListener(checkNavigationEvent, "before");
|
||||
unloads = unloads.filter(function (callback) {
|
||||
return callback !== unload;
|
||||
});
|
||||
@ -67,11 +82,8 @@ define(
|
||||
|
||||
// Show a dialog before allowing a location change
|
||||
function checkLocationChange(event) {
|
||||
// Get an unload message (if any)
|
||||
var warning = unload();
|
||||
// Prompt the user if there's an unload message
|
||||
if (warning && !$window.confirm(warning)) {
|
||||
// ...and prevent the route change if it was confirmed
|
||||
if (!shouldAllowNavigation()) {
|
||||
// Prevent the route change if it was confirmed
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@ -90,6 +102,8 @@ define(
|
||||
|
||||
// Also handle route changes
|
||||
scope.$on("$locationChangeStart", checkLocationChange);
|
||||
|
||||
navigationService.addListener(checkNavigationEvent, "before");
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -22,89 +22,72 @@
|
||||
/*global define,describe,it,expect,beforeEach,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/controllers/EditController"],
|
||||
function (EditController) {
|
||||
["../../src/controllers/EditObjectController"],
|
||||
function (EditObjectController) {
|
||||
"use strict";
|
||||
|
||||
describe("The Edit mode controller", function () {
|
||||
var mockScope,
|
||||
mockQ,
|
||||
mockNavigationService,
|
||||
mockObject,
|
||||
mockType,
|
||||
mockLocation,
|
||||
mockStatusCapability,
|
||||
mockCapabilities,
|
||||
controller;
|
||||
|
||||
// Utility function; look for a $watch on scope and fire it
|
||||
function fireWatch(expr, value) {
|
||||
mockScope.$watch.calls.forEach(function (call) {
|
||||
if (call.args[0] === expr) {
|
||||
call.args[1](value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
mockScope = jasmine.createSpyObj(
|
||||
"$scope",
|
||||
[ "$on" ]
|
||||
);
|
||||
mockQ = jasmine.createSpyObj('$q', ['when', 'all']);
|
||||
mockNavigationService = jasmine.createSpyObj(
|
||||
"navigationService",
|
||||
[ "getNavigation", "addListener", "removeListener" ]
|
||||
[ "$on", "$watch" ]
|
||||
);
|
||||
mockObject = jasmine.createSpyObj(
|
||||
"domainObject",
|
||||
[ "getId", "getModel", "getCapability", "hasCapability" ]
|
||||
[ "getId", "getModel", "getCapability", "hasCapability", "useCapability" ]
|
||||
);
|
||||
mockType = jasmine.createSpyObj(
|
||||
"type",
|
||||
[ "hasFeature" ]
|
||||
);
|
||||
mockStatusCapability = jasmine.createSpyObj('statusCapability',
|
||||
["get"]
|
||||
);
|
||||
|
||||
mockCapabilities = {
|
||||
"type" : mockType,
|
||||
"status": mockStatusCapability
|
||||
};
|
||||
|
||||
mockLocation = jasmine.createSpyObj('$location',
|
||||
["search"]
|
||||
);
|
||||
mockLocation.search.andReturn({"view": "fixed"});
|
||||
|
||||
mockNavigationService.getNavigation.andReturn(mockObject);
|
||||
mockObject.getId.andReturn("test");
|
||||
mockObject.getModel.andReturn({ name: "Test object" });
|
||||
mockObject.getCapability.andCallFake(function (key) {
|
||||
return key === 'type' && mockType;
|
||||
return mockCapabilities[key];
|
||||
});
|
||||
mockType.hasFeature.andReturn(true);
|
||||
|
||||
controller = new EditController(
|
||||
mockScope.domainObject = mockObject;
|
||||
|
||||
controller = new EditObjectController(
|
||||
mockScope,
|
||||
mockQ,
|
||||
mockNavigationService
|
||||
mockLocation
|
||||
);
|
||||
});
|
||||
|
||||
it("exposes the currently-navigated object", function () {
|
||||
expect(controller.navigatedObject()).toBeDefined();
|
||||
expect(controller.navigatedObject().getId()).toEqual("test");
|
||||
});
|
||||
|
||||
it("adds an editor capability to the navigated object", function () {
|
||||
// Should provide an editor capability...
|
||||
expect(controller.navigatedObject().getCapability("editor"))
|
||||
.toBeDefined();
|
||||
// Shouldn't have been the mock capability we provided
|
||||
expect(controller.navigatedObject().getCapability("editor"))
|
||||
.not.toEqual(mockType);
|
||||
});
|
||||
|
||||
it("detaches its navigation listener when destroyed", function () {
|
||||
var navCallback = mockNavigationService
|
||||
.addListener.mostRecentCall.args[0];
|
||||
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
// Verify precondition
|
||||
expect(mockNavigationService.removeListener)
|
||||
.not.toHaveBeenCalled();
|
||||
|
||||
// Trigger destroy
|
||||
mockScope.$on.mostRecentCall.args[1]();
|
||||
|
||||
// Listener should have been removed
|
||||
expect(mockNavigationService.removeListener)
|
||||
.toHaveBeenCalledWith(navCallback);
|
||||
});
|
||||
|
||||
it("exposes a warning message for unload", function () {
|
||||
var obj = controller.navigatedObject(),
|
||||
var obj = mockObject,
|
||||
mockEditor = jasmine.createSpyObj('editor', ['dirty']);
|
||||
|
||||
// Normally, should be undefined
|
||||
@ -112,14 +95,32 @@ define(
|
||||
|
||||
// Override the object's editor capability, make it look
|
||||
// like there are unsaved changes.
|
||||
obj.getCapability = jasmine.createSpy();
|
||||
obj.getCapability.andReturn(mockEditor);
|
||||
mockCapabilities.editor = mockEditor;
|
||||
mockEditor.dirty.andReturn(true);
|
||||
mockStatusCapability.get.andReturn(true);
|
||||
|
||||
// Should have some warning message here now
|
||||
expect(controller.getUnloadWarning()).toEqual(jasmine.any(String));
|
||||
});
|
||||
|
||||
|
||||
it("sets the active view from query parameters", function () {
|
||||
var testViews = [
|
||||
{ key: 'abc' },
|
||||
{ key: 'def', someKey: 'some value' },
|
||||
{ key: 'xyz' }
|
||||
];
|
||||
|
||||
mockObject.useCapability.andCallFake(function (c) {
|
||||
return (c === 'view') && testViews;
|
||||
});
|
||||
mockLocation.search.andReturn({ view: 'def' });
|
||||
|
||||
fireWatch('domainObject', mockObject);
|
||||
expect(mockScope.representation.selected)
|
||||
.toEqual(testViews[1]);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -31,6 +31,7 @@ define(
|
||||
mockScope,
|
||||
testAttrs,
|
||||
mockEvent,
|
||||
mockNavigationService,
|
||||
directive;
|
||||
|
||||
function fireListener(eventType, value) {
|
||||
@ -46,7 +47,8 @@ define(
|
||||
mockScope = jasmine.createSpyObj("$scope", ['$eval', '$on']);
|
||||
testAttrs = { mctBeforeUnload: "someExpression" };
|
||||
mockEvent = jasmine.createSpyObj("event", ["preventDefault"]);
|
||||
directive = new MCTBeforeUnload(mockWindow);
|
||||
mockNavigationService = jasmine.createSpyObj("navigationService", ["addListener", "removeListener"]);
|
||||
directive = new MCTBeforeUnload(mockWindow, mockNavigationService);
|
||||
directive.link(mockScope, {}, testAttrs);
|
||||
});
|
||||
|
||||
@ -65,6 +67,10 @@ define(
|
||||
);
|
||||
});
|
||||
|
||||
it("listens for navigation changes", function () {
|
||||
expect(mockNavigationService.addListener).toHaveBeenCalledWith(jasmine.any(Function), "before");
|
||||
});
|
||||
|
||||
it("listens for its scope's destroy event", function () {
|
||||
expect(mockScope.$on).toHaveBeenCalledWith(
|
||||
"$destroy",
|
||||
@ -108,9 +114,10 @@ define(
|
||||
it("cleans up listeners when destroyed", function () {
|
||||
fireListener("$destroy", mockEvent);
|
||||
expect(mockWindow.onbeforeunload).toBeUndefined();
|
||||
expect(mockNavigationService.removeListener).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -33,6 +33,8 @@ define(
|
||||
testRepresentation,
|
||||
mockDomainObject,
|
||||
mockPersistence,
|
||||
mockStatusCapability,
|
||||
mockCapabilities,
|
||||
representer;
|
||||
|
||||
function mockPromise(value) {
|
||||
@ -57,11 +59,20 @@ define(
|
||||
]);
|
||||
mockPersistence =
|
||||
jasmine.createSpyObj("persistence", ["persist"]);
|
||||
mockStatusCapability =
|
||||
jasmine.createSpyObj("statusCapability", ["get", "listen"]);
|
||||
|
||||
mockCapabilities = {
|
||||
"persistence": mockPersistence,
|
||||
"status": mockStatusCapability
|
||||
};
|
||||
|
||||
mockDomainObject.getModel.andReturn({});
|
||||
mockDomainObject.hasCapability.andReturn(true);
|
||||
mockDomainObject.useCapability.andReturn(true);
|
||||
mockDomainObject.getCapability.andReturn(mockPersistence);
|
||||
mockDomainObject.getCapability.andCallFake(function(capability){
|
||||
return mockCapabilities[capability];
|
||||
});
|
||||
|
||||
representer = new EditRepresenter(mockQ, mockLog, mockScope);
|
||||
representer.represent(testRepresentation, mockDomainObject);
|
||||
@ -101,4 +112,4 @@ define(
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
@ -165,16 +165,14 @@ define(
|
||||
if (shouldCreateVirtualPanel(domainObject, selectedObject)){
|
||||
editableDomainObject = createVirtualPanel(domainObject, selectedObject);
|
||||
if (editableDomainObject) {
|
||||
navigationService.setNavigation(editableDomainObject);
|
||||
editableDomainObject.getCapability('action').perform('edit');
|
||||
broadcastDrop(id, event);
|
||||
editableDomainObject.getCapability('status').set('editing', true);
|
||||
}
|
||||
} else {
|
||||
$q.when(action && action.perform()).then(function (result) {
|
||||
//Don't go into edit mode for folders
|
||||
if (domainObjectType!=='folder') {
|
||||
navigationService.setNavigation(editableDomainObject);
|
||||
editableDomainObject.getCapability('status').set('editing', true);
|
||||
editableDomainObject.getCapability('action').perform('edit');
|
||||
}
|
||||
broadcastDrop(id, event);
|
||||
});
|
||||
|
Reference in New Issue
Block a user