Check out our new training course on AngularJS for Flex Developers

Using Flex and HTML together

I wrote an article for this month's newsletter that harken back to our past as Flex developers.

I was recently contacted by a developer who wanted to use the Flex iFrame component to open up a Google Maps link. This was an experienced developer, but one that is new to Flex. He didn't know where to start. I gave him some input, and thought I'd do a write up about it.

What is Flex iFrame?

The Flex iFrame component is a component that uses an iFrame to open up an HTML page over a SWF. This makes it appear as if the HTML portion of the app was part of the Flex app. It is a kludgy way to get HTML and Flex talking to each other, but is functional for certain use cases.

Get the Component Code

The first step is to get the code, which you can do from this GitHub repository. Drill down into the repository to get at the component's code . The code is in two classes, the IFrame class and the IFrameExternalCalls class.

To use this code, you have two options. The easiest is to copy the two classes into your project. The second is to create your own library project and a SWC of the component. The latter is preferred for reusability purposes, but for the sake of this sample we'll use the first option and keep it simple.

Unfortunately, Flash Builder had trouble with the class names, IFrame and IFrameExternalCalls. I believe this is because the 'I' at the front of a class name is convention that denotes the class as an interface, not a real class. As such I had to rename the two classes for them to be usable. I chose FlexIFrame and FlexIFrameExternalCalls. The two classes have references to each other, and each one will have to be modified to reference their newly named counterpart. If the Flash Builder refactor function doesn't perform the code changes for you, a quick search and replace will.

Get your Google Maps URL

Before opening up a URL in the app you have to determine what URL you want to open. Since my new developer friend wanted to open something in Google Maps, the first thing is to get a Google Maps API Key. This is a simple form for you to fill out.

Next you need to create a URL. There are a bunch of different options for creating Google Maps URLs. I am going to bring up a map that points to the Water Park I'd like to be at today. This is the URL:

https://www.google.com/maps/embed/v1/place?key=yourKeyHere=Lake+Compounce+Bristol+CT

With the URL in hand, we can go into the code.

Using the Flex IFrame

This is the code that will open up load a Flex app with an iFrame. It is a simple MXML application, and only uses the Flex iFrame component :


<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:flexiframe="com.google.code.flexiframe.*"
width="100%" height="100%" >

<fx:Script>
<![CDATA[
[Bindable]
public var iFrameURL :String = "https://www.google.com/maps/embed/v1/place?key=YourKey&q=Lake+Compounce+Bristol+CT";
]]>

</fx:Script>
<flexiframe:FlexIFrame id="iFrameBySource"
width="100%" height="100%"
source="{this.iFrameURL}"
/>

</s:Application>

The source of the IFrame component refers to the Google Maps URL.

See this app run here, or view the full source

Mixing Flex and HTML

The previous example just used an iFrame component. However, the iFrame can exist and be placed around other Flex elements; making the iFrame HTML content look as if it were part of the Flex application. This sample will show you how to hide or display the Flex iFrame with the click of a button:

This is the code:


<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:flexiframe="com.google.code.flexiframe.*"
width="100%" height="100%">

<fx:Script>
<![CDATA[
[Bindable]
public var iFrameURL :String = "https://www.google.com/maps/embed/v1/place?key=yourkey&q=Lake+Compounce+Bristol+CT";
protected function button1_clickHandler(event:MouseEvent):void
{
iFrameBySource.visible = !iFrameBySource.visible;
}
]]>

</fx:Script>
<mx:VBox width="100%" height="100%">
<s:Button click="button1_clickHandler(event)" label="Toggle Google Map" />
<flexiframe:FlexIFrame id="iFrameBySource"
width="100%"
height="100%"
source="{this.iFrameURL}" visible="false"
/>

</mx:VBox>
</s:Application>

The map is put in a VBox, which includes a button above the iFrame. The iFrame component's visibility is set to false by default. On the click of the button, the iFrame's visibility is toggled, either displaying or hiding the component.

This is the default load of the app, with the map hidden:

This is the app with the map displayed, after clicking the button:

See this app run here, or view the full source

Final Thoughts

Sometimes this Flex stuff feels like a walk down memory lane, but half of my consulting work is still in the ActionScript and Flex realm. I feel lucky to be able to balance my time between my old world and the new world.

What have you been up to?

Sign up for DotComIt's Monthly Technical Newsletter

Setting up my Dev Environment with ColdFusion 10, ColdFusion 11, and Apache Web Server

One of the first articles I wrote for the ColdFusion Developer's Journal was about setting up IIS to run on ColdFusion 5, ColdFusion MX, and BlueDragon. This was an article I co-wrote with my friend Charlie Arehart and it was published back in 2003. My intent was to write code once, and be able to test it on different application servers. This worked pretty well.

Nowdays I primarily use Apache Web Server for development purposes instead of IIS. ColdFusion has increased multiple version numbers. The need from the original article still exists. I currently do work on sites that use both ColdFusion 10 and ColdFusion 11. It'd be nice to be able to run both versions of ColdFusion side by side.

This article details how I set up my machine.

Installing ColdFusion

I already have CF10 installed on my machine, but need CF11. My first step was to download the ColdFusion 11 Developer Edition from the Adobe web site and follow the normal install instructions. You could install the EAR/WAR version of CF11 and deploy that using the CF10 instance manager. This is like running CF11 inside CF10. However, I decided to install CF11 in the server configuration mode; similar to how CF10 was previously installed.

During the install, you are given the option to configure your web server. Create a copy of your Apache Web Server conf directory and have the CF11 installer configure that copy. Then you can compare the two configurations to see what has changed. I was hoping that I would be able to make use of virtual servers, attach CF10 to some virtual servers, and CF11 to others. Unfortunately that is not possible.

The Problem

ColdFusion 10 and 11 both use Tomcat under the hood; a change since they have been using JRUN since CFMX when I wrote my initial article. In Apache, the JkShmFile directive is used to associate Apache with Tomcat servlet container. Unfortunately, only one JkShmFile directive can be in an Apache Web Server config; making it impossible to point different virtual servers in the same Apache instance to different versions of ColdFusion.

My Solution

I decided that I could use the same Apache installation with completely different configurations, one pointed at CF10 and one pointed at CF11. I wouldn't be able to run them at the same time unless I gave them different web server ports, but I would be able to do testing against different versions of ColdFusion. I decided this was suitable for my current needs.

This is how to run Apache from the command line and specify your custom config directory

[Apache Install Directory]\bin\httpd -k start -f "[Config Directory\httpd.conf"

The K parameter is used to start, restart, or stop the Apache Web Server depending of the value of the parameter. The f parameter specifies the location of the config file. The f parameter is key to launching Apache with different configs.

I did some testing with this and determined it was working great. But, I wanted to set it up as a service so it would show up in the control panel on my Windows machine. This command did that:

httpd -k install -n "MyServiceName" -f "[Config Directory\httpd.conf \httpd.conf"

Final Thoughts

Although I didn't take it this far, I could have shared a lot of configuration variables between the two servers through the use of Apache includes. That could be the topic of another article, though.

Sign up for DotComIt's Monthly Technical Newsletter

Why won't my AdvancedDataGrid sort correctly?

I was having some problems with sorting the Flex AdvancedDataGrid, and went into details of them in DotComIt's newsletter. I thought I'd share them here.

The Problem

I was creating a AdvancedDataGrid in a client project. The dataProvider for the grid had nested objects whose properties were being displayed in the column with an itemRenderer. I was also using a custom itemRenderer that would introspect the objects based on the dataField of the object. The combination of these things made two different columns always perform the same sort, even though my code told them to use a different sortCompareFunction.

This will make more sense with a sample. Let's start with the dataProvider:


[Bindable]
public var collection :ArrayCollection = new ArrayCollection([
{dummyValue:'',user:{firstName:'Arvid', lastName:'Zimmer'}},
{dummyValue:'',user:{firstName:'Jeffry', lastName:'Houser'}},
{dummyValue:'',user:{firstName:'Tom', lastName:'Amilton'}},
{dummyValue:'',user:{firstName:'Kyle', lastName:'Wellington'}},
{dummyValue:'',user:{firstName:'Zipper', lastName:'Stretch'}},
]);

Each property in the ArrayCollection has two values: a dummyValue which is a placeholder and a user value which is an object. I want the DataGrid to display the firstName and lastName in columns. This will do it:


<mx:AdvancedDataGrid dataProvider="{this.collection}"
sortExpertMode="true">

<mx:columns>
<mx:AdvancedDataGridColumn visible="false" />
<mx:AdvancedDataGridColumn headerText="first Name" sortable="true" dataField="user" sortCompareFunction="sortFirstName">
<mx:itemRenderer>
<fx:Component>
<s:MXAdvancedDataGridItemRenderer dataChange="itemrenderer1_dataChangeHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function itemrenderer1_dataChangeHandler(event:FlexEvent):void
{
if(!data){
return;
}
labelDisplay.text = data[this.advancedDataGridListData.dataField].firstName;
}
]]>

</fx:Script>
<s:Label id="labelDisplay" />
</s:MXAdvancedDataGridItemRenderer>
</fx:Component>
</mx:itemRenderer>
</mx:AdvancedDataGridColumn>
<mx:AdvancedDataGridColumn headerText="last Name" sortable="true" dataField="user" sortCompareFunction="sortLastName">
<mx:itemRenderer>
<fx:Component>
<s:MXAdvancedDataGridItemRenderer dataChange="itemrenderer1_dataChangeHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function itemrenderer1_dataChangeHandler(event:FlexEvent):void
{
if(!data){
return;
}
labelDisplay.text = data[this.advancedDataGridListData.dataField].lastName;
}
]]>

</fx:Script>
<s:Label id="labelDisplay" />
</s:MXAdvancedDataGridItemRenderer>
</fx:Component>
</mx:itemRenderer>
</mx:AdvancedDataGridColumn>
</mx:columns>
</mx:AdvancedDataGrid>

I simplified things from my client app, however the key to remember is that I was setting the dataField property so that the itemRenderer could access it. The problem with this is that clicking the column to sort would treat the columns as if they were identical. You can try a full sample. The top grid in the sample exhibits the problem.

Once one column was clicked, the other would always call the first column's sortCompareFunction; thus sorting based on the wrong criteria. For example. Load the app, click the first name column:

You'll see that the first name column is sorted. However, you can also see the sort arrow show up on the second column. Now click the last name column:

After clicking the last name column, the first name column is sorted in reverse order, but there is no sorting on the last name column. Reload the app, and try to sort the second column first. It sorts correctly. Then click the first column, and you'll see that only the second column is affected. Using the Flex Debugger I determined that once one column was clicked that column's sortCompareFunction was called for all columns that had the same dataField specified. That was not expected behavior.

My Solution

After some experimentation, I determined that the problem was that the dataField on both columns was identical. My solution was to add the dummy property into the dataProvider's objects and change one of the column's dataFields to point to the dummValue:


<mx:AdvancedDataGridColumn headerText="last Name" sortable="true" dataField="dummyValue" sortCompareFunction="sortLastName">

The side effect of this change is that I could no longer use the 'generic' itemRenderer. Instead of using the listData object in the renderer, I had to hard code the value. Like this:


labelDisplay.text = data.user.lastName;

I determined using a non-generic itemRenderer was better than having the columns not sort properly. I'm sure my client would agree. Click the first column in the grid and see this:

The first name column is sorted properly, and the last name column is untouched. Now click the last name column:

You see that the last name column is sorted properly and the first name column is untouched. This is how it is supposed to work.

Play with the full app here and check out the source code. The top grid shows the problem and the bottom grid shows the solution.

Sign up for DotComIt's Monthly Technical Newsletter

Creating an AngularJS Transform Function to post to ColdFusion

Recently a colleague of mine had some issues posting from AngularJS to a ColdFusion CFC. I thought back to my training course on AngularJS and thought "Hey, didn't I write an article about that." I did; and I also put together a screencast as part of the video series in my "Life After Flex" master course.

I watched it again and impressed myself. So here it is for all to see:

Be sure to pick up the full training course.

Oh, if you were on our mailing list you would have gotten a nice big discount on the master course this month:

Sign up for DotComIt's Monthly Technical Newsletter

An Introduction to Web Components

I wrote this up for the DotComIt newsletter in February, and wanted to post it here too. This is my introduction to Web Components.

What are Web Components?

Web Components are reusable browser widgets. They are being created as a set of standards by the W3C. In a practical sense, Web Components are like Angular directives, but independent of any singular JavaScript framework and built right into the browser. You use Web Components to bundle markup and styles into your own encapsulated, reusable entity.

There are four separate elements of web components:

  • Custom Elements: A custom element is, basically, your own HTML tag.
  • HTML Imports: An HTML Import is like an import in other programming languages, such as ActionScript or Java. They basically point to a piece of encapsulated code and make it available for use in the page.
  • Templates: Templates allow you to define fragments of an HTML page for reuse.
  • Shadow DOM: The Shadow DOM is used to define functional boundaries between the Web Component and the page that uses it. This, essentially, defines your API. It prevents conflicts between CSS and named HTML elements inside the Web Component and outside of it.

This article will server as an introduction to all these aspects.

One warning before we get into it; Web Components are a new technology and not supported by all browsers. These samples were built and tested in Chrome, but do not seem to work elsewhere.

Get Polymer

Polymer is a common framework used for building web components, and we're going to make use of Polymer in this article. You can find information about getting Polymer. You'll just need to put the polymer files in a web accessible directory. I put mine in "/bower_components/polymer/polymer.html".

Sample 0: No Web Component

I want to start this with a simple example. This sample will be a web page, that has some centered text on it:


<html>
<head lang="en">
<meta charset="UTF-8">
<title>Web Components: Sample 0</title>
<style>
.centeredContent{
text-align: center;
}
</style>
</head>
<body>
<div class="centeredContent" >
Hello World!
</div>
</body>
</html>

The "Hello World" text is put inside a div. The text is centered with the centeredContent style. This is about as simple as it comes. Your browser should look something like this:

Check out a running sample.

For the remainder of this article we are going to iterate over a web component that will center its text. This is a simple sample for example purposes, but can be much more complex.

Sample 1: Centered Text Web Component

Let's create our first Web Component. Let's start with a simple web page:


<html>
<head lang="en">
<meta charset="UTF-8">
<title>Web Components: Sample 1: A Simple Component</title>
</head>
<body>
</body>
</html>

Since we will be using the Polymer framework, the first step is to import it. Put the import statement in the head of the HTML Page:


<link rel="import" href="bower_components/polymer/polymer.html">

The import statement is based off the link tag, which I primarily use to import external style sheets. The syntax should be familiar to you. The rel says import and the href is the link to the HTML file that you're importing; in this case the polymer framework.

Next, define the web component with the polymer-element tag:


<polymer-element name="my-HelloWorld" noscript>
</polymer-element>

This tag can be added to the body of the page. The name of our component is specified as my-HellowWorld; but currently it does nothing. A template is needed for the component, so inside the polymer-element add the template tag:


<template>
</template>

So far so good! Next, add the content inside the template; which will be the style for centering content and the div that includes the content:


<style>
.centeredContent{
text-align: center;
}
</style>
<div class="centeredContent" >
Hello World inside component!
</div>

Finally, you can use the Web Component as if it were a standard HTML tag:


<my-HelloWorld ></my-HelloWorld>
<my-HelloWorld ></my-HelloWorld>

See this in action:

This example touched on three of primary elements of creating Web Components. We used an import to import the polymer framework. We used a template to create our own template. And we created a custom element that is used to create an instance of the web component.

Sample 2: Defining Content with Attributes

The Web Component we've created doesn't have much utility yet. It only displays items and does not give us any external control over the component. We can change that, by adding support for attributes to the component. We are going to add a single attribute, named text and that attribute will be used to determine the content that the component will center.

First, add the attributes attribute to the polymer-element:


<polymer-element name="my-HelloWorld" attributes="text" noscript>

When we want to display the text inside the div, we can do this:


<div class="centeredContent" >
{{text}}
</div>

The double curly bracket syntax that is prevalent in Angular, so you should be familiar with it from my past writings. Flex uses a similar syntax--except with just a single curly bracket. The syntax does binding under the hood. It means that whenever the text value changes, the internal component display will also update. Here is the code that uses the attribute:


<my-HelloWorld text="Hello World attribute!" ></my-HelloWorld>
<my-HelloWorld text="Hello World attribute 2!" ></my-HelloWorld>

The attribute we defined is used just like any other attribute on an HTML tag.

Try this in action, and you should see a screen similar to this:

Sample 3: Defining Content Body Text

In addition to adding attributes, you can also access the tag's body text from within the component. Start from sample 1 to create this modification. Inside the template you can reference the body text, or inner HTML, of the tag using the content tag:


<div class="centeredContent" >
<content></content>
</div>

Then you specify the content to send into the tag like this:


<my-HelloWorld >Hello World Body Text!</my-HelloWorld>
<my-HelloWorld >Hello World Body Text 2!</my-HelloWorld>

The results can be shown here:

Sample 4: Separate Files

In real development you won't want to define your Web Components in the same file. Doing so limits reuse, which defeats the purpose of componentizing your code in the first place. Let's create a new file, component4.html and move our component into it:


<link rel="import" href="bower_components/polymer/polymer.html">

<polymer-element name="my-HelloWorld" attributes="text" noscript>
<template>
<style>
.centeredContent{
text-align: center;
}
</style>
<div class="centeredContent" >
{{text}}
</div>
</template>
</polymer-element>

This code contains the import of the polymer framework. The code includes the polymer definition of the my-HelloWorld component and also contains the text attribute and the template with our style and the centered content. The main page becomes much simpler now:


<html>
<head lang="en">
<meta charset="UTF-8">
<title>Web Components: Sample 3: A Component in a Different File</title>
<link rel="import" href="Component4.html">
</head>
<body>
<my-HelloWorld text="Hello World Separate File" ></my-HelloWorld>
<my-HelloWorld text="Hello World Separate File 2" ></my-HelloWorld>

</body>
</html>

This new main page imports Component4.html, but does not import the Polymer framework, because that is imported directly into the component, and not used in the main page.

This is the sample here:

Sample 5: Shadow DOM Isolation

One thing I didn't want to go into details on, but did want to touch on was the shadow DOM. The shadow DOM means that the DOM inside the Web Component is isolated from the DOM of the main page. This will prevent styles from different components, or your main application, from interfering with the main component styles. Let's take a look at Component5.html:


<link rel="import" href="bower_components/polymer/polymer.html">

<polymer-element name="my-HelloWorld" attributes="text" noscript>
<template>
<style>
.alignedContent{
text-align: center;
}
</style>
<div class="alignedContent" >
{{text}}
</div>
</template>
</polymer-element>

This is pretty similar to the example we saw in sample 4. The main difference is that I named the style alignedContent instead of centeredContent. The text inside the component will center the content. But, let's use the same style inside the main index file to right align content:


<html>
<head lang="en">
<meta charset="UTF-8">
<title>Web Components: Sample 3: Shadow DOM Samples</title>
<link rel="import" href="Component5.html">
<style>
.alignedContent{
text-align: right;
}
</style>

</head>
<body>

<my-HelloWorld text="Hello World Shadow DOM Example" ></my-HelloWorld>

<div class="alignedContent" >
Shadow DOM Example
</div>
</body>
</html>

To reiterate, the alignedContent style will center content in the Web Component, but will right align content in the main file. Run this code and you'll see:

You can see the top text is centered while the bottom text is right aligned. Two styles with the same name do not interfere with each other. This is why we love encapsulation.

When using browser developer tools by default the Shadow DOM element is hidden; however you can enable them if you want. This is a great debugging tool because if you're just using a component, you may want to focus on your own code without drilling down into the code of the component that you use. A parallel in the Flex world is that you often want to debug your own code, but do not need to drill into the code behind the Flex Framework code or code from complimentary frameworks such as Robotlegs or Swiz.

Final thoughts

Here are some good resources for reading up on web components:

Web Components, as an evolving standard, are not supported in all browsers yet. The polymer framework helps by providing support for browsers that don't have web components implemented natively. That is why most examples about web components use the framework. I like the concept, and this is something to watch in the future.

Sign up for DotComIt's Monthly Technical Newsletter

Random Thoughts on 2014

Inspired by my good friend John Wilker, I thought I'd put together some random thoughts on 2014. In no particular order, here they are:

  • Patent Pending may move into my "Favorite Band" spot. Their music is clever and fun. When performing they choose energy over technical prowess, but I really don't mind. I imagine it hard to sing well while you're jumping around like a jackrabbit on speed whose fur is on fire.
  • I took a vacation to Yellowstone park and proved I can have a good time while not working.
  • A big thanks goes out to all of DotComIt's clients who keep me busy. I currently have a few consistent clients which is great on the cash flow, but not so great on fulfilling the Entrepreneurial urges.
  • I released a training course on AngularJS early in the year; and then updated it later in the year to include an extra book on NodeJS. I still sell a few "pay what you want" copies each month, but the upper tiers have all but been forgotten since the initial launch. For whatever reason, I have failed and pushing folks through a sales funnel. I really thought the six hours of Angular screencasts would be appealing; but perhaps it is not communicated well enough.
  • I play Bloon Monkey City almost every day, because everyone needs a brainless casual game to give their mind time to work.
  • I was performing an Edwin McCain song in my office and my wife said I sounded good; which is the second nicest thing she has ever said to me music-wise. Remind me to be nicer to her.
  • I reviewed 16 games for Just Adventure. That means I reviewed one third of the games they reviewed games this year. Holy Carp! I didn't realize I was that big of a contributor. The site has changed a lot since I started writing reviews for them in 2009; and a lot of my older reviews are no longer on the site. It bums me out a bit. Check out the 2014 year in review over there. I wrote the non-game parts; and edited the game parts.
  • Since ending The Flex Show, John and I don't chat nearly as much. I miss it. I gotta figure out how to use Google Hangouts at some point. I should see all of my friends more, but life gets in the way.
  • Speaking of Entrepreneurial drive; I'm working on creating a dirty card game. I have no idea if I have a good balance of naughty and entertaining or it just vulgar and offensive. I should get my first prototype today.
  • One of my songs was put on a community coder's compilation. I chose a song from my February Album Writing Month set that I liked. Speaking of which I wrote and recorded 19 songs for February Album Writing Month. Near the end of the year I started writing songs w/ Shadow. My songwriting is all mathematical while she is very artsy. It has been an interesting experience.
  • I made it another year without updating this blog's design or my company site. I really gotta do that.
  • I supported a bunch of PledgeMusic campaigns. Some people do it better than others. I'm really excited for Ryan Hamilton's album; especially since he promised us a full set of demos for the album. I eat that stuff up. Compare that to Guster whose presale prices are higher than I would expect suggested retail price to be. It is just one more time in recent years I feel like Guster is trying to rip off their biggest fans. I seem to be the only one indifferent to People On Vacation's release. It's an album I keep listening to because I don't get it and want to hear the brilliance everyone else hears. I think some of the songs are great, but the production feels like a glorified demo and not of professional quality. Sometimes I complain too much.

There are my random thoughts. Happy 2015 Everyone!

Turning the ESRI Map Component into an Angular Directive

A client of mine builds a lot of mapping applications using the ESRI API. The ESRI HTML5 Map components are built on top of the DOJO toolkit, a JavaScript framework and UI library. DOJO has its' own way of doing things. My client likes the Angular approach better than DOJO, so they wanted to be able to use the DOJO Mapping component as an Angular directive. How do we do that?

There are lots of blog posts on using the ESRI Mapping components inside an Angular application. This was the best one I found, however I could not find any ready to go samples. This article intends to explain exactly my approach to make the DOJO component work as an Angular directive.

Why is there a problem?

DOJO is JavaScript code, right? Any JavaScript code can be wrapped up in an Angular directive, right? Why does the ESRI mapping component present problems? The problem is that DOJO uses a “load on demand” approach, and that brings out issues where the AngularJS Directive is created in the browser before the DOJO object is loaded. Getting Angular and DOJO to sync up was the root of my problem.

Create the Main Index

The main index will be an HTML page that will display the map using the Angular directive we will create. First create a basic outline:


<html>
<head lang="en">
<meta charset="UTF-8">
<link rel="stylesheet" href="http://js.arcgis.com/3.9/js/esri/css/esri.css" />
<script src="http://js.arcgis.com/3.9/"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.10/angular.min.js"></script>
<script src="js/ESRIDirective.js" ></script>
</head>
<body >
</body>
</html>

There are four important elements of the HTML header. The first is a link tag to load the ESRI style sheet. This is loaded directly from the ESRI servers. The second loads the ESRI libraries, also loaded from the ESRI servers. The third library loads the Angular library from the Google CDN. The final script tag will load our local ESRIDirective.js file. This directive will include the finished Angular library, which will be built later in this article.

Next, create an AngularJS module and controller for testing the application:


<script>
var mapTest = angular.module('mapTest',['map']);
mapTest.controller('mapTestController',['$scope', function($scope){
$scope.title = "Hello Map";
}]);
</script>

This creates a new AngularJS Module named mapTest. One custom directive is passed into it, named map. This is the directive that will be defined in the ESRIDirective.js script. A single controller is created, named mapTestController. It creates a single variable in the $scope, named title.

We flesh out the body of the HTML page to load the map:


<body ng-app="mapTest" >
<div ng-controller="mapTestController">
<h1>{{title}}</h1>
<esri-map zoom="4" basemap="streets" id="mapID">
</esri-map>
</div> -
</body>

I modified the body tag to add the ngApp directive on it. Then I added a div which points to the mapTestController with the ngController directive. The title from the directive's $scope is displayed. Then the map directive is used. Three attributes are specified directly on the esriMap directive: zoom, basemap, and id. These relate to specifics of the ESRI mapping API.

Trying to load this app in a browser will just give you errors, because the ESRIDirective.js file has not yet been created. We can tackle that next.

Write the JavaScript Code

Create the JavaScript file ESRIDirective.js in a js directory relative to the main index file. This is the file that will contain the Angular mapping directive based on the ESRI DOJO component. First create an Angular module:


var esriMap = angular.module('map', []);

When you want to create Angular directives that can be shared across different modules, it is a common approach to create the directive in a separate module. The module is passed into the main angular application as part of the dependency array.

Next, I want to create a mapObjectWrapper:


var mapObjectWrapper = function(){
this.map = undefined
}

This is a variable that will exist as part of the HTML page. It exists outside of AngularJS and independent of DOJO. This object will wrap the DOJO map object, however it is undefined in its default state.

Next, create an AngularJS factory to wrap the mapObjectWrapper:


esriMap.factory('esriMapService',function(){
return mapObjectWrapper;
})

This factory allows us to use the mapObjectWrapper inside of an Angular controller without breaking the dependency injection principle of AngularJS that allows for easy module testing.

Now create the directive:


esriMap.directive('esriMap', function () {
return {
restrict: 'EA',
controller: 'MapController',
link: function (scope, element, attrs, ctrl) {
ctrl.init(element);
}
};
});

The restrict property of the directive allows the directive to be used as an entity, or as an attribute. In the code we shared in the previous section it was used as an Entity, like this:


<esri-map>
</esri-map>

However, the could also have been used as an attribute, like this:


<div esri-map>
</div>

This directive specifies both a controller and a link function. The controller refers to MapController, something we haven't created yet. The link function does not contain any code other than to execute an init() method on the controller. This approach is used so that the controller is separate from the directive and can be tested independently.

The real meat of this directive is in the controller, so let's create that next:


esriMap.controller('MapController',
['$rootScope', '$scope', '$attrs','esriMapService',
function ($rootScope, $scope, $attrs, esriMapService) {
$scope.mapService = esriMapService;
$scope.mapService.scope = $scope;
}

The previous code block creates a controller on the esriMap module. The controller is named MapController. There are four services passed into it:

  • $rootScope: The $rootScope service will be used for broadcasting evnts to other aspects of the application.
  • $scope: The angular $scope service is used for sharing data between a view and controller.
  • $attrs: The $attrs service will contain all the attributes on our custom HTML element. It will allow us to introspect the attributes if need be.
  • esriMapService: The esriMapService is the custom service we created, which wraps the mapObjectWrapper.The mapService is saved to the local $scope, and the local $scope is saved as a property on the mapService object. This is so that our DOJO code can execute a function on the the directive’s $scope once it is loaded. You'll see this code later.

Here is the init() function:


this.init = function (element) {
if (!$attrs.id) { throw new Error('\'id\' is required for a map.'); }
$scope.$element = element;
if(!$scope.mapService.map){
return;
}
$scope.recreateMap();
};

The init() function is called from the link in the directive's controller. The first piece of code in the the method is to make sure that an id attribute exists on the directive's tag. The ID is required by the ESRI map component. The element argument represents the tag that created the directive. The element is stored into the controller’s $scope for later reference.

If the mapService.map variable is not yet defined, then the method’s execution stops. Otherwise, a recreateMap() method is called in the controller:


$scope.recreateMap = function(){
createDiv();
createMap();
}

This method just calls two other methods. The createDiv() method is used to create a separate div required by the mapping component. The createMap() component will create a map on that div. First, create a variable to contain the map div:


var mapDiv

Now, we can examine the method:


var createDiv = function () {
if(mapDiv){
return;
}
mapDiv = document.createElement('div');
mapDiv.setAttribute('id', $attrs.id);
$scope.$element.removeAttr('id');
$scope.$element.append(mapDiv);
};

If the mapDiv already exists, then the function processing is terminated. If the mapDiv doesn't exist, then a new div is created. It is given the same ID that is specified as an attribute. The id attribute is removed from the main tag. This is so that the map component does not get confused by two separate divs identically named. Finally the new map div is added to the DOM as a child of the main element.

Here is the createMap function:


var createMap = function () {
if(!$scope.mapService.map){
return;
}
if(!mapDiv){
return;
}
var options = {
center: $attrs.center ? JSON.parse($attrs.center) : [-56.049, 38.485],
zoom: $attrs.zoom ? parseInt($attrs.zoom) : 10,
basemap: $attrs.basemap ? $attrs.basemap : 'streets'
};
$scope.map = new $scope.mapService.map($attrs.id, options);
$scope.map.on('load', function () { $rootScope.$broadcast('map-load'); });
$scope.map.on('click', function (e) { $rootScope.$broadcast('map-click', e); });
};

First, the method checks to see if the mapService is loaded and if the mapDiv is created. If either of these conditions is false, then the code is not executed.

Next the code creates an options object. This options object properties are created by introspecting the attributes on the directive's main tag, and uses those attributes to create a options object to send to the map. In this case, only three are implemented: center, zoom, and basemap.

I'm unsure how I feel about the code examining the attributes to create the options object. I’d rather pass this responsibility back to the directive user, so they can add or remove properties as needed. The current approach will need to change the directive code if we want to add, or remove, option parameters. In my Angular travels it is more common to see an options object passed into the directive, and that options object contains all the relevant parameters. I’d much prefer that to introspecting the attributes however I did not have time to change that for this proof of principle demo.

The next line of the method uses the DOJO mapService’s map() method to create the map on the screen. The last two lines add load and click event handlers on the map. This uses the $rootScope to broadcast events, essentially telling anyone listening that these actions occurred on the map.

The final piece of code is the DOJO statement to load ESRI’s map function. This should exist outside of any Angular code:


require(['esri/map'], function (Map) {
mapObjectWrapper.map = Map;
mapObjectWrapper.scope.recreateMap();
});

The DOJO component is loaded using the require() method. That means it is not loaded immediately, but queued up to be loaded as it is needed. Once loaded, this method accesses the mapObjectWrapper directly. It saves the map as a parameter inside the object, and then executes the recreateMap() function on the controller's scope. Remember when we added the scope to the esriMapService? This is why. It provides a hook so that the DOJO code can communicate with the Angular directive.

Final Thoughts

You can view a working demo here. The client I built this prototype for also put the code in their GitHub account. My own fork is here.

Sign up for DotComIt's Monthly Technical Newsletter

Pizza Dough

Someone asked for my Pizza Dough recipe, so here it is. I got this from my brother primarily, but have tweaked it slightly

Ingredients

  • 2.5 Tablespoons of Active Dry Yeast
  • 1/4 Teaspoon of Sugar
  • 1.5 cups of warm water
  • 3.5 cups of Flour
  • 1.5 teaspoons of salt
  • 1 Tablespoons of Olive Oil

Make the Dough

  1. Dissolve Yeast and Sugar in warm water
  2. Sift Flour (I sift it directly into my Kitchen aid mixer bowl)
    • My siblings add extra Gluten here. For each cup of Flour, remove one tablespoon of Flour and add in one Tablespoon of Gluten. I do not do this.
  3. Add salt to Sifted Flour
  4. Add Olive Oil to Flour Mixture
  5. Add Yeast/Sugar/Water mixture to Flour Mixture
    • If you're adventurous you can add extra seasoning to the dough at this stage; Basil works really well.
    • If you saved a small dough ball from your last batch; add it to this batch to give this batch a sour dough taste.
  6. Mix in Kitchen Aid Mixer with Dough Hook. I usually run this for 10 minutes or so, usually on mid-speed. You can watch the dough as it mixes. If it looks too stiff; I may add more water; or if it is too watery I may add more flour.
  7. Coat a separate bowl in Olive Oil. I eyeball it, but roughly use a tablespoon and then use a basting brush to coat the inside of the bowl.
  8. When dough is ready; move from KitchenAid mixer to Olive Oil Bowl. I'll roll around the dough in the bowl.
  9. Cover and put someplace warm for 2+ hours. This will let the dough rise; it will probably double in size.
  10. Punch dough down and break into 2-3 dough balls. Store in the refrigerator. [I use a large Lock and Lock box; although that brand is no more]

Make the Pizza

  1. Heat Oven to 550 degrees
  2. Put Flour on Counter; roll out dough with a rolling pin. Add additional flour to rolling pin / on top of dough if dough sticks to Rolling pin (or counter)
  3. Roll up Sides of Dough (I do two rolls to make the edge of the pizza)
  4. Add sauce, cheese and toppings
  5. Cook for about 10 minutes

How do I run code on Browser Resize with AngularJS?

I was working on a client application and wanted to execute code inside a controller whenever the browser resized.

My purpose was to change a ng-class value, based on the available width. I had it all working great on the initial application load, but the client wanted to make sure this display update would take place if the browser resized and without having to reload the app.

How can I run code when the browser resizes? I imagine there are a bunch of ways, but in the context of AngularJS this is how I did it.

First add the $window service to your controller:


function someCtrl($scope, $window) {
}

With the $window service and the Angular element, you can get a reference to the JQuery object:


var w = angular.element($window);

I believe this should work even if you don't have the full JQuery version loaded into your app, because AngularJS has a version of JQuery under the hood.

Then you can use bind() on resize.


w.bind('resize', function () {
console.log('resize');
});

The bind() function, in this case, will execute the function whenever the browser resizes.

Here is a Plunkr to demo this.

Sign up for DotComIt's Monthly Technical Newsletter

What technology should I add to Life After Flex?

If you don't know, I put together a training course on AngularJS for Flex Developers. It includes some free books, and a bunch of additional (non-free) content including six hours of screencasts.

I'd like to extend the series to include more technologies, and have put together this quiz to collect your feedback. It is only three questions and should take less than a minute, so go in and throw your two cents.

I'd welcome the feedback.

More Entries

All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.