Note: Python 2.7 is required until either node-gyp drops their dependency), or we move off of node-sass.
Install the following:
Then execute:
nvm install 6
nvm use 6
Next, install npm
dependencies:
yarn
- Start node development server:
npm run dev
- Start the Portal Server
- View the site: local.dcc.icgc.org:9000
Connect to the production server's API instead of a local backend
npm run dev:prodapi
Connect to the staging API instead of local backend
API_SOURCE=https://staging.dcc.icgc.org npm start
Adds local.dcc.icgc.org to your host file and point to localhost
npm run sethost
Open fontello with the project's json config loaded
npm run font:open
- Max line length: 120
- Spaces NOT Tabs
- 2 Spaces per indent
- Strings in 'single quotes'
- camelCase for variable names
- Use === instead of ==
- Fields generated by the UI should be prefixed with 'ui'
- Use jQuery not $
- Comments begin with a Capital letter
- Use lodash functions to handle your array/object manipulation to avoid writing boilerplate array/object code. See - https://lodash.com/docs
- Angular Services, Controllers and any function used to construct new objects should begin with a capitalized letter to denote that it is essentially a constructor function (i.e. called with the new JS keyword.)
-
This document is a work in progress and therefore it's important to note that it isn't a definitive guide to all possible style or convention-related topics you will encounter in your development endeavours.
-
This below 'best practices' are here to give you a starting point on how to structure your code moving forward.
-
Of course it should be mentioned that testing your code should always be part of your development process and you should consistently make best efforts to provide automated tests for any modules you develop/modify. Technical debt (introduced when testing is not a consideration) is bad as it drives up maintenance and development efforts moving forward.
-
These tests will help you (and others) catch regression bugs on the frontend moving forward!
-
You may see some assets which do not fully conform to some (or many) of the below conventions. If you do see this please feel free to refactor them appropriately as you fix/extend functionality in these modules.
-
In general modules are self contained in the
app/scripts
directory. -
Each module has is named in all lowercase and is comprised of a js, views, styles. See below:
module_name/ |__ js/ |__ views/ |__ styles/ |__ images/
-
The js folder contains your angularJS controllers, services, filters, etc.
-
The views folder contains your angularJS html templates.
-
The styles folder contains your scss (sass) files pertaining (i.e. relevant to the styling of your html views).
-
The images folder contains your image files relevant to the module.
-
Modules are broken down according to the function it serves within the application. This means that your module names should reflect the functionality provided by your module and provide a simple (relatively flat structure) to gain access to your module's' angular assets.
-
In general it is preferred that you use the
.
notation to separate your namespaced module from others.- For example if you were to create a module that provided gene set functionality your module name could be called
geneset
with the primary module definition (including possible application routing), controllers, services/factories and directives, filters, etc. beinggeneset.controllers
,geneset.services
,geneset.directives
andgeneset.filters
respectively with each of these sub-modules living in their own geneset.js, controllers.js, services.js, directives.js and filters.js files respectively under thejs
folder.
- For example if you were to create a module that provided gene set functionality your module name could be called
- It could certainly be argued that it would be better to breakdown the angular assets even further (which is great for unit tests) into each individual controller, directive or service however we feel that this may introduce too much complexity for very little payback. As such we have opted for a middle ground approach. That is either the whole module is included in its entirety or none at all.
-
The bad example...
// A private function --> Ensure all your comments begin with a capital letter function meaningOfLife() { } // A private variable var theMeaningOfLife = 42;
-
The good example...
// A private function function _meaningOfLife() { } // A private variable var _theMeaningOfLife = 42;
-
The bad example - contents of secrets.js
// What does this method do? function meaningOfLife(meaningOfLifeVal) { // ... some implementation details return _theMeaningOfLife; } /* ... Some code ... */
-
The good example - contents of secrets.js
/** * Gets and sets the meaning of Life (this is a method description that is picked up by jsdocs). * @param {string} meaningOfLifeVal - The new meaning of life. * @returns {string} The current meaning of life. */ function meaningOfLife(meaningOfLifeVal) { // ... return _theMeaningOfLife; }
var _service = this;
var _controller = this;
var _this = this;
var _meaningOfLifeVar;
function meaningOfLife(answer) {
if (arguments.length === 1) {
_meaningOfLifeVar = answer;
}
return _meaningOfLifeVar;
}
console.log(meaningOfLife()); // Outputs 'undefined'
console.log(meaningOfLife(1)); // Outputs '1'
meaningOfLife(42);
console.log(meaningOfLife()); // Outputs '42'
-
In general rather then using the traditional get and set methods we are collapsing this into one function. Why? To reduce verbose duplication of code and to map our conventions to the same one angularJS does with their getter/setters See https://docs.angularjs.org/api/ng/directive/ngModel#binding-to-a-getter-setter
-
Note: In the case where we only wish to create a getter function only it is completely admissible to use the traditional
get<propertyName>()
functional naming scheme.
-
Use controllerAs or
controller: 'a as b' (in the $stateProvider)
syntax in your controller (we do this in our route definition on the parent module). This allows you to bind your controller'sthis
(we will store this in a variable called _controller in our controllers --> see above) value to functionality your view can use. -
Using this convention allows you to use $scope as well however please try not to!!! The reality is Angular makes the controller available via the $scope variable irregardless so it's really your call if you have some compelling reason to use both.
-
An example of a module called projects in a file called projects.js
// Projects modules definition including dependencies angular.module('session', ['session.controllers', 'ui.router']) .constant('sessionConstants', { FOO_1: 'foo1', FOO_2:'foo2' // CAPITALIZE AND UNDERSCORE CONSTANTS! }) .config(function ($stateProvider) { $stateProvider.state('sessions', { url: '/sessions?filters', templateUrl: 'scripts/session/views/session.html', controller: 'SessionCtrl as SessionCtrl', // <--- controller declaration! data: { tab: 'summary' } }); }) .run(function(someDependency1, someDependency1, ..., someDependencyN) { /* ... Run block implementation ... */ });
-
In general for anything you would like to expose (as an API via a service/factory or to a scope) use private methods (with
_
) to bind your components and make this declarations at the top most portions of your components (for readability). Note: If you are just straight assigning a variable you can do it inline as long as it does not span more then 3 lines. -
A Bad Example
angular.module('session.controllers', []) .controller('SessionCtrl', function() { var _controller = this; _controller.gotoSession = function() { /* ... */ }; _controller.refresh = function() { /* ... */ }; _controller.search = function() { /* ... */ }; _controller.sessions = []; _controller.title = 'Sessions'; }) .controller('FixedSessionCtrl', function() { /* ... */ });
-
A Good Example
angular.module('session.controllers', []) /** * This controller does ... * @requires sessionProxyService **/ .controller('SessionCtrl', function(sessionProxyService) { var _controller = this; // Public Controller API _controller.gotoSession = _gotoSession; _controller.refresh = _refresh; _controller.search = _search; _controller.sessions = ['bob', 'dusan', 'terry']; _controller.title = 'Sessions'; // Assign an object used by the view or a child controller. _controller.sessionProxyManager = sessionProxyService.sessionProxyManagerFactory(); // Private multiline functions // A great candidate for using your epic JSDocs skills to document the below functions... function _gotoSession() { /* ... */ } function _refresh() { /* ... */ } function _search() { /* ... */ } }) /** * This child controller (parent: SessionCtrl) does ... * @requires someService **/ .controller('FixedSessionCtrl', function(someService) { /* ... */ });
-
If you must use
$scope
in your controllers/directives ensure that you are at least encapsulating your data members in coherent objects i.e. do not assign properties (non-functions) directly to the$scope
. This will prevent $scope prototype inheritance issues you might run into with child scopes used in forms, subcontrollers, and directives. -
A Bad Example
angular.module('sessions.controller') .controller(function($scope) { var _controller = this; $scope.name = 'Tim Cooke'; $scope.address = '...'; $scope.phone = '...'; /* ... */ });
-
A Good Example
angular.module('users.controllers') .controller('UserCtrl', function($scope) { var _controller = this, // You can seperate your vars with commas _user = { name : 'Tim Cooke', address: '...', phone: '...' }; $scope.user = _user; /* ... */ });
Pass JSHint: npm run lint
Pass Unit tests: npm test
If a production build fails to produce the expected output when deployed, try rm -rf node_modules
from the dcc-portal-ui
root folder and rebuilding.