Skip to content

Instantly share code, notes, and snippets.

@pyreta
Last active January 25, 2019 15:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyreta/73dd66eb8e7e9b394737b18e7b8ed493 to your computer and use it in GitHub Desktop.
Save pyreta/73dd66eb8e7e9b394737b18e7b8ed493 to your computer and use it in GitHub Desktop.
Jingle Player Documentation

Jingle Player Documentation

Contents:

Tools used

  1. Yarn - package manager used for installing new libraries, and running other commands. Some helpful commands:
    • yarn install or just yarn - installs all libraries in package.json
    • yarn add (library) - installs a new library and adds it to package.json
    • yarn test - runs all frontend tests
    • yarn lint - runs linter according to lint config
    • yarn storybook - runs Storybook on port http://localhost:9001/.
  2. React - Handles all DOM updates/manipulation. Replaced old Backbone and jQuery code.
  3. Redux - Handles application state management.
  4. redux-actions - added to make reducers more readable (mapping with an object instead of switch statements)
  5. Storybook - Provides component development environment outside of application
  6. Jest - Front end testing engine
  7. Enzyme - Airbnb's testing utilities
  8. redux-saga - Used for handling asynchronous side-effects caused by dispatching a redux action.
  9. styled-components - Used for styling, completely replacing the use of .css files.

Structure

All folders within app/javascript/jingleplayerReact are structured in concurrence with the application state. Each folder represents a portion of the application, and contains all react components, sagas, reducers, and tests related to that section. So instead of being structured by file type, the player folders are structured by distinct application features/areas.

Every folder, with the exception of /util, /dataModels and /components follows the same structure. This structure repeats itself indefinitely as features become nested. The basic pattern followed is that no folder may touch another that is not directly above it. If something is needed by multiple folders (such as a component, helper, or action) then it should be moved up until it is directly accessible by all files that need it by traversing vertically in one direction only.

Every folder contains the following:

  1. reducer.js - Updates the portion of the application state that the section is responsible for. If the reducer is a composition of multiple, more granular reducers, then it will instead be represented by a /reducer folder containing all the nested reducers and the composed reducer in an index.js file. All reducer files are eventually composed into a single reducer found in the reducer.js file found at the root level of app/javascript/jingleplayerReact.
  2. actions.js (when applicable) - These are the action creators used for dispatching actions to update the redux store. Action types only used by folder will be defined here. Action types needed by more than one folder are added to the array in app/javascript/jingleplayerReact/util/createActionsAndTypes. This makes them available to all action creators.
  3. /components (when applicable) - Contains all components used within the section and and folders nested within it.
  4. saga.js (where applicable) - Each folder contains it's own saga that handles all asynchronous side-effects within the application section. This usually is used when a component triggers a request to load records on mount that then need to be added to the store (such as a cue search). If the saga is a composition of multiple nested sagas, then it will instead be represented by a /saga folder composed in index.js. All saga files are eventually composed into a single root saga; saga.js found at the root level of app/javascript/jingleplayerReact.
  5. /__tests__ (where applicable) - contains tests related to the files in the same directory as this folder
  6. Additional files/folders containing logic used by the section (such as /util)

A description of these folders is as follows, where the first 4 represent actual UI sections of the player itself:

  1. /pageDisplay - Main page in the player that displays search results, playlists, etc
  2. /sidebar - This holds folders containing playlists, links, and search suggestions
  3. /audioPlayer - The actual audio player at the bottom of the page
  4. /search - The search field and filter drawer that opens when field is engaged
  5. /cues - contains the cues loaded on the front end from search or playlists. Used by multiple sections of player (Sidebar and Page Display).
  6. /theme - contains the customizable theme used by the organization which flows through the entire player within Styled Components.
  7. /translations - this contains all the i18n translations for text displayed within the player. No text is hard coded. Any viewable text is stored as a variable (in app/locales/player .yml files) and then accessed through this store key. It is essentially a giant Object that is keyed into.
  8. /user - current user information
  9. /modal - contains the modal type and data to be loaded if present

For each of these folders, there is a corresponding top level key in the Redux store with contains it's state:

  /jingleplayerReact
    /search ------------------> store key 
      /components 
        /SearchFilters
          /components
          /index.js
        /dataModels
        /fixtures
        /reducer
        /actions.js
      /reducer.js
    /reducer.js
  /theme --------------------> store key
    /reducer.js
  /translations -------------> store key
    /actions.js
    /reducer.js
  /util
    ...helpers
  /index.js
  /JinglePlayer.js
  /reducer.js
  /store.js

This structure was inspired by this article. It allows each feature to be more modular since each section is self contained.

While this format adds more nesting, it should also allow feature work to traverse less folders, keeping work that relates to each feature all in once place. Also, the structure shouldn't get more complicated as the app grows, but rather just adding a store key will add to the file structure accordingly.

State

All JP state is managed using Redux. Actions are dispatched to update the state using composed reducers.

Store

The redux store follows the same structure as the folders mentioned above.

Reducers

Since the state is a composition of reducers, and the folder structure follows the structure of the store, each top level folder within section of the Jingle Player has its own reducer. Each of these reducers then flows up to the top level reducer at the root of /jingleplayerReact

Dispatching Actions

All changes to application state must be done through dispatching an action. This is the fundamental concept behind the flux pattern.

The player uses a specific type of action creator instead of passing a vanilla JS object into the dispatch() method.

So instead of:

dispatch({ type: 'SOME_ACTION', payload: 'stuff' });

we do:

dispatch(actions.SOME_ACTION('stuff'));

Or with no payload:

dispatch(actions.SOME_ACTION());
// dispatch({ type: 'SOME_ACTION' })

Or other keys besides payload:

dispatch(actions.SOME_ACTION('stuff', { foo: 'bar' }));
// dispatch({ type: 'SOME_ACTION', payload: 'stuff', foo: 'bar' })

dispatch(actions.SOME_ACTION(undefined, { foo: 'bar' }));
// dispatch({ type: 'SOME_ACTION'', foo: 'bar' })

These actions are created by adding the string to array arg passed into the invoked function in app/actions/index.js which can be accessed with

import actions from 'path/to/actions';

Constants for created actions can be accessed by:

import { actionTypes } from 'path/to/actions';

actionTypes.DO_SOMETHING // 'DO_SOMETHING'

This seemingly strange syntax (function constants?) has some benefits:

  • Errors using string literals are avoided (since misspelling action types on dispatch or in the reducer will fail silently).
  • Encourages the use/standardization of the payload key
  • The action creating function 'looks like' a constant. This is a helpful visual cue to know you are looking at an action type.

Note: The redux dev-tools chrome extension is helpful for debugging anything related to redux.

Sagas

Any side-effects caused by dispatching an action are handled with redux-saga

Storybook

Storybook allows react components to be developed outside the application. This encourages decoupling components from the rest of the player and provides a nice UI for seeing how components react to changes in props. These various states of components are called "stories".

All stories related to a component live in a subfolder in the same directory as a component with the following convention: component: folder/to/components/SomeComponent.js story: folder/to/components/__stories__/SomeComponent.stories.js

Mounting React

The react app jingle player is mounted in the DOM when this file is initialized

Testing

Using the command yarn test, tests are run using the Jest engine and Enzyme testing utilities. All tests related to a file live in a subfolder in the same directory as the file with the following convention: file to be tested: folder/to/components/SomeComponent.js test: folder/to/components/__tests__/SomeComponent.test.js

Style guide

Styles are made conistent using Prettier. Styles are to be automatically adjusted in editor (CTRL + OPTION + F in Atom editor) according to the config options found here.

All React component files are capitalized to signify that the file is exporting React. Components also are nested in a /components folder in whichever directory they belong.

Styling

Stylesheets are not used at all in the Jingle Player. Instead styled-components are used to keep styles linked to the UI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment