We are using Forge Autodesk Viewer to load Forge Models.
We are using the framework ReactJS for our application and we have the function bellow to load one model at a viewer :
function loadModel(viewer, documentId) {
function onDocumentLoadSuccess(viewerDocument) {
// viewerDocument is an instance of Autodesk.Viewing.Document
const bubbleNode = viewerDocument.getRoot();
let defaultModel;
if (props.phaseName) {
defaultModel = bubbleNode.getMasterView(props.phaseName);
} else if (props.guid) {
defaultModel = bubbleNode.findByGuid(props.guid);
} else if (props.viewableID) {
const results = bubbleNode.search({viewableID: props.viewableID});
if (results && results.length) {
defaultModel = results[0];
}
} else if (props.geomIndex) {
const geoms = bubbleNode.search({type: "geometry"});
if (geoms.length) {
if (props.geomIndex < 0 || props.geomIndex >= geoms.length) {
console.warn("GeometryIndex Error: Invalid geometry index.");
}
const index = Math.min(Math.max(props.geomIndex, 0), geoms.length - 1); // Ensure index is valid.
defaultModel = geoms[index];
}
}
if (!defaultModel) defaultModel = bubbleNode.getDefaultGeometry(true);
const skipHiddenFragments = props.skipHiddenFragments || false;
viewer.loadDocumentNode(viewerDocument, defaultModel, {
keepCurrentModels: true,
skipHiddenFragments: skipHiddenFragments,
});
viewer.prefs.set("ghosting", false);
viewer.loadExtension("Autodesk.Viewing.MarkupsCore")
viewer.loadExtension("Autodesk.Viewing.MarkupsGui")
}
function onDocumentLoadFailure() {
console.error("Failed fetching Forge manifest");
}
if (documentId) {
Autodesk.Viewing.Document.load(
documentId,
onDocumentLoadSuccess,
onDocumentLoadFailure
);
} else {
props.eventBus.dispatchEvent({type: "VIEWER_READY", data: {viewer}});
}
}
We actually want to know how we could load multiple models using ReactJS.
Thank you for your response.
There is no difference in making the code support multiple models between w/ and w/o react.js. You can find lots of examples by searching https://stackoverflow.com/search?q=%5Bautodesk-forge%5D+multiple+models
Anyway...
Here is one for loading multiple models, but you must change the props passed to the Viewer component, and event calls outside the Viewer component accordingly.
//process each promise
//refer to http://jsfiddle.net/jfriend00/h3zaw8u8/
const promisesInSequence = (tasks, callback) => {
const results = [];
return tasks.reduce((p, item) => {
return p.then(() => {
return callback(item).then((data) => {
results.push(data);
return results;
});
});
}, Promise.resolve());
};
const AGGREGATE_GEOMETRY_LOADED_EVENT = 'aggregateGeometryLoaded';
/**
* #component
* Component for rendering LMV
* #param {Object} props
* #param {("AutodeskProduction"|"AutodeskStaging"|"MD20ProdUS"|"MD20ProdEU")} [props.env] Forge API environment
* #param {Function} props.getToken Returns the Forge API token to access LMV
* #param {"derivativeV2"|"derivativeV2_EU"|"modelDerivativeV2"|"fluent"|"D3S"|"D3S_EU"} [props.api] Default = "derivativeV2". Please refer to LMV documentation for more information.
* #param {Object[]} [props.docUrns] Model data to be loaded
* #param {string} [modelURNs[].urn] Document URN of the model to be loaded
* #param {Object} [modelURNs[].options] model options used in loading the specfic model
* #param {string} [modelURNs[].options.phaseName] phaseName of view to load in scene.
* #param {string} [modelURNs[].options.guid] guid of BubbleNode to load in scene.
* #param {string} [modelURNs[].options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [modelURNs[].options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [modelURNs[].options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #param {OnModelLoaded} [props.onModelLoaded] Callback function invoked when the model has loaded
* #param {OnViewerInitialized} [props.onViewerInitialized] Callback function invoked when LMV has been intialized
* #param {string[]} [props.extensions] List of extension ids forwarded to viewer config to load.
* #param {Object.<string, Object>} [props.disabledExtensions] Default extensions to prevent being loaded.
* #param {Object} [props.viewerOptions] Options object to forward to Autodesk.Viewing.Initializer
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer
*/
export default function Viewer(props) {
const viewerRef = useRef(null);
const viewerDomRef = useRef(null);
function onModelLoaded(event) {
const viewer = viewerRef.current;
// const av = Autodesk.Viewing;
// viewer.removeEventListener(av.GEOMETRY_LOADED_EVENT, onModelLoaded);
viewer.removeEventListener(AGGREGATE_GEOMETRY_LOADED_EVENT, onModelLoaded);
if (props.onModelLoaded) {
props.onModelLoaded(viewer, event);
}
}
/**
* Initializes LMV.
*
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#initializeViewer
* #private
*/
function initializeViewer() {
let viewerOptions = props.viewerOptions;
var options = Object.assign({}, viewerOptions, {
env: props.env,
api: props.api || "derivativeV2", // for models uploaded to EMEA change this option to 'derivativeV2_EU'
getAccessToken: async function (onTokenReady) {
let token = await props.getToken();
var timeInSeconds = 3600; // Use value provided by Forge Authentication (OAuth) API
onTokenReady(token, timeInSeconds);
},
});
Autodesk.Viewing.Initializer(options, async function () {
const extensionsToLoad = props.extensions;
const extensionsWithConfig = [];
const extensionsWithoutConfig = [];
for (let key in extensionsToLoad) {
const config = extensionsToLoad[key];
if (Object.keys(config).length === 0) {
extensionsWithoutConfig.push(key);
} else {
extensionsWithConfig.push(key);
}
}
const viewer = new Autodesk.Viewing.GuiViewer3D(viewerDomRef.current, {
extensions: extensionsWithoutConfig,
disabledExtensions: props.disabledExtensions || {},
});
extensionsWithConfig.forEach((ext) => {
viewer.loadExtension(ext, extensionsToLoad[ext]);
});
viewerRef.current = viewer;
const startedCode = viewer.start(undefined, undefined, undefined, undefined, options);
if (startedCode > 0) {
console.error("Failed to create a Viewer: WebGL not supported.");
return;
}
// loadModel(viewer, props.docUrn);
await loadModels(viewer, props.docUrns);
if (props.onViewerInitialized) {
props.onViewerInitialized(viewer);
}
});
}
/**
* Loads the specified models into the viewer.
*
* #param {Object} viewer Initialized LMV object
* #param {string} documentId Document URN of the model to be loaded
* #param {Object} options
* #param {string} [options.phaseName] phaseName of view to load in scene.
* #param {string} [options.guid] guid of BubbleNode to load in scene.
* #param {string} [options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#loadModelAsync
* #private
*/
async function loadModelAsync(viewer, documentId, options) {
return new Promise((resolve, reject) => {
async function onDocumentLoadSuccess(viewerDocument) {
// viewerDocument is an instance of Autodesk.Viewing.Document
const bubbleNode = viewerDocument.getRoot();
let defaultModel;
if (options.phaseName) {
defaultModel = bubbleNode.getMasterView(options.phaseName);
} else if (options.guid) {
defaultModel = bubbleNode.findByGuid(options.guid);
} else if (options.viewableID) {
const results = bubbleNode.search({ viewableID: options.viewableID });
if (results && results.length) {
defaultModel = results[0];
}
} else if (options.geomIndex) {
const geoms = bubbleNode.search({ type: "geometry" });
if (geoms.length) {
if (options.geomIndex < 0 || options.geomIndex >= geoms.length) {
console.warn("GeometryIndex Error: Invalid geometry index.");
}
const index = Math.min(Math.max(options.geomIndex, 0), geoms.length - 1); // Ensure index is valid.
defaultModel = geoms[index];
}
}
if (!defaultModel) defaultModel = bubbleNode.getDefaultGeometry(true);
const skipHiddenFragments = options.skipHiddenFragments || false;
let model = await viewer.loadDocumentNode(viewerDocument, defaultModel, {
keepCurrentModels: true,
skipHiddenFragments: skipHiddenFragments,
});
// modify the preference settings, since ghosting is causing heavy z-fighting with the room geometry
// it would be good we turn it off
if (!viewer.model)
viewer.prefs.set("ghosting", false);
await viewer.waitForLoadDone();
resolve(model);
}
function onDocumentLoadFailure() {
console.error("Failed fetching Forge manifest");
}
if (documentId) {
Autodesk.Viewing.Document.load(
documentId,
onDocumentLoadSuccess,
onDocumentLoadFailure
);
} else {
props.eventBus.dispatchEvent({ type: "VIEWER_READY", data: { viewer } });
}
});
}
/**
* Loads the specified models into the viewer.
*
* #param {Object} viewer Initialized LMV object
* #param {Object[]} modelURNs Model data to be loaded
* #param {string} [modelURNs[].urn] Document URN of the model to be loaded
* #param {Object} [modelURNs[].options] model options used in loading the specfic model
* #param {string} [modelURNs[].options.phaseName] phaseName of view to load in scene.
* #param {string} [modelURNs[].options.guid] guid of BubbleNode to load in scene.
* #param {string} [modelURNs[].options.viewableID] viewableID of BubbleNode to load in scene.
* #param {number} [modelURNs[].options.geomIndex] Index of geometry to load in scene.
* #param {Boolean} [modelURNs[].options.skipHiddenFragments] Boolean to specify if hidden fragments should be skipped (Default: false,
* Hidden fragments are required for heatmaps in rooms, only applicable to SVF2)
* #memberof Autodesk.DataVisualization.UI
* #alias Autodesk.DataVisualization.UI.Viewer#loadModelAsync
* #private
*/
async function loadModelsAsync(viewer, modelURNs) {
// const av = Autodesk.Viewing;
// viewer.addEventListener(av.GEOMETRY_LOADED_EVENT, onModelLoaded, { once: true });
viewer.addEventListener(AGGREGATE_GEOMETRY_LOADED_EVENT, onModelLoaded, { once: true });
const results = await promisesInSequence(modelURNs, (d) => loadModelAsync(d.urn, d.options));
viewer.fireEvent({
type: AGGREGATE_GEOMETRY_LOADED_EVENT,
models: results
});
}
useEffect(() => {
initializeViewer();
return function cleanUp() {
if (viewerRef.current) {
viewerRef.current.finish();
}
};
}, []);
return <div id="forgeViewer" ref={viewerDomRef}></div>;
}
Viewer.displayName = "Viewer";
Hi I have this implemented and I want the keys to be dynamic based on function return. Lets say I have function in controller called "returnKeyFront" which returns "F" key. I then want to take it and apply it for first element F. So instead of writing F there would be that function which returns that key. No idea how to implement that. I am using keyMap from extJS with coffescript. THX
keyMap:
F:
handler: "onCamerasHotkeyFront"
B:
handler: "onCamerasHotkeyBack"
You can add a helper function.
Something like this (I did not test it, but should be a pretty good hint.
Ext.define('MyApp.KeyChain', {
singleton: true,
/**
* holds the current keymap appwide
* #private
*/
_keymap: null,
/**
* initialize with a base defintion
*/
initKeyChain() {
const keyChain = this.definition;
this.updateKeyMap();
},
/**
* base definition. You can also set it to null
* if you want to start blank
*
* #private
*/
definition: {
CameraFront: {
key: 'f',
alt: true,
fn: 'onCamerasHotkeyFront',
scope: this // typically you want to either set the fn and/or scope
},
CameraBack: {
key: 'f',
alt: true,
fn: 'onCamerasHotkeyBack',
scope: this // typically you want to either set the fn and/or scope
},
},
/**
* changeKey
* to add or change a definition. You can also init via changeKey
* see this.defintion for examples on how keyDefinition should look like
*
* #arg {string} type e.g. 'CameraFront'
* #arg {object} keyDefinition
* #arg {string} keyDefinition.key
* #arg {boolean} [keyDefinition.alt]
* #arg {function|string} keyDefinition.fn
* #arg {scope} [keyDefinition.scope]
*/
changeKey(type, keyDefinition) {
if(!keyDefinition.scope) keyDefinition.scope = this;
if(typeof keyDefinition.fn === 'string') keyDefinition.fn = scope[definition.fn];
MyApp._definition[type] = keyDefinition;
this.updateKeyMap();
},
/**
* updateKeyMap
*
* #private
*/
updateKeyMap() {
if(this._keymap) this._keymap.destroy();
const newBinding = this.createBindings();
this._keymap = new Ext.util.KeyMap({
target: Ext.getBody(),
binding: newBindings
});
},
/**
* createBinding
*
* #private
*/
createBinding() {
const def = this.definition;
let bindings = [];
Ext.Object.each(def, function(key, value) {
bindings.push({value});
});
return bindings;
}
});
I made a very small test using protractor to open my application (build on angular) and start clicking at random links in a random order. We use this to let the web application run for several days and check for memory leaks.
After some time the memory of the browser (chrome) is huge and when i create a memory dump i find 33k strings all containing the same piece of code (shown below).
I tried searching where this was coming from and it that led me to Webdriver and the code looks like something injected by webdriver to trigger the mouse click ability but from there i'm lost.
Am i doing something wrong or should i clean the memory somewhere in my test?
(When i run the application normally for multiple hours and opening all pages there is no occurrence at all of this string)
"(function() { // Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Enum for WebDriver status codes.
* #enum {number}
*/
var StatusCode = { STALE_ELEMENT_REFERENCE: 10, JAVA_SCRIPT_ERROR: 17, };
/**
* Enum for node types.
* #enum {number}
*/
var NodeType = { ELEMENT: 1, DOCUMENT: 9, };
/**
* Dictionary key to use for holding an element ID.
* #const
* #type {string}
*/
var ELEMENT_KEY = 'ELEMENT';
/**
* True if using W3C Element references.
* #const
* #type {boolean}
*/
var w3cEnabled = false;
/**
* True if shadow dom is enabled.
* #const
* #type {boolean}
*/
var SHADOW_DOM_ENABLED = typeof ShadowRoot === 'function';
/**
* Generates a unique ID to identify an element.
* #void
* #return {string} Randomly generated ID.
*/
function generateUUID() {
var array = new Uint8Array(16);
window.crypto.getRandomValues(array);
array[6] = 0x40 | (array[6] & 0x0f);"
.... (continues for a lot more lines)
The test i try to run is:
import {browser, by, element} from 'protractor';
describe('Webclient', () => {
const MAX_SAFE_TIMEOUT = Math.pow(2, 31) - 1;
beforeAll(async function () {
await browser.get('/');
});
it('Endurance test', (done) => {
runTest();
}, MAX_SAFE_TIMEOUT);
});
let counter = 0;
async function runTest() {
try {
const elements = await element.all(by.css('.header a, .sidebar-left a, .footer a, .content a'));
let random = Math.floor(Math.random() * elements.length);
while (!(await elements[random].isDisplayed())) {
random = Math.floor(Math.random() * elements.length);
}
await elements[random].click();
console.log('click counter:', counter++, await browser.getCurrentUrl());
} catch (e) {
// Do nothing, just continue
}
// Break the chain so that we don't get one big recursive function
setTimeout(() => {
runTest();
}, 0);
}
I can't describe controllers methods. How i can do this?
/**
* #ngdoc controller
* #name works.controller:worksCtrl
* #requires $http
* #requires $element
* #function
*
* #description
* Description for works controller. All methods will be writen later
*/
var worksCtrl = function ($http, $element) {
var ctrl = this;
//how it do there? this not work
/**
* #name initializeGrid
* #function
* #description
* Description for initializeGrid
*/
ctrl.initializeGrid = function (a) {
//...
}
ctrl.getTemplate = function (workIndex) {
//...
}
//...
};
I am using ngdoc for auto generate documentation. But i can't understand what i do wrong.
I have never used ngdoc but looking to angular code itself, it looks that you need to add a #ngdoc method tag to the documentation for internal functions. For example, inside $locationProvider:
/**
* #ngdoc method
* #name $locationProvider#hashPrefix
* #description
* #param {string=} prefix Prefix for hash part (containing path and search)
* #returns {*} current value if used as getter or itself (chaining) if used as setter
*/
this.hashPrefix = function(prefix) {
if (isDefined(prefix)) {
hashPrefix = prefix;
return this;
} else {
return hashPrefix;
}
};
I hope it helps.
/**
* #ngdoc function
* #name initializeGrid
* #methodOf works.controller:worksCtrl
* #description This method initialize auto grid system for works
* #private
*/
ctrl.initializeGrid = function () {
...
}
That is what i need.)
In definition of AngularJS module, [] is a parameter for other depended module on this module.
<script>
var app = angular.module('myApp',[]);
app.controller("myCtrl", function($scope) {
$scope.firstName = "John";
$scope.lastName = "Doe";
});
</script>
My Question is,
Is this parameter [] necessary, because the the following link or example they didn't mentioned [] parameter, but in above example(w3schooles), if we remove '[]' parameter then code will not give correct output see it?
Please see the this link openstack, they are not using [] parameter
var module = angular.module('hz.dashboard.launch-instance');
/**
* #ngdoc service
* #name launchInstanceModel
*
* #description
* This is the M part in MVC design pattern for launch instance
* wizard workflow. It is responsible for providing data to the
* view of each step in launch instance workflow and collecting
* user's input from view for creation of new instance. It is
* also the center point of communication between launch instance
* UI and services API.
*/
module.factory('launchInstanceModel', ['$q',
'cinderAPI',
'glanceAPI',
'keystoneAPI',
'neutronAPI',
'novaAPI',
'novaExtensions',
'securityGroup',
'serviceCatalog',
function ($q,
cinderAPI,
glanceAPI,
keystoneAPI,
neutronAPI,
novaAPI,
novaExtensions,
securityGroup,
serviceCatalog) {
var initPromise,
allNamespacesPromise;
// Constants (const in ES6)
var NON_BOOTABLE_IMAGE_TYPES = ['aki', 'ari'],
SOURCE_TYPE_IMAGE = 'image',
SOURCE_TYPE_SNAPSHOT = 'snapshot',
SOURCE_TYPE_VOLUME = 'volume',
SOURCE_TYPE_VOLUME_SNAPSHOT = 'volume_snapshot';
/**
* #ngdoc model api object
*/
var model = {
initializing: false,
initialized: false,
/**
* #name newInstanceSpec
*
* #description
* A dictionary like object containing specification collected from user's
* input. Its required properties include:
*
* #property {String} name: The new server name.
* #property {String} source_type: The type of source
* Valid options: (image | snapshot | volume | volume_snapshot)
* #property {String} source_id: The ID of the image / volume to use.
* #property {String} flavor_id: The ID of the flavor to use.
*
* Other parameters are accepted as per the underlying novaclient:
* - https://github.com/openstack/python-novaclient/blob/master/novaclient/v2/servers.py#L417
* But may be required additional values as per nova:
* - https://github.com/openstack/horizon/blob/master/openstack_dashboard/api/rest/nova.py#L127
*
* The JS code only needs to set the values below as they are made.
* The createInstance function will map them appropriately.
*/
// see initializeNewInstanceSpec
newInstanceSpec: {},
/**
* cloud service properties, they should be READ-ONLY to all UI controllers
*/
availabilityZones: [],
flavors: [],
allowedBootSources: [],
images: [],
allowCreateVolumeFromImage: false,
arePortProfilesSupported: false,
imageSnapshots: [],
keypairs: [],
metadataDefs: {
flavor: null,
image: null,
volume: null
},
networks: [],
neutronEnabled: false,
novaLimits: {},
profiles: [],
securityGroups: [],
volumeBootable: false,
volumes: [],
volumeSnapshots: [],
/**
* api methods for UI controllers
*/
initialize: initialize,
createInstance: createInstance
};
// Local function.
function initializeNewInstanceSpec(){
model.newInstanceSpec = {
availability_zone: null,
admin_pass: null,
config_drive: false,
user_data: '', // REQUIRED Server Key. Null allowed.
disk_config: 'AUTO',
flavor: null, // REQUIRED
instance_count: 1,
key_pair: [], // REQUIRED Server Key
name: null, // REQUIRED
networks: [],
profile: {},
security_groups: [], // REQUIRED Server Key. May be empty.
source_type: null, // REQUIRED for JS logic (image | snapshot | volume | volume_snapshot)
source: [],
vol_create: false, // REQUIRED for JS logic
vol_device_name: 'vda', // May be null
vol_delete_on_terminate: false,
vol_size: 1
};
}
/**
* #ngdoc method
* #name launchInstanceModel.initialize
* #returns {promise}
*
* #description
* Send request to get all data to initialize the model.
*/
function initialize(deep) {
var deferred, promise;
// Each time opening launch instance wizard, we need to do this, or
// we can call the whole methods `reset` instead of `initialize`.
initializeNewInstanceSpec();
if (model.initializing) {
promise = initPromise;
} else if (model.initialized && !deep) {
deferred = $q.defer();
promise = deferred.promise;
deferred.resolve();
} else {
model.initializing = true;
model.allowedBootSources.length = 0;
promise = $q.all([
getImages(),
novaAPI.getAvailabilityZones().then(onGetAvailabilityZones, noop),
novaAPI.getFlavors(true, true).then(onGetFlavors, noop),
novaAPI.getKeypairs().then(onGetKeypairs, noop),
novaAPI.getLimits().then(onGetNovaLimits, noop),
securityGroup.query().then(onGetSecurityGroups, noop),
serviceCatalog.ifTypeEnabled('network').then(getNetworks, noop),
serviceCatalog.ifTypeEnabled('volume').then(getVolumes, noop)
]);
promise.then(
function() {
model.initializing = false;
model.initialized = true;
// This provides supplemental data non-critical to launching
// an instance. Therefore we load it only if the critical data
// all loads successfully.
getMetadataDefinitions();
},
function () {
model.initializing = false;
model.initialized = false;
}
);
}
return promise;
}
/**
* #ngdoc method
* #name launchInstanceModel.createInstance
* #returns {promise}
*
* #description
* Send request for creating server.
*/
function createInstance() {
var finalSpec = angular.copy(model.newInstanceSpec);
cleanNullProperties();
setFinalSpecBootsource(finalSpec);
setFinalSpecFlavor(finalSpec);
setFinalSpecNetworks(finalSpec);
setFinalSpecKeyPairs(finalSpec);
setFinalSpecSecurityGroups(finalSpec);
return novaAPI.createServer(finalSpec);
}
function cleanNullProperties(finalSpec){
// Initially clean fields that don't have any value.
for (var key in finalSpec) {
if (finalSpec.hasOwnProperty(key) && finalSpec[key] === null) {
delete finalSpec[key];
}
}
}
//
// Local
//
function onGetAvailabilityZones(data) {
model.availabilityZones.length = 0;
push.apply(model.availabilityZones, data.data.items
.filter(function (zone) {
return zone.zoneState && zone.zoneState.available;
})
.map(function (zone) {
return zone.zoneName;
})
);
if(model.availabilityZones.length > 0) {
model.newInstanceSpec.availability_zone = model.availabilityZones[0];
}
}
// Flavors
function onGetFlavors(data) {
model.flavors.length = 0;
push.apply(model.flavors, data.data.items);
}
function setFinalSpecFlavor(finalSpec) {
if ( finalSpec.flavor ) {
finalSpec.flavor_id = finalSpec.flavor.id;
} else {
delete finalSpec.flavor_id;
}
delete finalSpec.flavor;
}
// Keypairs
function onGetKeypairs(data) {
angular.extend(
model.keypairs,
data.data.items.map(function (e) {
e.keypair.id = e.keypair.name;
return e.keypair;
}));
}
function setFinalSpecKeyPairs(finalSpec) {
// Nova only wants the key name. It is a required field, even if None.
if(!finalSpec.key_name && finalSpec.key_pair.length === 1){
finalSpec.key_name = finalSpec.key_pair[0].name;
} else if (!finalSpec.key_name) {
finalSpec.key_name = null;
}
delete finalSpec.key_pair;
}
// Security Groups
function onGetSecurityGroups(data) {
model.securityGroups.length = 0;
push.apply(model.securityGroups, data.data.items);
// set initial default
if (model.newInstanceSpec.security_groups.length === 0 &&
model.securityGroups.length > 0) {
model.securityGroups.forEach(function (securityGroup) {
if (securityGroup.name === 'default') {
model.newInstanceSpec.security_groups.push(securityGroup);
}
});
}
}
function setFinalSpecSecurityGroups(finalSpec) {
// pull out the ids from the security groups objects
var security_group_ids = [];
finalSpec.security_groups.forEach(function(securityGroup){
if(model.neutronEnabled) {
security_group_ids.push(securityGroup.id);
} else {
security_group_ids.push(securityGroup.name);
}
});
finalSpec.security_groups = security_group_ids;
}
// Networks
function getNetworks() {
return neutronAPI.getNetworks().then(onGetNetworks, noop);
}
function onGetNetworks(data) {
model.neutronEnabled = true;
model.networks.length = 0;
push.apply(model.networks, data.data.items);
}
function setFinalSpecNetworks(finalSpec) {
finalSpec.nics = [];
finalSpec.networks.forEach(function (network) {
finalSpec.nics.push(
{
"net-id": network.id,
"v4-fixed-ip": ""
});
});
delete finalSpec.networks;
}
// Boot Source
function getImages(){
return glanceAPI.getImages({status:'active'}).then(onGetImages);
}
function isBootableImageType(image){
// This is a blacklist of images that can not be booted.
// If the image container type is in the blacklist
// The evaluation will result in a 0 or greater index.
return NON_BOOTABLE_IMAGE_TYPES.indexOf(image.container_format) < 0;
}
function onGetImages(data) {
model.images.length = 0;
push.apply(model.images, data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(!image.properties || image.properties.image_type !== 'snapshot');
}));
addAllowedBootSource(model.images, SOURCE_TYPE_IMAGE, gettext('Image'));
model.imageSnapshots.length = 0;
push.apply(model.imageSnapshots,data.data.items.filter(function (image) {
return isBootableImageType(image) &&
(image.properties && image.properties.image_type === 'snapshot');
}));
addAllowedBootSource(model.imageSnapshots, SOURCE_TYPE_SNAPSHOT, gettext('Instance Snapshot'));
}
function getVolumes(){
var volumePromises = [];
// Need to check if Volume service is enabled before getting volumes
model.volumeBootable = true;
addAllowedBootSource(model.volumes, SOURCE_TYPE_VOLUME, gettext('Volume'));
addAllowedBootSource(model.volumeSnapshots, SOURCE_TYPE_VOLUME_SNAPSHOT, gettext('Volume Snapshot'));
volumePromises.push(cinderAPI.getVolumes({ status: 'available', bootable: 1 }).then(onGetVolumes));
volumePromises.push(cinderAPI.getVolumeSnapshots({ status: 'available' }).then(onGetVolumeSnapshots));
// Can only boot image to volume if the Nova extension is enabled.
novaExtensions.ifNameEnabled('BlockDeviceMappingV2Boot')
.then(function(){ model.allowCreateVolumeFromImage = true; });
return $q.all(volumePromises);
}
function onGetVolumes(data) {
model.volumes.length = 0;
push.apply(model.volumes, data.data.items);
}
function onGetVolumeSnapshots(data) {
model.volumeSnapshots.length = 0;
push.apply(model.volumeSnapshots, data.data.items);
}
function addAllowedBootSource(rawTypes, type, label) {
if (rawTypes && rawTypes.length > 0) {
model.allowedBootSources.push({
type: type,
label: label
});
}
}
function setFinalSpecBootsource(finalSpec) {
finalSpec.source_id = finalSpec.source && finalSpec.source[0] && finalSpec.source[0].id;
delete finalSpec.source;
switch (finalSpec.source_type.type) {
case SOURCE_TYPE_IMAGE:
setFinalSpecBootImageToVolume(finalSpec);
break;
case SOURCE_TYPE_SNAPSHOT:
break;
case SOURCE_TYPE_VOLUME:
setFinalSpecBootFromVolumeDevice(finalSpec, 'vol');
break;
case SOURCE_TYPE_VOLUME_SNAPSHOT:
setFinalSpecBootFromVolumeDevice(finalSpec, 'snap');
break;
default:
// error condition
console.log("Unknown source type: " + finalSpec.source_type);
}
// The following are all fields gathered into simple fields by
// steps so that the view can simply bind to simple model attributes
// that are then transformed a single time to Nova's expectation
// at launch time.
delete finalSpec.source_type;
delete finalSpec.vol_create;
delete finalSpec.vol_device_name;
delete finalSpec.vol_delete_on_terminate;
delete finalSpec.vol_size;
}
function setFinalSpecBootImageToVolume(finalSpec){
if(finalSpec.vol_create) {
// Specify null to get Autoselection (not empty string)
var device_name = finalSpec.vol_device_name ? finalSpec.vol_device_name : null;
finalSpec.block_device_mapping_v2 = [];
finalSpec.block_device_mapping_v2.push(
{
'device_name': device_name,
'source_type': SOURCE_TYPE_IMAGE,
'destination_type': SOURCE_TYPE_VOLUME,
'delete_on_termination': finalSpec.vol_delete_on_terminate ? 1 : 0,
'uuid': finalSpec.source_id,
'boot_index': '0',
'volume_size': finalSpec.vol_size
}
);
}
}
function setFinalSpecBootFromVolumeDevice(finalSpec, sourceType) {
finalSpec.block_device_mapping = {};
finalSpec.block_device_mapping[finalSpec.vol_device_name] = [
finalSpec.source_id,
':',
sourceType,
'::',
(finalSpec.vol_delete_on_terminate ? 1 : 0)
].join('');
// Source ID must be empty for API
finalSpec.source_id = '';
}
// Nova Limits
function onGetNovaLimits(data) {
angular.extend(model.novaLimits, data.data);
}
// Metadata Definitions
/**
* Metadata definitions provide supplemental information in detail
* rows and should not slow down any of the other load processes.
* All code should be written to treat metadata definitions as
* optional, because they are never guaranteed to exist.
*/
function getMetadataDefinitions() {
// Metadata definitions often apply to multiple
// resource types. It is optimal to make a single
// request for all desired resource types.
var resourceTypes = {
flavor: 'OS::Nova::Flavor',
image: 'OS::Glance::Image',
volume: 'OS::Cinder::Volumes'
};
angular.forEach(resourceTypes, function (resourceType, key) {
glanceAPI.getNamespaces({
'resource_type': resourceType
}, true)
.then(function (data) {
var namespaces = data.data.items;
// This will ensure that the metaDefs model object remains
// unchanged until metadefs are fully loaded. Otherwise,
// partial results are loaded and can result in some odd
// display behavior.
if(namespaces.length) {
model.metadataDefs[key] = namespaces;
}
});
});
}
return model;
}
]);
})();
Status
The array or [] parameter is needed to specify dependent modules when you declare your own module, so this should only happen once per module.
The second notation, without the parameter is just retrieving the module so you can attach controllers/services/filters/... to it.
Use the array notation for the declaration of your module, use the single parameter notation if you want to add something to it.
For example:
in app.module.js
//You want to make use of the ngRoute module,
//so you have to specify a dependency on it
angular.module('app', ['ngRoute']);
You will only specify the dependencies on your module once, when you declare it.
in main.controller.js
//You want to add a controller to your module, so you want to retrieve your module
angular.module('app').controller('mainCtrl', mainCtrl);
function mainCtrl() { };
Now angular will try to find a module by that name instead of creating one, when it doesn't find one, you'll get some errors, which explains your original question.
You will typically do this every time you want to add something to your module.
Note that you could also achieve this by storing your module in a global variable when you create it and then access the module by that variable when you want to add things to it, however as you probably know, creating global variables is a bad practice.
Facing an error with angular is a bliss because it provides the link to description of the error in the console.
From an example page like that...
When defining a module with no module dependencies, the array of dependencies should be defined and empty.
var myApp = angular.module('myApp', []);
To retrieve a reference to the same module for further configuration, call angular.module without the array argument.
var myApp = angular.module('myApp');