# mPortalUI

<!-- START doctoc generated TOC -->
<!-- END doctoc generated TOC -->

## Notes

All file paths in this document assume that they are from the root of the mPortalUI project.

## Setup

1. Create an IIS site bound to `portal.mainline.dev`, pointing to the root of the mPortal project. You'll need a hosts file entry for this.
2. Create a virtual directory named `maverick-agency` that points to the old Agency application. For example, `C:\websites\rex\branches\mainline\migration\Application`.
3. Create a virtual directory named `jakarta` that points to the location of your CF install's `isapi_redirect.dll`. For example, `C:\ColdFusion10\config\wsconfig\1`.
4. Run `npm install` in the `mPortal` root. This will install all the NPM packages needed for building and testing.

## Dependencies

### Management

If you are installing or updating packages via NPM or Bower, ensure that you run the command with `--save` or `--save-dev` (depending on the type of package) to persist the package and/or version number to `package.json` or `bower.json`.

If you are updating a package, *do not trust [semver](http://semver.org/)*! It would be nice if everyone strictly followed semver, but that's just simply not the case. Make sure you thoroughly test any updated packages even if the version number only indicates a minor change. Two examples of why we should not trust semver are Angular and jQuery. Semver defines a minor version change (e.g. 1.1 to 1.2) to be a version that adds functionality in a backwards-compatible manner. In the case of Angular, moving from 1.2.x to 1.3.x results in breaking changes for IE 8. Similarly, moving from jQuery 1.8.x to 1.9.x results in breaking changes that require an [additional plugin](https://github.com/jquery/jquery-migrate/#readme) be included to allow for backwards compatibility.

#### [Bower](http://bower.io/)

mPortal dependencies are managed via Bower and they all live in `/app/lib`. Installing Bower dependencies is not part of the build process, so everything in `/app/lib` is checked in to SVN. If a new package is installed or a package is updated, make sure that the code itself gets committed in addition to the bower.json file.

#### [NPM](https://www.npmjs.com/)

NPM is used to manage dependencies for the build process. These dependencies live in `/node_modules` and are not committed to SVN. If a package is installed or updated, only the package.json file should be committed.

### mPortal Dependencies

#### [Angular](https://github.com/angular/angular.js/tree/v1.2.28)

We are using Angular 1.2.28, which is the most recent version in the 1.2.x series. We cannot upgrade beyond 1.2.x as long as we support IE 8.

#### [Angular UI Bootstrap](http://angular-ui.github.io/bootstrap/)

This works fine in IE 8, with the caveat that any directive using custom elements (e.g. `<tabset></tabset>`) needs a little extra work. In the header of index.html, there is an IE 8 script block where custom elements are defined via document.createElement(). If the element(s) needed are not defined there, the directive will fail to render in IE 8 and will throw a somewhat cryptic error.

#### [jQuery](http://jquery.com/)

jQuery is included primarily to support legacy applications. That being said, there's no reason that we can't take advantage of having a full version of jQuery (rather than jqLite). In instances where it makes sense - such as directives - we should make sure to utilize jQuery rather than doing it the hard way.

#### [jQuery UI](http://jqueryui.com/)

jQuery UI is primarily used in legacy applications, but a few of the elements have been wrapped in Angular directives for usage within mPortal. At the time of this writing, directives exist for autocomplete and date picker, located in `/app/common/directive/autocomplete.js` and `/app/common/directive/datePicker.js`, respectively. The general rule of thumb is to first try to use UI Bootstrap, but if that doesn't work, wrap a jQuery UI element in a directive.

#### [Moment](http://momentjs.com/)

Moment is included to make working with dates simpler. Try to use Moment anywhere you're working with dates rather than doing it the hard way.

#### [Overmind](https://github.com/s992/overmind)

Overmind functions as a sort of an application-level router. If you consider each of our apps as "sub-applications," Overmind would be the primary application. With Overmind, we define top level routes for a sub-application (e.g. "/accounts" maps to mPortal.accounts) and Overmind takes care of loading and bootstrapping the sub-application when the route matches. It's essentially a wrapper for ngRoute that replaces the ngView directive with `div#current-view` and manages routing between applications. Loading of sub-applications is performed using RequireJS.

Overmind also supports the sharing of constants, but it's preferable to define constants in a config module and then add that as a dependency to a sub-application (see `/common/config/constants.js`).

We are using a forked version of Overmind because we needed some minor tweaks to support IE 8 and the project maintainer was not interested in merging these changes. The original repository can be found at https://github.com/geddski/overmind, but our fork is located at https://github.com/s992/overmind.

#### [RequireJS](http://requirejs.org/)

All mPortal JS files are defined as RequireJS modules. When defining a module, the following syntax is preferred:

```
define(function( require ) {
	
	var moduleA = require("moduleA"),
		moduleB = require("moduleB");

	// module code

});
```

The example above is more legible and maintainable than array based injection:

```
define(["moduleA", "moduleB"], function( moduleA, moduleB ) {
	
	// module code

});
```

RequireJS should be used to load dependencies, but not to manage dependency injection. For example, if you need a service in a controller, that service should be required and registered with Angular in `app.js` (or the equivalent) rather than being required and used directly in the controller.

Good:

```
// app.js
var ExampleService = require( "ExampleService" ),
	ExampleController = require( "ExampleController" );

app.factory( "ExampleService", ExampleService );
app.controller( "ExampleController", ExampleController );

// ExampleController.js
ExampleController.$inject = [ "ExampleService" ];

function ExampleController( ExampleService ) {
	ExampleService.doStuff();
}
```

Bad:

```
// ExampleController.js
var ExampleService = require( "ExampleService" );

function Examplecontroller() {
	ExampleService.doStuff();
}
```

### Build Dependencies

There are a large number of build dependencies that don't make sense to list in the same fashion as the mPortal dependencies. The build process as a whole is described in the "Build" section of this document, but you can view `package.json` for a full list of dependencies used.

<hr>

## Structure

### Root

The root directory contains our index file, favicon, and some configuration/build process files.

### /app

All of our JS resides in this directory. Each folder, with the exception of `bootstrap`, `common`, `components`, `lib`, `require`, and `shim`, represents a sub-application that is able to be routed to by Overmind.

#### /app/bootstrap

Contains our bootstrapping code. This is the first file required when RequireJS loads and it configures and loads Overmind. The bootstrapping process is explained in depth in the "Application" section of this document.

#### /app/common

Contains common configuration, controllers, directives, filters, and services. The files included here should be reusable across sub-applications.

#### /app/components

Contains UI components (directives) that are built as self contained sub-applications. These are intended to be included as dependencies for sub-applications. For example, the home page has a dependency on `components/activityFeed/app.js` in order to display the activity feed. The files included here should be reusable across sub-applications.

#### /app/lib

Contains all of our external dependencies. These dependencies are described in the "mPortal Dependencies" section of this document.

#### /app/require

Contains our RequireJS config. The configuration is explained in depth in the "Application" section of this document.

#### /app/shim

Contains RequireJS shims for external dependencies that are not available in AMD format.

#### /app/[sub-application]

Each sub-application directory should contain a single sub-application and dependencies, with the exception of dependencies that exist in the `common` or `components` directories. Generally, this means a sub-application's structure will look like this:

```
/sub-application
	/controller
		ControllerA.js
		ControllerB.js
	/template
		templateA.html
		templateB.html
	app.js
```

### /assets

Images, CSS, LESS, and any other static assets reside in this directory. During the build process, `assets/mPortal.css` is overwritten with the compiled LESS, so you should not expect any changes (even committed changes) to the CSS to persist through a build. All LESS/CSS modifications should be routed through the UX Designer (currently Bob Chesley).

### /build

Contains a `web.config` template that is copied into each build. Builds are placed in environment-specific folders within this directory.

### /test

Contains our Karma configuration and all unit tests. Testing is defined in depth in the "Testing" section of this document.

<hr>

## Application

### RequireJS Config

RequireJS is configured in `/require/config.js`.

#### Base URL

The base URL is configured as "app". This means that RequireJS will look for modules starting in the `app` directory. So, a call to `require("common/config/config")` will translate to loading `/app/common/config/config.js`.

#### Paths

The paths object contains aliases for our external dependencies. This allows us to use e.g., `require("angular")` instead of `require("lib/angular/angular")`. All of these paths are relative to the baseUrl.

#### Shim

The shim object allows us to define dependencies for our non-AMD dependencies. For example, Overmind requires both `angular` and `angular-route` to run, so we have to tell RequireJS to load those two modules prior to loading Overmind:

```
"overmind": ["angular", "angular-route"]
```

#### Deps

The deps array is a list of modules that will be required and executed immediately following the completion of RequireJS configuration.

### Bootstrapping

Bootstrapping of our Angular application takes place in `/app/bootstrap/app.js`. This module is listed in the RequireJS deps array, so it is loaded and executed immediately after RequireJS's configuration has completed.

The bootstrap module performs the following tasks:

1. Configures Overmind's routes
2. Registers global event handlers like `$routeChangeError` and `$routeChangeSuccess`
3. Overrides `$routeProvider.when()` to inject an authentication guard into all routes
4. Bootstraps overmind, mPortal.nav, and mPortal.footer

### Authentication Guard

All defined routes (unless specifically indicated with the "insecure" flag) have an `__authGuard` method injected injected into the `resolve` object. This method checks the cached user profile (cached from the last time we requested it from the server) for the `isLoggedIn` flag. If the user is currently logged in, we resolve the promise and the route continues as normal. If the user is not logged in, we reject the promise. This causes a route change error to be thrown, which is handled by a global event handler.

### $routeChangeError

We're using this event to redirect the user to the login screen in the event that the authentication guard indicates the user is not logged in. If the rejection reason is `notLoggedIn`, we perform a redirect to `/login` with an optional query param that indicates where the user should be forwarded to upon successful login.

### $routeChangeSuccess

We're using this event to clean up legacy apps when transitioning from a legacy app to an Angular app. If the app is registered with Overmind and it is not a legacy app, we empty `div#tmplMainContent`, the container for legacy apps.

### Configuration

The configuration module is located in `/app/common/config/config.js`. It is composed of several other modules that make up our site-wide configuration.

#### config.js

In addition to requiring sub-modules and composing our main config module, this file defines a decorator for Angular's `$exceptionHandler`. In order to get Angular to play nice with sourcemaps in built environments, we have to rethrow exceptions in order to get the correct sourcemap file and line number. This decorator is only enabled in environments where sourcemaps are available, so we won't see multiple exceptions thrown in local development or production.

#### constants.js

This file contains constants used throughout the application. During the build process, the entire file is overwritten with environment-specific constants from `constants.json`. Any changes made to `constants.js` should also be addressed in `constants.json` and vice versa.

#### globalRequires.js

Some modules (e.g. `ui-bootstrap`) are required globally and it gets tiresome to `require()` them and add them to each application's dependencies. This module looks to alleviate that by allowing a space to define modules that will be included anywhere `mPortal.config` is included.

#### httpConfig.js

Common headers and settings for `$http` are defined here. Additionally, an interceptor is registered so that we can perform appropriate actions when receiving an error response from the server.

#### templates.js

This file should be a shell during development. When the application is built and deployed, all template files are converted to JS strings and injected into Angular's `$templateCache` via this file. The entire file is overwritten during the build process.

### Global Error Interceptor

A site-wide HTTP interceptor (found in `common/services/ErrorInterceptor.js`) is registered in `common/config/httpConfig.js`. The purpose of this interceptor is to capture error responses from the server and then display a modal indicating that we received an error. Currently, it intercepts HTTP responses with a status code of 401 (session timeout), 403 (access denied), 408 (request timeout), and 500 (server error). Only unrecoverable errors should be intercepted. For example, we don't want to intercept 400 errors, which we commonly use for indicating validation errors - these types of errors should be handled within the calling application.

### Global Services

**Global services should be avoided unless absolutely necessary.**

The user profile object (defined in `common/services/user/UserProfile.js`) is a global service. This means that each Angular application with a dependency on the service will receive a reference to the exact same object, as opposed to "normal" services which are instantiated for each application. The service needs to be global so that we ensure all references to the user profile stay in sync with one another.

When working with a global service, several things must be taken into account:

1. The service itself must be a factory that returns a global (e.g. on the `$window` scope) object
2. The service must have some way of notifying all of its dependents when a property changes
3. Once the global object has been injected into an application, it cannot be replaced (e.g. `$window.userProfile = new UserProfile()`) without falling out of sync with its dependents.

It is necessary for the service to manually notify its dependents of changes because the global object exists outside of the Angular world and is not tracked in Angular's digest cycle. Addressing this problem is a two step process: First, the service must maintain an array of root scopes from all dependent applications. Second, it must track changes somehow and then iterate through the array of root scopes, calling `$apply()` on each one. Currently, changes are "detected" by setting properties via a `set( key, value )` method (rather than `obj.key = value`), which in turn notifies the root scopes of the change. When/if [`Object.observe`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe) has appropriate browser support, it should be used instead of the `set()` method.

The global object cannot be replaced due to how Angular's dependency injection works. Each application that has the global object injected into it receives a reference to whatever the service returns at the time of instantiation. Behind the scenes, the service is returning the global object from the `$window`, but Angular has no knowledge of that - it simply stores the reference to the object and moves on. If the global object is replaced with a new one, we're left with essentially two "copies" of the object: The first is the reference stored in Angular's DI, and the second is the new global object that exists on the window. Modifying either of these objects will not propagate to the other, and we immediately run into synchronization issues.

If it is necessary to reset a global object, you should implement a `reset()` (or similar) method rather than attempting to replace the global object with a pristine version. This method should revert the objects to its default state.

### Adding a new sub-application

When a new sub-application (as described in the above section, "/app/[sub-application]") is created, several steps must be taken to ensure correct routing and building:

1. Add the top level route for the sub-application to the `overmind.apps` object in `app/bootstrap/app.js`.
2. Ensure the sub-application has at least one route defined that corresponds with the top level route defined in `overmind.apps`.
3. Ensure the sub-application is being picked up by the RequireJS optimizer during the build. It should be discovered automatically (described in the "Build" section of this document).

<hr>

## Testing

Testing is done using Karma and Jasmine. All tests, helpers, and configuration files are contained in `/test`. Tests can be started one of two ways: via Gulp or via the Karma CLI.

### Gulp

Our Gulpfile offers the `test` task, which can be kicked off via the command line:

```
gulp test
```

With no flags, the tests will run once in the PhantomJS browser and then exit. The following flags are available:

* `--watch`
* `--browser (PhantomJS|Chrome|Firefox|IE|IE9|IE8|all)`
* `--coverageSummary`

So, to run tests against IE9 in continuous integration mode, you can run:

```
gulp test --watch --browser IE9
```

If "all" is passed as the browser parameter, the tests will be run against all available browsers. It is not recommended to pass `--browser all` with the `--watch` flag.

If `--coverageSummary` is used, a code coverage summary will be output to the console. Otherwise, code coverage details will be output to the `/coverage` directory as described in the "Code Coverage" section of this document.

### Karma CLI

With Karma CLI tests can be started with (assuming your current directory is `/test`): 

```
karma start karma.conf.js
```

Starting tests via Karma CLI will run them in PhantomJS in continuous integration mode.

### Code Coverage

Code coverage is provided by the [`karma-coverage`](https://github.com/karma-runner/karma-coverage) plugin, which utilizes [Istanbul](https://github.com/gotwarlost/istanbul) for the actual coverage. Coverage reports are placed in `/coverage`, which should not be checked in to SVN. Coverage runs every time tests run.

Because we're using RequireJS in our tests instead of the more standard "include everything" approach, there is some complexity involved in making sure Istanbul picks up on all of our testable modules. Istanbul only tracks files that are included and executed, so with a default RequireJS configuration it will only run coverage for modules that already have tests written. To solve this, we have to load all of our testable modules up front during our Karma RequireJS configuration. You can view the code for this in `/test/test-main.js`, but the basics for how we determine it are as follows:

1. If the file is a test, it is not a testable module
2. If the file is not a JS file, it is not a testable module
3. If the file exists in a subdirectory of `app/`, it is a testable module unless:
	1. It exists in `lib/`, `require/`, or `shim/`
	2. It exists in `components/*/directive/`
	3. It exists in `common/config/`
	4. It is named `app.js`

### Structure

#### karma.conf.js

Standard Karma configuration, with one exception: to support RequireJS for testing, we do not allow Karma to load our test files. They are marked as `included: false` so that Karma will watch them for changes but not load them:

```
{ pattern: "test/unit/**/*.spec.js", included: false }
```

#### test-main.js

This is our RequireJS configuration for the Karma instance. The `paths` and `shim` objects should mirror those defined in `/require/config.js` to ensure that the test environment and the real environment run the same way. The exception to this is test-specific libraries such as angular-mocks, which should only be included in `test-main.js`.

#### helpers/

If you find yourself writing the same code for multiple unit tests (such as compiling directives), consider writing a helper module to do it.

#### unit/

This directory should follow the same structure as our `app/` directory. Tests should be named with the extension `.spec.js`: a test for `ExampleController.js` would be named `ExampleController.spec.js`.

<hr>

## Build

The build process is defined in `/gulpfile.js`. There is a single task to kick it off which turns around and runs a sequence of other tasks to actually build the application. To run a build, use this command:

```
gulp build
```

This task supports two flags:

* `--env (dev|alpha|beta|prod)`
* `--skipGzip`

If `env` is not specified, it defaults to "dev." `skipGzip` will bypass the portion of the process where all files are gzipped. This can be useful if you want to view the built files in your editor/IDE.

When a build is initiated, the following tasks are run in sequence:

1. `clean` - Removes the environment-specific build folder from `/build` (e.g. `/build/alpha`)
2. `less` - Compiles the LESS files from `/assets/less` and overwrites `/assets/css/mPortal.css` with the compiled CSS.
3. `copy` - Copies `index.html`, `favicon.ico`, `assets/`, and `app/` to the environment-specific build directory.
4. `webconfig` - Copies `/build/web.config.template` to the environment-specific build directory. If the `skipGzip` flag is present, the `Content-Encoding` header will be removed from the file.
5. `index` - Alters `index.html` to replace environment-specific values with config values from `constants.json`. Also switches jQuery and jQuery UI script paths to their minified equivalents.
6. `config` - Generates the `/app/common/config/constants.js` module from `constants.json`.
7. `html` - Converts all template files to JS strings and generates the `/app/common/config/templates.js` module.
8. `rjs-optimize` - Runs the RequireJS optimizer against the app. This concatenates and minifies the entire application.
9. `clean-build` - Removes unnecessary files from `app` and `assets` directories.
10. `compress` - Runs gzip against the environment-specific build directory. This is skipped if the `skipGzip` flag is present.

### RequireJS Optimizer

There aren't any Gulp plugins that offer the full features of RequireJS's optimizer, so we have to use the `optimize` method and wrap it in a through2 stream. Basically, this allows us to trick Gulp into thinking RequireJS is streaming (like a real plugin) instead of returning from the task prematurely. For all environments except "prod", this task also generates source maps.

The majority of our modules should be discovered automatically when the optimizer walks the dependency tree. The exception to this is our sub-applications' bootstrapping code, found in `[sub-application]/app.js`. These are loaded dynamically by Overmind when routed to, so we have to explicitly define them for the optimizer to pick them up. This is accomplished by getting a list of subdirectories under the `app/` directory and adding each to the optimizer's `include` array. Modules that will be automatically discovered (such as `bootstrap`, `common`, `components`, `lib`, `require`, and `shim`) should be excluded from this array. Locating our sub-application modules takes place in the `rjs-optimize` task.

### Testing a Build

When working in a development environment, you should ensure that your components are successfully building. In order to access a built environment on your local machine (assuming you already ran `gulp build --env dev`), you can append "/build/dev" to the URL. For example, if you are currently accessing mPortal via http://services.markelcorp.dev/mportal, you can access the built environment via http://services.markelcorp.dev/mportal/build/dev. This only applies in development environments because we deploy fully built environments to alpha, beta, and prod.

<hr>

## This File

To generate HTML from this Markdown document, run `gulp readme` from the root of the project. The table of contents will be automatically generated and the compiled HTML will be placed in `/docs`.