Skip to content
Frontend Assets for RoundingWell Care Team Operations
JavaScript CSS HTML Shell
Branch: develop
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
.github
config
release
src
test
.babelrc
.codecov.yml
.editorconfig
.eslintrc
.gitignore
.nvmrc
.stylelintrc
LICENSE.md
README.md
composer.json
cypress.json
package-lock.json
package.json
webpack.config.js

README.md

CircleCI codecov Cypress.io tests

Getting Started: Frontend

Documentation for the RoundingWell frontend can be found within README.md files throughout this repo.

Dependencies & Installation

We use npm for our package manager

Node

You will need Node.js. It is recommended that devs install nvm the node version manager. NVM allows you to change node versions on the fly.

Once NVM is installed, activate the right Node version with:

$ nvm use

Installing Project Dependencies

Then you will need to run

$ npm i

Releasing

Releases are automated via an npm script. Make a new temporary local branch consisting of the commits you want to release. Then run

$ npm run release

It will automatically create a release branch on origin named release/YYYYMMDD If that release branch already exists it will increment the branch name ie: release/YYYYMMDD-1 All dates are in UTC.

To make a release outside of this convention, pass it a branch name.

$ npm run release test-foo

This will create the branch release/test-foo

Development

There are two npm commands most useful for development:

Useful for local development with webpack local dev-server and the backend docker instance

$ npm run dev

To develop in the Cypress gui:

$ npm run dev:coverage

Important Dependencies

const MyApp = Toolkit.App.extend({
  onBeforeStart(options) {
    this.setView(new MyLayoutView());
    this.showChildView('header', options.headerText);
    this.showView();
  },
  beforeStart() {
    return Radio.request('entities', 'data');
  },
  onStart(options, data) {
    this.showChildView('content', new DataView({ model: data }));
  }
});

Components should be configurable but not built to be extended. They can also act as a wrapper for 3rd party widgets such as jquery plugins. A component controls a region and a view combined with a state model, providing a consistent API for interaction.

  • Backbone.Store A small but important library that ensures there is only a single instance of a model in memory. It is used for both server models and to sync state models across applications.
const MyModel = Backbone.Model.extend({...});

const MyUniqueModel = Backbone.Store(MyModel);

const myUniqueModel = new MyUniqueModel({ id: '1', foo: 'bar' });
const sameModel = new MyUniqueModel({ id: '1' });
sameModel.get('foo') === 'bar'; // true

const nonUnique = new MyModel({ id: '1' });
nonUnique.get('foo') === 'bar'; // false
  • Store.js A library for accessing localstorage across browsers

Font Awesome

This project uses Font Awesome Pro and should be setup globally to work with npm for devs.

Icons are loaded with subsetting. To use an icon with Font Awesome it needs to be set in package.json

"fontawesome": {
  "far": ["faCheck"],
  "fas": ["faCheck"],
  "fal": ["faAcorn"]
},

The above example would make available solid and regular check and light acorn. To access the icon, use the following template helper:

{{fas "check"}}
{{fal "acorn"}}
{{far "check"}}

Templating and Styles

Handlebars templates and stylesheets should be imported directly into the modules they're used. Handlebars can also be made inline using the ``hbs```` template literal

import CompiledTemplate from './template-file.hbs';
import 'some-styles.scss';
const OtherTemplate = hbs`
  <div class="other-template">
    This multi-line template will be precompiled during the build process.
  </div>
`;

Feature Flags

Feature Flags are intended to protect users from new code that isn't fully baked or to allow for gradual rollout.

JavaScript

if (Radio.request('feature', 'has', 'group_management')) {
  // new code
}

Handlebars

Javascript flags are highly prefered, but a template helper is available if needed.

{{#ifHasFeatureFlag "some_flag"}}
  <p>this</p>
{{ else }}
  <p>that</p>
{{/ifHasFeatureFlag}}

Using Feature Flags

Flags are best used such that a minimal amount of changes are made when the flag is being removed.

// Bad
function foo() {
  if (flag) {
    doNew();
  } else {
    doOld();
  }
}

// Good
function foo() {
  if (!flag) {
    doOld();
    return;
  }
  doNew();
}

It also may be better to duplicate larger areas of code for fewer easier to remove flags.

// Bad
function foo() {
  if (flag) foo();
  bar();
  baz();
  if (!flag) bazinga();
  quxx();
}

// Good
function foo() {
  if (!flag) {
    bar();
    baz();
    bazinga();
    quxx();
    return;
  }

  foo();
  bar();
  baz();
  quxx();
}

Code Standards

Outside of linting there are some recommendations for code standards that will help in long term maintenance.

  • SASS follows a general BEM format. Ideally do not make deep elements, but start a new block if additional descriptors are needed.
  • Favor object composition over class inheritance
  • Within reason functions should ideally accomplish one task in less than 10 lines unless logically trivial. If it is getting to big, break up the function.
  • Similarly modules should ideally get no larger than 300 lines or so. This is a general guideline and not a rule.
  • Use only .js- prefixed selectors if at all possible from the code. Do not use style classes, ids, or tagnames if it can be avoided.
  • Utilize the ui hash on views and avoid using the $el if possible. Never use $ directly within a view. Worst case use this.$el or this.$.
  • The view should have say over only the DOM inside its own template or in the case of a CollectionView sometimes its direct children. Avoid allowing deep reaching within the DOM or anything external changing the DOM besides the view itself.
  • Anything AJAX should happen within the entities service only.
  • Generally it is better to store state on a state model than to append a property to an instance.
  • Events and handlers should be named verb:subject or context:verb:subject based on what happened not what will happen ie:
triggers: {
  'click .js-button1': 'show:modal' // bad
  'click .js-button2': 'click:button2' // good
},
childViewTriggers: {
  'click:button': 'button:clicked', // bad
  'click:button': 'listItem:click:button' //good
},
onShowModal() {
  console.log('The naming of this handler suggest a modal was just shown');
},
onClickButton2() {
  this.showModal(); // It's ok for a handler to simply call a single action
}

Use guards to handle the exception on functions when possible ie:

// Good
doFoo(options) {
  if (!options.bar) {
    this.doSomethingElse();
    return;
  }

  return 'Foo:' + options.bar;
}

// Bad
doFoo(options) {
  if (options.bar) {
    return 'Foo:' + options.bar
  } else {
    this.doSomethingElse()
  }
}

An Open Source Culture

It is our intention to open source by default. Ideally any generic solution can be extracted, well documented, and tested. Open sourcing encourages better code, collaboration with outside developers, and potentially free battle-testing, bugfixes and features from the public.

Our Libraries

Libraries for public consumption are licensed with the MIT License.

Currently our OS projects are available mainly at https://github.com/RoundingWellOS

Each project contains its own documentation for contributions.

Other Libraries

Additionally we actively encourage contributing to other projects. Don't know where to start? Look at documentation or tests for any of the libraries we use.

You can’t perform that action at this time.