15 Feb 2015

An AngularJs directive for the View & Data API Viewer

By Philippe Leefsma

Far from being an expert in the topic, I've been playing with AngularJs for a while and I'm pretty much a huge fan of it... Today's post focuses on creating an angular directive to easily use the View & Data viewer in your angular-powered web pages.

Let's start by defining what is an angular directive:

"At a high level, directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.

Angular comes with a set of these directives built-in, like ngBindngModel, and ngClass. Much like you create controllers and services, you can create your own directives for Angular to use. When Angular bootstraps your application, the HTML compiler traverses the DOM matching directives against the DOM elements."

Basically a directive is a piece of JavaScript code loaded by your angular application that allows to create new html tags or extend the behavior of existing ones. I am not going to expose directive concepts in details, there is already a lot of very documentation on the web, so if you are not familiar with it I strongly recommend you take a look at the following reference before reading any further:

Creating Custom Directives

Understanding Directives

Let's start with a simple yet cool directive: let's suppose I want to insert in my webpage a spinning image animated using a css transform. I could use an <img> tag and add some JavaScript code to access the img element which will update the rotation value. That will of course work but if I want to insert 2 or more spinning images, I have to write code accordingly. Applying the same concept to multiple elements may end up in oa lot of fuzzy code all over the place.

A much slicker approach is to write a directive that will handle the spinning logic, so with a simple html tag like below:

 1 <!-- A spinning image using adn-spinning-img directive-->
 2 <a class="navbar-brand" 
 3    href="http://www.autodesk.com"
 4     target="_blank">
 5     <adn-spinning-img
 6         step="5.0"
 7         period="100"
 8         height="256"
 9         width="256"
10         src="img.jpg"/>
11 </a>

You end up with that result:



It's actually pretty easy to write such a directive: all you need is setting up a timer in the "link" function that will update the element (ie our image) transform property:

 1 app.directive('adnSpinningImg',
 2         ['$interval', function($interval) {
 4     function link($scope, $element, $attributes) {
 6         var angle = 0.0;
 8         function update() {
10             angle += parseFloat($attributes.step);
12             angle = angle % 360;
14             var value = "rotateY(" + angle + "deg)";
16             jQuery($element).css({
17                 "transform": value,
18                 "-moz-transform": value,
19                 "-webkit-transform": value,
20                 "-ms-transform": value,
21                 "-o-transform": value
22             });
23         }
25         var timerId = $interval(function() {
26             update();
27         }, parseInt($attributes.period));
28     }
30     return {
31         restrict: 'E',
32         replace: true,
33         template: '<img height={{height}}width={{width}}'
34             + 'src={{src}}style={{style}}>',
35         link: link
36     }
37 }]);

If you find that cool, hang on because the best is yet to come...

How about writing a directive that would allow to easily embed the View & Data viewer in an html page: instead of using a simple div tag and writing a bunch of code that will access the div and render the graphics in it, we could write the following html and have the viewer render the specified model in urn in that area, without worrying about writing any code:

1 <!-- basic adn-viewer-div -->
2 <adn-viewer-div style="width: 300px; height: 300px;"
3     url="<replace with generated token or token url>"
4     urn="<replace with document URN>">
5 </adn-viewer-div>

Yes that would be great! ... but we are developers so we want to customise the behavior of the viewer using it's JavaScript API. Since there is just an html tag, how do I access the viewer object? Well, one elegant solution would be to use on of the tag attribute to specify a callback. That way you could write a method in your controller that will be called once the viewer has been initialized.

Here is how your html could look like:

1 <!-- adn-viewer-div with initialized callback-->
2 <adn-viewer-div style="width: 300px; height: 300px;"
3     id="viewer"
4     url="{{tokenUrl}}"
5     urn="{{docUrn}}"
6     viewer-initialized="onViewerInitialized(viewer)">
8 </adn-viewer-div>

And the controller implementation as follow:

 1 var app = angular.module('Autodesk.ADN.Demo.App', []);
 3 app.controller('Autodesk.ADN.Demo.Controller', 
 4         ['$scope', function ($scope) {
 6     $scope.tokenUrl = '<replace with token or token url>';
 8     $scope.onViewerInitialized = function(viewer) {
10         viewer.addEventListener(
11             Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
12             function (event) {
13                     console.log('Geometry Loaded...');
14             });
15     }
17     $scope.load = function() {
18         $scope.docUrn =
19             "<replace with document URN>";
20     }
22     $scope.close = function() {
23         $scope.docUrn = "";
24     }
26 }]);

Pretty slick! And very angular-ish :)

Below is the implementation of my adn-viewer-div directive. It's not very complicated, however there are several directive concepts that you need to know in order to understand the details. I would suggest first that you take a look at that very concise article if you are not an angular directive expert. Also to shorten the code, the directive is using my wrapper API available there: View and Data API JavaScript Wrapper Library

 1 app.directive('adnViewerDiv', function () {
 3     function link($scope, $element, $attributes) {
 5         // instanciate viewer manager in directive scope
 7         $scope.adnViewerMng =
 8          new Autodesk.ADN.Toolkit.Viewer.AdnViewerManager(
 9             $attributes.url,
10             $element[0],
11             ($attributes.hasOwnProperty('environment') ?
12                 $attributes.environment :
13                 'AutodeskProduction'));
15         $attributes.$observe('urn', function(urn) {
17             // check if urn is not empty
18             // if empty close doc
19             if(urn.length) {
21                 // loads document from urn
22                 $scope.adnViewerMng.loadDocument(
23                         urn,
24                         function (viewer) {
25                             $scope.viewerInitialized({
26                                 viewer: viewer
27                             })
28                         });
29             }
30             else {
31                 $scope.adnViewerMng.closeDocument();
32             }
33         });
34     }
36     return {
37         scope: {
38             url: '@',
39             urn: '@',
40             viewerInitialized: '&'
41         },
42         restrict: 'E',
43         replace: true,
44         template: '<div style="overflow: hidden;' +
45          ' position: relative; {{style}}"><div/>',
46         link: link
47     };
48 });

Here is the complete code for that sample, also attached as a zip:

Finally a side note: the url expected by the directive is either a token string generated using your API credentials or more useful the url of your token server which should return a json response formatted as follow:

1 {
2     token_type: "Bearer",
3     expires_in: 1799,
4     access_token: "9PWqbIZyAXh5DIVKKCj9XaCdYiy4"
5 }

Download Directive-demo


Related Article

Posted By

Philippe Leefsma

I write JavaScript for the future 3D World Wide Web, A.K.A Forge Platform