angular2-c360
v0.3.0
Published
Angular 2 services and components for working with Configurator 360
Downloads
14
Readme
angular2-c360
Angular2 components and services for interacting with Configurator 360.
By using C360ContextService to load a Configurator 360 model (either a new model or a saved model), you are given a javascript object representing the root part from your model. This object contains a property for each child part, a property for each model property, and a function for each action (e.g. downloading drawings). Each of the child parts contains all of this functionality as well -- all the way down the hierarchy. This allows you to interact with your entire C360 model on the client side using javascript.
Once the C360 model has been retrieved, the client-side model is automatically kept in sync with the server-side model. When a UIProperty is updated on the client, the change is automatically sent to the server, and any resultant changes (dependent properties, addition/removal of children, etc.) are returned and incorporated into the client-side model.
NOTE: An Angular 1 version of this library also exists and can be found here
Table of Contents
- Demo
- Installation
- Common Usage
- Advanced Usage
- Inspecting Client-Side Model
- C360 Errors
- Versioning
- Authors
- License
- API
Demo
A live sample application can be found here. The source code for this sample is in its own repository, along with instructions for running the sample locally.
Installation
Install library
Add the library to your project using npm:
npm install angular2-c360 --save
Add C360 script from Autodesk to your index.html
<script src="https://configurator360.autodesk.com/Script/v1/EmbeddedViewer"></script>
Set design key
In order for angular2-c360 to know which design to use, we have to configure C360ContextService with the appropriate designKey when the application is bootstrapped. The design key is the unique identifier for your design within C360 (i.e. all text to the right of the "https://configurator360.autodesk.com/" text in your C360 design URL). This is accomplished by passing an instance of C360ContextServiceConfig to AngularC360Module.forRoot() when AngularC360Module is imported into the application's root module. More details can be found in the official documentation for Angular Modules.
Here is an example:
@NgModule({
imports: [
BrowserModule,
AngularC360Module.forRoot({designKey: "575458448649916390/2gn1dj1tslb4"})
],
declarations: [
AppComponent
]
bootstrap: [ AppComponent ]
})
export class AppModule { }
Common Usage
Loading Model
- In the component in which you will use the C360 model, inject
C360ContextService
. - Call
C360ContextService.getNewModel()
, which returns a promise containing the root part. - In order to prevent reloading the model every time the component is activated,
C360ContextService.isModelLoaded()
can be evaluated first
Example:
import { Component, OnInit } from '@angular/core';
import { C360ContextService, UIPart } from 'angular2-c360';
@Component({
selector: 'app-model-simple',
templateUrl: './model-simple.component.html',
styleUrls: ['./model-simple.component.css']
})
export class ModelSimpleComponent implements OnInit {
constructor(private c360Context: C360ContextService) { }
public rootPart: UIPart;
ngOnInit() {
if (this.c360Context.isModelLoaded()) {
this.rootPart = this.c360Context.getRoot();
}
else {
this.c360Context.getNewModel()
.then(root => {
this.rootPart = root
});
}
}
}
See the C360 Errors section below for details on error handling.
Creating Inputs for Properties
Bindings
Each UIProperty has several properties containing metadata from the C360 model that reflect how the property should behave in the UI. By binding your HTML elements to these properties, your component can automatically update itself using logic driven by your C360 model.
Input
Here is an example of binding various attributes of an <input>
element to a UIProperty:
<input #i (blur)="rootPart.SomeProperty.value=i.value" [value]="rootPart.SomeProperty.value" placeholder="{{rootPart.SomeProperty.fullName}}"
[class.c360-modified]="rootPart.SomeProperty.isModified" [class.c360-invalid]="rootPart.SomeProperty.errorInfo"
[disabled]="rootPart.SomeProperty.isReadOnly" [type]="rootPart.SomeProperty.inputType" />
Note: In order to prevent updating the C360 model on every keystroke, the blur
attribute was used, along with a template reference variable
rather than the standard [(ngModel)]
method of two-way data-binding.
Select
Here is an example of binding various attributes of a <select>
element to a UIProperty:
<select [(ngModel)]="rootPart.SomeProperty.value">
<option *ngFor="let choice of rootPart.SomeProperty.choiceList" [value]="choice.value">
{{choice.text}}
</option>
</select>
Creating Reusable Component
Although the above syntax is relatively straightforward, it is pretty verbose. Using this syntax for every property in your application will quickly clutter your application and make it difficult to maintain. The solution is to create a custom Angular component that can then be used throughout your application.
An example of this is the <c360-prop>
component, which
can be found in the angular2-c360-sample repository. The <c360-prop>
component
accepts a UIProperty as an input, and it dynamically renders an <input>
or <select>
bound to that property -- depending on how the property
is defined in the C360 model.
Here is an example using the <c360-prop>
component to display all properties for a part:
<div *ngIf="part">
<c360-prop *ngFor="let prop of part.uiProperties" [uiProp]="prop"></c360-prop>
</div>
Note: The <c360-prop>
mentioned above has a dependency
on Bootstrap 4.
Future Plans
Ideally, it wouldn't require fully implementing a custom component in order to encapsulate the template to be used for UIProperty objects
in your application. In the Angular 1 version of this library, an implementation of <c360-prop>
is provided
as part of the library. It is then possible to override just the template for the directive (via configuration) without having to implement a custom directive.
Angular 2 does not currently support this, but there is already an issue requesting this functionality, so hopefully this will be possible at some point.
Executing Actions (e.g. downloading drawings)
As mentioned above, all actions defined on a given part in your C360 model are available in the client-side model as functions on that part. Executing an action is as simple as calling one of those functions.
The simplest way to do this is to bind the action to the click
event of a button:
<button (click)="rootPart.CreateDrawingDWG()">Download DWG</button>
Graphics
Adding the graphics viewer is as simple as adding a viewer element:
<c360-viewer></c360-viewer>
There is no interactibility with the actual viewer, it's just plug and play.
Advanced Usage
Get Part By Refchain
Assuming c360Context
is an object of type C360ContextService
, the following can be used to retrieve a specific UIPart
from the client-side model:
let part: UIPart = c360Context.getPartByRefChain('Root.Foo.Bar');
This returns the UIPart at the given refChain, in this case, the UIPart Bar
which is a child of Foo
Interacting With Model in Typescript/Javascript
Once you have a reference to a part within your component (see above for how to get root part and/or get a specific part by Refchain), you can evaluate/set properties and execute actions on parts anywhere in the model hierarchy.
Evaluating / Setting Properties
Assuming you already have a variable named rootPart
that references the root part of your model, the following code will evaluate (and potentially set) a property on a child part:
if (rootPart.SomeChild.SomeGrandchild.PartNumber.value !== null) {
alert('The part number is ' + rootPart.SomeChild.SomeGrandchild.PartNumber.value);
} else {
rootPart.SomeChild.SomeGrandchild.PartNumber.value = 'PT-15';
}
In the above example, the code sets the PartNumber property if it is not already set. However, since this is an asynchronous call to the server (which returns a Promise
), any code after the property is set will execute immediately rather than waiting on the update to finish. In order to add code after the asynchronous call, the following approach can be used (assuming c360Context
is an object of type C360ContextService
):
c360Context.updateProperty('Root.SomeChild.SomeGrandchild', 'uiPartNumber', 'PT-15')
.then(function() {
alert('The PartNumber property was successfully set');
})
.catch(function() {
alert('An error occurred while setting the PartNumber property');
});
Evalating other attributes of the C360 property object
There are many more properties on the object created from the UIProperty, including choiceList, dataType, uiRuleName and more. For more, you can reference the ui-property.ts file.
Executing Actions
Actions appear as functions on the root part and all of its children, all the way down the hierarchy, so they can be called just like calling any existing javascript function.
Here is an example of executing an action within a component:
import { Component, OnInit } from '@angular/core';
import { C360ContextService, UIPart } from 'angular2-c360';
@Component({
selector: 'app-model-simple',
templateUrl: './model-simple.component.html',
styleUrls: ['./model-simple.component.css']
})
export class ModelSimpleComponent implements OnInit {
constructor(private c360Context: C360ContextService) { }
public rootPart: any;
downloadDrawings() {
this.rootPart.CreateDrawingDWG()
.then(() => {
// The action returns a promise, so put any logic here
// that you would like to execute after the action completes
});
}
ngOnInit() {
if (this.c360Context.isModelLoaded()) {
this.rootPart = this.c360Context.getRoot();
}
else {
this.c360Context.getNewModel()
.then(root => {
this.rootPart = root
});
}
}
}
See the C360 Errors section below for details on error handling.
Custom Model Adapter
When the client-side model is updated after each call to the server, we have the ability to affect how the model is created by using a model adapter object. The default model adapter can be found in model-adapter.ts.
By creating a custom model adapter, we can override the logic used for the following:
- Replacing invalid characters in property names
- The base name for a property in a C360 model can contain characters that are not valid in property names in javascript
- For example, a property in C360 might be named "Scrap %". Neither the space nor the % can be used in a javascript property name, so they need to be replaced when the client-side model is created.
- By default, an empty string will be used as the replacement
- Executing custom javascript for every part that is returned from the server
- One example would be to log some information about each part
- Another example is to actually modify some of the parts in some way in order to facilitate some special logic in the UI
Here is an example of a custom model adapter that overrides visitPart
to log the name of each UIPart
that has been updated:
import { ModelAdapter, UIPart } from 'angular2-c360';
export class LoggingModelAdapter extends ModelAdapter {
visitPart(part: UIPart) {
console.log(part.refChain);
}
}
In order to use this custom model adapter, you simply pass an instance of it into AngularC360Module.forRoot()
, along with the C360ContextServiceConfig
object
that was mentioned above:
@NgModule({
imports: [
BrowserModule,
AngularC360Module.forRoot({designKey: "575458448649916390/2gn1dj1tslb4"}, new LoggingModelAdapter())
],
declarations: [
AppComponent
]
bootstrap: [ AppComponent ]
})
export class AppModule { }
Accessing the C360 viewer object directly
With angular2-c360, we have exposed and streamlined a set of functions we think should give you the ability to do almost everything needed in typical usage scenarios. All of this functionality exists in C360ContextService
. For anything else, you can directly retrieve the viewer object (C360ContextService.getViewer()
), and you'll have complete access to the functionality provided by C360.
Autodesk documentation for C360 Viewer
Inspecting Client-Side Model
Once the client-side model has been created by C360ContextService
, it is pretty handy to be able to inspect the model object. This makes it easier to see what properties, children, and actions are available on each part without having to refer back to the C360 designs.
The easiest way to do this is by using the Angular Augury extension for Google Chrome. Once this extension is installed, you are able to see
a visual representation of the component tree for your application. As you click on each component, you are then able to view details about the component, as well as interact
with the component from the console (it is available as $a
from the console). So, once you've clicked on a component that injects C360ContextService
, executing the following
statement from the console will return the root UIPart
from the client-side model:
$a.componentInstance.c360Context.rootPart
C360 Errors
Resolving Error Name From Error Code
If an error occurs while loading the model, the error code will be passed in to the catch
method of the promise. The error code can then be
compared to the ADSK.C360.loadedState
enumeration to
determine the type of error.
Common Errors
ADSK.C360.loadedState.Forbidden (403)
This error occurs when the URL for your site has not been added under the Allow these authorized sites to embed configuration pages of my designs page under Options/Embedding for the C360 account that owns the design your application is configured to use.
This will always occur if you are using localhost in your URL. If your application is running on your local machine, you will need to use http://127.0.0.1 rather than http://localhost. You will also still need to add http://127.0.0.1 as an authorized site.
ADSK.C360.loadedState.DesignOpenInOtherWindowOrTab (12)
This error occurs when you attempt to load a C360 design in a browser tab while you already have the same design opened in another tab of the same browser. Due to the way C360 handles sessions, using a different browser altogether is the only way to use multiple instances of the same design simultaneously.
Versioning
We use SemVer for versioning. For the versions available, see the tags on this repository.
Authors
See also the list of contributors who participated in this project.
License
This project is licensed under the MIT License - see the LICENSE file for details