Web Modules Core
================

- [Objective](#objective)
- [Definition](#definition)
- [Collaboration Guidelines](#collaboration-guidelines)
- [Monorepository Guidelines](#monorepository-guidelines)
- [Installation](#installation)
- [Initial configuration](#initial-configuration)
- [Localization](#localization)
- [Skins](#skins)
- [Skin Development](#skin-development)
- [Lists](#lists)
- [Redux](#redux)
- [Q & A](#q-a)
- [Dev Server & Demo](#dev-server-demo)
- [Check Build Consistency](#check-build-consistency)
- [Documentation](#documentation)
- [Testing](#testing)
- [ESLint](#eslint)
- [Migration](#migration)

-----

# Objective

As a part of ongoing effort to make Web Modules available for use outside of Service Web and also to simplify
cross-location collaboration process, we plan make Web Modules sub-packages available in NPM.

# Definition

This repo consists of following main sub-packages:

- `build` — build utilities (skin/i18n loaders, NWB config builder) 
- `core` - UI library
- `dnd` - drag'n'drop helpers
- `redux` - Redux helpers
- `utils` - client-side skin/i18n support

Core (e.g. UI) package consists of following big entities:

1. Skin &mdash; adaptation of Bootstrap SASS project for SW look and feel, including special brands
2. UI &mdash; adaptation of React Bootstrap project which includes:
    - Automation tools support
    - Easy nested forms
    - Grids (could be replaced with Redux Grids)
    - Wizards

# Benefits

- Independent release cycle, teams will have an ability to share code before “official” syncs with mercurial’s default
- Not only SW will have an ability to use component library and skins: express setup, for example
- Demo pages with all available components will help design teams to know their building blocks better
- Transparent industry-standard open source contribution process via pull requests
- In future we can set up a separate CI process. In order to use externalized NPM version in SW a traditional
    Web Module Package still will have to exist, but this package should just re-export everything from NPM package.
- Transition is a one-time effort when we will have to gather all changes of WMC, put them in GIT, refer to GIT repo
    from `package.json` in SW and do cleanup. Ideally, this should be done on default branch, so that all teams
    will be able to sync right away.

# Collaboration Guidelines

- FT will work with two DEV branches – Mercurial (SW) and Github (WM core). Before the Mercurial code merge, FT will
    need to issue a WM pull request first if updates were made. This action should generate a new version number
    such as `major.minor.patch`. Other FT with WM Dev branch would need to resolve conflicts if any. At the time
    of the Mercurial check in request, the WM core new version number will need to be registered. Since WM doesn’t
    report the CI result, we assume the GCI passing rate for SW can represent the WM code quality.
- The key point is that team responsible for SW merge must bump the version of WM Core in package.json, and in order
    to do that, this team needs to finalize any pending pull requests, if any. This process ensures that SW will always
    have it’s own codebase in sync with certain WMC version.
- If a bug in WM needs to be fixed after Code Freeze, FT needs to file a bug to get the WM version upgraded for SW.
    So this action should trigger the WM update alert after CF.
- GIT repository of WMC must follow same branching model that SW repo has. With one difference — actual versions are
    GIT tags. For example: Assume SW has a bug in SW `elease_81` branch. It means, that in GIT the branch with same
    name WMC `release/8.1` should be cut from the same version that SW was pointing. After fixes GIT `release/8.x`
    branch should get a tag `8.1.x`. Appropriate `8.1.x` version should be referenced in `package.json` in
    SW `release_81` branch
- Before SW release_81 branch will be merged to default, WMC release/8.1 branch should be merged to master and should
    get a new version, say `8.2.x`, since there could be changes for 8.2 already (if there were no updates for 8.2 in WMC
    then version will still be `8.1.x`)
- After that SW release_81 branch is merged to default and correct version should be referenced in `package.json`

# Monorepository Guidelines

Track dependencies between `src/*` directories (sub-packages), `core` that depends on `utils` is OK because it is the
lowest level, `redux` or `dnd` that depend on `core` is NOT OK because `redux` can easily live without `core` and it
will also cause issues with SW packaging mechanism

# Installation

```sh
npm install web-modules-core --save
```

# Initial configuration

```js
import {setConfig} from "web-modules-core/src/utils";

setConfig({
    brandId: 1210,
    userLanguage: getLocale(),
    brandDisplayName: 'RingCentral',
    shortBrandName: 'RC'
});
```

After that application may access configuration at any time:

```js
import {getConfig} from "web-modules-core/src/utils";
console.log(getConfig().brandId);
```

# Localization

## Where should I store localized strings?

The root namespace for all localization files is `src/app/lang`. All strings are grouped by relevance into packages.

## How to include localization?

See [full example repo](boilerplate) for more information.

In order to enable 3.0 workflow you need to execute the `rc-generate` script:

```json
{
  "scripts": {
    "generate": "rc-generate --config build-config.js",
    "start": "npm run generate && rc-webpack --config webpack.config.dev.js"
  }
}
```

You need to include localization only once before you bootstrap the entire application:

Then create a `langLoader.js` file:

```js
// langLoader.js
import {loader, defaultLocale} from 'src/lang/loader'; // this file is generated by Webpack utility
import {localize, createStringsLoader} from 'web-modules-core';

const langLoader = createStringsLoader(loader, defaultLocale, localize);

export default langLoader;
```

After that you can use it in main entry point:

```js
// index.js
import {getStrings, translate, setConfig} from "web-modules-core/src/utils";
import langLoader from "./langLoader";

// Step 1. Set the language and all other configuration (brand, etc.)
setConfig({
    //...
    userLanguage: 'en_US'
});

// Step 2. Load strings
langLoader().then(() => {
    // Now it is safe to use localization
    alert(translate(getStrings().SOME_KEY));
});
```

Then you can use localization in components:

```js
// components/*.js
import {translate, setConfig, getConfig} from "web-modules-core/src/utils";
import stringsLoader from "../langLoader";
import page1 from "../lang/page1/index-en_US"; // you may import files directly

// Step 1. Set the language and all other configuration (brand, etc.)
setConfig({
    //...
    userLanguage: 'en_US'
});

// Step 2. Load strings
stringsLoader(getConfig().userLanguage).then(() => {
    // Now it is safe to use localization, but keep in mind not to re-map page1's properties, use page1 as is
    alert(translate(page1.SOME_KEY));
});
```

In case translation contains HTML you still need to use ```translate()``` and wrap it into ```<DangerousHTML/>``` component. DangerousHTML designed specially to mark that values of translated variables should be escaped to prevent XSS vector.

Say you have this key in your localization dictionary:

```js
// content of src/lang/home/index-en_US
//
export default {
    GREETING_MESSAGE: 'Hello, <strong>{userName}</strong>'
};
```

```js
import langHome from "src/lang/home/index-en_US";
// ...
render() {
    const {userName} = this.props;
    // DangerousHTML will inject unsafe HTML into the DOM, so You have to make sure that html parameters unescaped
    return (
        <p>
            <DangerousHTML>
                {translate(langHome.GREETING_MESSAGE, {userName: sanitize(userName)})}
            </DangerousHTML>
        </p>
    );
}
```

```<Translate/>``` tag is deprecated and should not be used.

Always include `en_US` version of file. Content of the file will be dynamically rewritten during runtime.

## What is the structure of strings file?

The structure is a simple object exported as usual:

```js
/** @namespace SOME_NAME_SPACE */
export default {
    ALERT: "Alert",
    CONFIRMATION: "Confirmation",
    // ICU Message Format is used to tokenize the string
    MF_TEST: "You have received {NUM, plural, one{# call} other{# calls}} today \
              Much wow, such new line",
    TOKENIZED: "A string with {TOKEN}",
    // If the same token can have different value in different brands
    COMPANY_NAME: {
        0: 'WhiteLabel',
        1210: 'RingCentral',
        2222: 'SomethingNew'
    }
};
```

# Skins

```js
import {setSkinLoader, loadSkin, setConfig} from "web-modules-core/src/utils";
import skinLoader from "src/skin/loader"; // this file is generated by Webpack utility

// Step 1. Set the brand and all other configuration (langage, etc.)
setConfig({
    //...
    brandId: 1210
});

// Step 2. Set the loader
setSkinLoader(skinLoader);

// Step 3. Load skin, promise is returned, but it's optional to handle it
loadSkin();
```

# Skin Development

## Variables

There are two variables files:

- Skinnable (colors, images): located in `src/skin/variables/_colors*.scss`
    **!!! NOT ALLOWED TO USE ANYWHERE OUTSIDE SKIN PACKAGE OR PROJECT'S ROOT SKIN FILES!!!**
- Non-skinnable (dimensions, sizes, paddings, etc): `src/skin/variables/_dimensions*.scss`, allowed to be used
    in components since dimensions does not vary per brand

In Service Web skin is included in `Bootstrap.js` with brand ID postfix.

## If bootstrap element has to be extended

1. Start with changing of variables
2. Copy-paste bootstrap selector (including nesting) and alter only
3. Create a JS file and extend Bootstrap's class

## If something has to be shared between components/projects

It should go to core, no inter-component dependencies (rule of a thumb: if anything is used twice – it goes to core).
Same applies to anything that has color.

## For each custom color in skin files (`skin/styles/*.scss`) a variable in `skin/variables/colors` has to be created

- Give a semantic name to variables (like $checkboxGroupHeaderBorderColor)
- Camel Case

## In skin files it is allowed to use pre-defined Bootstrap variables like `$table-bg-hover`

Such variables must either have a spec color, say `$some-random-var: $light-gray`) or initialized explicitly
in `colors.sass`.

## Patterns

### Colors and dimensions

- Each color must have a name (default brand color, default background, data table highlight, default text color)
- Colors that are derived from base ones (default brand color, etc.) should be defined as lighten(default-brand-color,
50%) and not as resulting HEX value
- Give names to default paddings/margins
- Give names to default font sizes
- Icons should have default dimensions (16x16, 24x24, 32x32 and so on)
- Names must be based on the purpose (default blue is a bad name since in another brand it could be
purple/red/whatever) – all colors/widths/paddings/dimensions will eventually be converted to SCSS variables
- Generally, don't name variables based upon the values - name them semantically, so based upon logical function in the
styling, Don't name a variable "light background" or "wide margin" or "large font", for example. The variable names
should not presume or indicate anything about the specific values that are used.

### Alignment

- Components should be aligned by using default Bootstrap containers
- If custom paddings are introduced they should have a value of an existing Bootstrap variable (gutter width for
example) or a fraction of it
- Default alignments
    - 12 column grid
    - Left / center / right
- Provide instructions in PNGs
    - For non-grid alignments or when it's not obvious
    - For grid blocks when they have sub-grids
    - Non-grid elements should be drawn off the grid to reduce confusion
    - Highlight grid blocks to make it clear how many columns it occupies

### Dynamic behavior

- Provide mocks with different states of dynamic elements (collapsed vs expanded left panel, expanded table row)
- If standard behaviors for some components in a mock have already been established and are part of a UX style guide,
then provide the relevant documents or links as reference.
- Provide overflow guidelines (multiple numbers in table row, too long text labels, etc.)

## Layouts

### Blocks

Popups and panels have default padding in header, footer and body. This padding is synchronized with bottom margin of
`H1`, `H2`, `H3`, `P`, `DL` tags, etc.

In order to cancel this padding use `noPadding` and `noHeaderPadding` props in `<Popup>` and `<Panel>`.

If you need to lift/shift component, you can wrap it with `<Block>` component like the following:

```jsx
<Block cancelBottomOffset cancelHorizontalOffset>
    <TabPanel .../>
</Block>
```

This will move the tab panel right close to left, right & bottom borders of container.

If you need to have more space around component use `rc-block-accent` class.

If you need less than usual block space (device order or role permissions, for example) use `rc-block-condensed` class.

All custom components (like `TabPanel`) should not have any margins.

### Grid System

TBD (see demo for now).

### Inline Layouts

TBD (see demo for now).

## Icons

Use `npm run icommon` task to download new icons, don't forget to commit files after that.

# Lists

In order to create a list a new class that extends List class must be created:

```js
import {ActionsButton, List, ListCheckbox, ListHeaderCheckbox, ListHighlight, GridSorter, GridCell} from 'web-modules-core/src/core';

class UsersGrid extends List {

    render() {

        return <table className="rc-grid table-striped">
            <thead>
            <th style={{width: '5%'}}><ListHeaderCheckbox/></th>
            <GridSorter property="status" className="status-column" style={{width: '10%'}}>{translate(langUsers.STATUS)}</GridSorter>
            <GridSorter property="name" className="name-column" style={{width: '20%'}}>{translate(langUsers.USER_NAME)}</GridSorter>
            <GridSorter property="numbers" className="number-column" style={{width: '25%'}}>{translate(langUsers.NUMBER)}</GridSorter>
            <GridSorter property="pin" className="extension-column" style={{width: '15%'}}>{translate(langUsers.EXTENSION)}</GridSorter>
            <GridSorter property="department" className="department-column" colSpan="2" style={{width: '25%'}}>{translate(langUsers.DEPARTMENT)}</GridSorter>
            </thead>
            <tbody>
            {this.hasItems() ? this.renderItems() : this.renderEmpty()}
            </tbody>
        </table>;

    }

    renderItem(record) {

        var actions = getItemActions(record);

        var gridActions = actions.length > 0 ? (<div className="rc-grid-visibleOnRowHover rc-grid-actions">
            <ActionsButton popoverClassName='rc-grid-actionsButton-popover' actions={actions}/>
        </div>) : null;

        return (<tr className={classnames({'is-selected': this.isSelectedRow(record)})} key={record.id}>
            <td><ListCheckbox record={record}/></td>
            <GridCell>{statusFormatter(record)}</GridCell>
            <GridCell onClick={SW.openUserSettings.bind(null, record.id)}>{record.name}</GridCell>
            <GridCell>{ExtensionsHelper.utils.numbersFormatter(record, this.getDetails())}</GridCell>
            <GridCell>{record.pin}</GridCell>
            <GridCell>{record.department}</GridCell>
            <td>{gridActions}</td>
        </tr>);

    }

    renderEmpty() {
        return <tr className="empty-row">
            <td colSpan="8">{super.renderEmpty()}</td>
        </tr>;
    }

}
```

The way we approach lists is basically a composition of reusable small React components with very specific functionality.
Those components can talk to each other via shared objects in context.

List of available components:

- `ListSorter` – control responsible for sorting of the store
- `GridSorter` – grid (table) version of ListSorter
- `ListCheckbox` – allows to select items from store
- `ListHeaderCheckbox` – controls all ListCheckboxes
- `ListHighlight` – highlights matched characters based on filter input
- `GridCell` – short hand for `<td><ListHighlight>...</ListHighlight></td>` that can be used in tables

List class itself has some useful methods that can be accessed by it's descendants:

- isSelectedRow(record) – use this to highlight a selected row in a certain way
- highlight(text) – functional analogue of <ListHighlight> component

# Redux

## Step 0. Imports

```js
import React from "react";
import ReactDOM from "react-dom";
import {Provider} from "react-redux";
import {createSetup, connectSetup, connectUI, createStore, Highlight, PersistGate} from "web-modules-core/src/redux";
``` 

## Step 1. Create a Setup
 
```js
const setup = createSetup({
    prefix: 'clientGrid',
    filterFn: filterFn,
    sortFn: sortFn,
    // loadFn: (options, dispatch) => USERS, // you can load items from server, this requires onLoad call when your component is mounted to DOM
    items: USERS, // or just set them synchronously
    sort: {by: 'firstName'}
});
```

## Step 2. Create Redux Store with Setup

You can use `createStore` helper or create a store with all middlewares by yourself.

```js
const {store, persistor} = createStore({
    reducers: {
        [setup.prefix()]: setup.createReducer()
    }
});
```

## Step 3. Define and connect UI components

UI elements may also be injected into `Panel` props via function `connectUI` (`Highlight` in this example). Actions and
selected state items are injected via `connectSetup`:

```js
let Panel = ({items, filter, onSetFilter, Highlight, ...props}) => {

    const onFilterChange = (e) => {
        onSetFilter(e.target.value);
    };

    return <div>
        <div>
            <input type="text" value={filter} onChange={onFilterChange} />
        </div>
        {items.map(item => (
            <div>
                <Highlight>{item}</Highlight>
            </div>
        ))}
    </div>;
}

Panel = connectSetup(setup)(Panel);
Panel = connectUI(setup)(Panel);
```

### 3.1. Mapping props

If names of props provided by `connectSetup` does not satisfy you, you can optionally provide a mapper function:

```js
let HL = connectSetup(setup, ({filter, ...props}) => ({customFilter: filter, ...props}))(Highlight);
```

### 3.2. Individually connect UI elements via `connectSetup`

UI elements may be connected separately and then used as regular React elements w/o injection into props:

```
let HL = connectSetup(setup)(Highlight);
```
### 3.3. Individually connect UI elements via `connect`

If you don't like how `connectSetup` wires things, you can also manually connect anything to anything using React Redux
`connect` function, this requires deeper knowledge of `Setup` API:

```js
let HL = connect((state) => ({
    highlight: function someCustomHighlightFunction(){}, // optional
    filter: setup.getFilter(state)
}), {
    onSetFilter: setup.setFilter
})(Highlight);
```

### 3.4. Contextual way

Another way to connect UI is through contextual components:

```js
let Panel = ({items, filter, onSetFilter, ...props}) => {
    return <div>
        {items.map(item => (
            <div>
                <ContextHighlight>{item}</ContextHighlight>
            </div>
        ))}
    </div>;
};

const ContextPanel = <SetupContext setup={setup}>
    <Panel/>
</SetupContext>;
```

## Step 4. Render

Don't forget to add `<PersistGate persistor={persistor}>` inside `<Provider store={store}>`, otherwise persist will work
with errors.

```js
const App = ({store, persistor}) => (
    <PersistGate persistor={persistor}>
        <Provider store={store}>
            <ContextPanel/>
        </Provider>
    </PersistGate>
);

ReactDOM.render(<App  persistor={persistor} store={store}/>, document.getElementById('root'));
```

## Understanding modes

There are 2 modes: client and server. In client mode all sorting & filtering is done on client, in server mode client
relies on backend, that does all the work.

You need to define `loadFn` which will return either an array of items or an object
`{items: [], total: 0, filteredTotal}`. In server mode this function will be called each time anything changes. 
In client mode this function can be called manually by your code (for example, when it's time to mount component).

It is safe to call this function as often as needed.

## Redux Tips And Tricks

Please read useful [blog article](http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao-of-redux-part-1).

### Use `combineReducers()` and primitive reducers instead of complex state and one reducer

```js
// BAD
function gridReducerBad(state = {items: [], pagination: {perPage: 10, page: 1}}, action) {  }
 
// GOOD
const gridReducerGood = combineReducers({
    items: (state = [], action) => {  },
    pagination: combineReducers({
        perPage: (state = 10, actions) => {  },
        page: (state = 1, actions) => {  }
    })
});
```

### One action may cause multiple reducers to change state

Actions never match reducers 1-to-1, one action may cause a ripple effect in sub-states. Use this technique to make
linked changes in many states based on user interactions.

```js
function perPageReducer(state = 10, action) {
    switch (action.type) {
        case 'SET_PER_PAGE':
                return action.payload;
        default:
            return state;
    }
}
 
const DEFAULT_PAGE = 1;
 
function pageReducer(state = DEFAULT_PAGE, action) {
    switch (action.type) {
        case 'SET_PAGE':
            return action.payload;
        case 'SET_PER_PAGE': // handled also by another reducer
                return DEFAULT_PAGE; // you can have calculation logic to move user to correct page instead of page 1
        default:
            return state;
    }
}
```

### Store only pure data and unmodified user inputs

All derived data has to be selected from state by reselect's optimized `createSelector()`. This allows to manipulate
the state freely without worrying that some derived data could become stale.

```js
// state {allItems: [], filter: 'filter string'}
 
// dumb straightforward selectors are also needed sometimes
export const getAllItems = (state) => state.allItems;
export const getAllItemsLength = (state) => getAllItems(state).length;
 
export const getFilter = (state) => state.filter;
 
// private filter function
const filterFn = (filter) => (item) => ~item.name.indexOf(filter);
 
export const getFilteredItems = createSelector(
    [getAllItems, getFilter], 
    (allItems, filter) => allItems.filter(filterFn(filter)) // this filter will be executed only if allItems or filter will change
);
 
export const getFilteredItemsLength = createSelector(
    getFilteredItems, // you can use cached selector as input for another selector
    (filteredItems) => filteredItems.length
);
```

### Export unwrapped dumb components and default export `connect()`-wrapped ones in the same file

Good for tests, good for development.

```js
export const Cmp = ({onAction}) => {  };
export default connect()(Cmp);
```

### Make any heavy iterable block as a separate component wrapped with `connect()`

Connect makes component to act as a pure function, which boosts performance since React will not re-render it if data
has not changed.

Make sure you bind all actions only once and you don't provide anything mutable as props `<Cmp fn={this.makeClick} />`
in render of parent will ruin any pure render optimizations in Cmp because `this.makeClick` will be a different
instance each time parent is re-rendered and it will cause children to re-render too.

### Use imported actions in mapDispatchToProps and keep it as plain object

```js
import * as actions from "./someActions";
export const Cmp = ({onAction}) => {  };
export default connect(null, {
    onSave: actions.savePost // this is perfectly reachable by any IDE and is very clean way to define the relationship
})(Cmp);
```

### Prefer using functional components over class-based ones

You write less code and make the behavior of components a lot cleaner if you work with them as if they are pure
functions. Output can be cached in this case.

### Prefer using uncontrolled inputs if state does not need to be maintained between page reloads

This makes overall global state a lot simpler and reduces the amount of reactions on user inputs.

### Don't mix routing with app state and component state

There has to be always only one source of truth.

### React Router should be responsible for URL params which affect page state 

URLs that can be handed over from one user to another, good example is a JIRA search which can be copy-pasted from
location bar, the rest of the UI state (that is not reflected in location bar) should be a part of redux or a part
of component (if this state is accessible only by one component).

Read more: https://github.com/reactjs/redux/issues/1287.

### Parent components must provide the bare minimum of props for child components

Do not provide things that are not required, e.g. do not spread the entire props of parent into a child, this will cause
child to render more often than needed.

### Children should get actions via mapDispatchToProps (not from parent via props)

This ensures parent knows as less as possible about it's children and we are free to change internal structure if needed.

### Only show as much of the redux state to a component as it needs by slicing exact parts of state

Keep all components as stupid as possible: React pure render optimizations allows to re-render components ONLY if
something visible data changes, so never supply a whole chunk of state to a component, first, break it to simpler
things or even to primitives.

### Each component should have it's own file unless it will only ever be found inside another component and it is simple

Saves space and makes navigation easy. If two components are placed in one file, then both should be exported, the main
one should be exported as default.

### Simple actions are better, type and payload is all that is necessary for most

If an action is more complex – sometimes it's better to break it down to a set of simpler actions and call them one by
one – easier to test.

### Component's action props naming may not match redux actions, use the most appropriate based on placement

Redux actions are usually named as "makeSomething" whereas components params should be names in a more event-like manor
`onSomething` and even can have UI details in naming (`onSaveClick` – click is a UI detail).

### Make authentication data available via redux store

Even if authentication is maintained by separate component and stored, for example, in localStorage, it's useful
to make it available in react app via redux state (at least as a boolean) so that app will have an easy way to show
logged in or logged out statuses:

```js
// Somewhere in Store setup
 
import {login, logout} from "./actions";
 
function dispatchPlatformEventsToStore(platform, store) {
    platform.on('loginSuccess', () => { store.dispatch(login()); };
    platform.on('logoutSuccess', () => { store.dispatch(logout()); };
}
 
// Actions
 
export const goToLogin = () => { history.push('/login'); }; 
```
 
```js
// LoggedInWrapper.js used as route wrapper around authenticated routes
 
import {getAuthUser} from './selectors';
import {goToLogin, logout} from './actions';
 
export const loggedInWrapper = ({user, goToLogin, logout, children}) => {
    if (!user) {
        setTimeout(goToLogin, 1); // setTimeout is needed to make async transition outside of sync render process
        return null;
    }
    const onClickLogout = (e) => { logout(); };
    return <div>
        <div><button onClick={onClickLogout}>Log Out {user.username}</button></div>
        <div>{children}</div>
    </div>
};
 
export default connect((state) => ({
    user: getAuthUser(state)
}), {
    goToLogin: goToLogin,
    logout: logout
})(loggedInWrapper);
```

# Q & A

## What does stateName in new ThingsStore({stateName: 'thingsList'}) do?

Sets the key for persistent storage of filter settings.

## Why `EventBus.emit("afterStoreReload");` (or before) does not provide event information? Why we need such event?

It is used for automation to and to show/hide `RC.Loader`.

## Why components subscribe to `EventBus.on("resetStore", this.onRefresh);`?

To reload data if linked store has been reset and for other cross-store syncs

## What does `this.register('Things');` do?

Sets up a key that may be used to externally emit an event on which the store have to react

# Dev Server \& Demo

```sh
npm start
open http://localhost:5000
```

# Check Build Consistency

```sh
npm run build
```

# Documentation

This command generates API documentation, which then needs to be committed to repository just like any other file.

```sh
npm run docs
```

Please read [JSDoc 2 Markdown Wiki](https://github.com/jsdoc2md/jsdoc-to-markdown/wiki) to get more information and
recipes.


# Testing

In order to enforce test creation developers must supply unit tests along with merge requests.

For special cases unit tests are mandatory, merge requests without such tests will not be accepted. Special cases are:

- Critical or not obvious functionality
- Complicated logic
- Critical data components like stores

Merge requests will not be accepted if Gitlab CI pipelines are be broken.

```sh
# single run
npm test

# watch mode
npm run test-watch
```

Tests should be located near the appropriate source files with names `OrigName.spec.js`.

WMC offers a set of tools which allow to simplify React-based tests:
- Enzyme https://github.com/airbnb/enzyme
- Chai
- Karma
- Sinon

Chai, Karma and Sinon are globally accessible, no need to import.

Minimal visual test setup:

```js
import React from 'react';
import {shallow, mount, render} from 'web-modules-core/src/build/enzyme';

describe('Component name', () => {
    it('does stuff', () => {});
});
```

Coverage can be found in the build directory.

# Code Quality Tools
This repository using [Prettier](https://github.com/prettier/prettier) for consistent auto formatting.
And provides a few scripts to simplify development.

Also, [ESLint](https://github.com/eslint/eslint) is used to follow our best practices and to avoid unnecessary 
questions on code-review. See [ringcentral-javascript](https://github.com/ringcentral/ringcentral-javascript) for details

## Development tools integration in IntelliJ IDEA
1. Enable eslint\
![idea-eslint-settings.png](screenshots/idea-eslint-settings.png)

2. Add custom 'prettier' command as a handy tool for reformat current file in IDE.\
Open **Tools > External Tools > Add**
![idea-prettier-command.png](screenshots/idea-prettier-command.png)\
set arguments field **"./scripts/prettier/index.js -wf $FilePathRelativeToProjectRoot$"**\
set Working Directory to **"$ProjectFileDir$"**
![idea-prettier-command-edit.png](screenshots/idea-prettier-command-edit.png)

3. Setup hotkey for custom command\
open **Keymap > Externa Tools > External Tools > prettier**
click right button on "Add Keyboard Shurtcut" and set **_"Ctrl+Shift+P"_** or any other You like to use.
![idea-prettier-keymap.png](screenshots/idea-prettier-keymap.png)
![idea-prettier-keymap-edit.png](screenshots/idea-prettier-keymap-edit.png)

4. Now You are ready to use this tool:\
Open any .js file, edit it and type **_"Ctrl+Shift+P"_** to apply automatically code formatting at this file. 
Any eslint issue will be highlighted.
 
## Commit flow
Say, you are ready to commit and push your changes.\
We enable pre-commit hook to ensure that new code is free from linter (thanks ESlint) and formatting (thanks Prettier) issues.
So, during commit, pre-commit scripts run and may detect issues You need to fix.


* attempt to commit, but formatting issues detected
![idea-prettier-keymap-edit.png](screenshots/idea-prettier-keymap-edit.png)
![commit-step2-formatting-dailed.png](screenshots/commit-step2-formatting-failed.png)

* autofix formatting issues and attempt to commit, but linter issues detected
![commit-step3-formatting-autofix.png](screenshots/commit-step3-formatting-autofix.png)
![commit-step4-eslint-failed.png](screenshots/commit-step4-eslint-failed.png)

* fix lint errors, then commit
![commit-step5-commit-successful.png](screenshots/commit-step5-commit-successful.png)

# Migration

## 9.2 to 9.3

1. Update `web-modules-core` pacakge to 9.3.x

2. Install NWB as dev dependency:
    
    ```bash
    $ npm install nwb --save-dev
    ```
    
3. Uninstall `web-modules-*` packages (except `web-modules-core`):

    ```bash
    $ npm uninstall web-modules-utils web-modules-dnd web-modules-redux
    ```    
    
4. Remove `webpack.config.js` and create new `nwb.config.js`, use [Boilerplate](boilerplate) as
    reference. Move all custom sections to new file, you will have to change format from Webpack config to NWB.

5. Create `src/index.html` and `src/index.js` entry points.

6. Change build/test scripts to call `nwb` instead of `webpack`, use [Boilerplate](boilerplate)
    as reference too.

7. All packages were merged into one mono repository, so you have to change references. Suggested auto-replaces:
    - `web-modules-core` -> `web-modules-core/src/core`
    - `web-modules-core/src/...` -> `web-modules-core/src/core/...`
    - `web-modules-utils` -> `web-modules-core/src/utils`
    - `web-modules-utils/src/...` -> `web-modules-core/src/utils/...`
    - `web-modules-dnd` -> `web-modules-core/src/dnd`
    - `web-modules-redux` -> `web-modules-core/src/redux`
    - `web-modules-utils/utils` -> `web-modules-core/src/build`

## `11.0` to `11.1`

1. Replace NWB configs with new `build-config.js` and `webpack.config.*.js`

2. Update `start` and `build` scripts: add `rc-webpack` usage

3. Add `.babelrc.js`

4. Update tests
    
## `19.2` to `19.3` (former `11.2`)

1. Prepend `start` and `build` scripts with usage of `rc-generate --config build-config.js` script

2. Use full package imports: `import {Whatever} from "web-modules-core";`, tree-shaking will do the rest

## `19.3.4` to `19.3.20`

In 19.3.20 monorepository has been introduced. SW used following remap: `web-modules-xxx` -> `web-modules-all/xxx`.

## `19.3.24` to `19.3.25`

Localization mechanism has been changed. Boilerplate/SW/etc. code no longer needs to import `localize` function and
provide it to `createStringsLoader`:

```
import {localize as localizeCore, createStringsLoader, moment} from 'web-modules-core';
        ^^^^^^^^^^^^^^^^^^^^^^^^^        
export default createStringsLoader({loader, defaultStrings, localizeCore, moment});
                                                            ^^^^^^^^^^^^^
```

Code underlined with `^^^` should be removed.