Skip to content

Latest commit

 

History

History
390 lines (293 loc) · 12.8 KB

README.md

File metadata and controls

390 lines (293 loc) · 12.8 KB

ICGC DCC - Portal UI

Setup

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

Running

Other Commands

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

Basic Style Guide

  • 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.)

Coding Conventions

Before we Begin...

  • 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.

Angular Modules

The Module Folder Structure

  • 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.

Module Breakdown

  • 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. being geneset.controllers, geneset.services, geneset.directives and geneset.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 the js folder.

Rationale for an Angular Flat Structure

  • 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.

Private methods and Variables

  • 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;

Code Commenting and Documentation

  • 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;
        }

Referencing the this context in your Angular Services and Controllers

Service Context:
var _service = this;
Controller Context:
var _controller = this;
In General
var _this = this;

Setter/Getter Methods

    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.

Angular Controller Conventions

  • 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's this (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 ... */
          });

General Controller/Service/Directive/(Whatever) Binding Conventions

  • 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;
    
                /* ... */
              });

Before Pull Request

Pass JSHint: npm run lint

Pass Unit tests: npm test

Tips

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.

Additional Info

Copyright and License