nqm-app-framework
v0.3.23
Published
common framework components for nqminds apps
Downloads
3
Keywords
Readme
nqm-app-framework
introduction
The nquiringminds application framework is a set of components, containers and functions that provide a consistent, scaleable and responsive infrastructure on which to quickly build bespoke applications against the nquiringminds Trusted Data Exchange (TDX).
Core features include:
- opinionated mobile-first user interface design and layout that scales well across all devices
- built-in integration with the TDX auth server, plus single sign-on integration with the nquiringminds toolbox
- application state management with support for serialization and replay
- built-in support for subscribing to TDX data publications
- built-in support for the TDX REST API
technologies
You will need to familiarise yourself with the following technologies, utilised by this framework.
- meteor - Meteor is a full-stack JavaScript platform for developing modern web and mobile applications. Meteor includes a key set of technologies for building connected-client reactive applications.
- mantra - application architecture and workflow, provides built-in unit testing support, controlled load order, routing, context, consistent module structure.
- redux - predictable application state management
- react - library for building component-based user interfaces
- react-router - client-side router for react apps
- material-ui - a set of React components implementing Google's Material Design spec.
- jss - high performance JS to CSS compiler
A working knowledge of mongoDB query syntax is desirable.
background reading
As well as familiarising yourself with the above technologies, you may find the following links useful background reading.
packaging
Use the following steps to publish a meteor application that utilises nqm-app-framework.
install meteor-build-client
sudo npm install -g meteor-build-client
create the html template
An HTML template needs to be created that specifies the React root DOM node, for example if you're using a 'react-root' div element, create a build-template.html file containing:
<!DOCTYPE html>
<html>
<head>
{{> css}}
{{> head}}
</head>
<body>
<div id="render-root"></div>
{{> scripts}}
</body>
</html>
build the app
cd /path/to/myApp
meteor-build-client ../myApp-client-only -s ./settings.json -t ../build-template.html
package the app
The application can then be run using a basic nodejs static file http server:
var express = require('express');
var app = express();
app.use("/", express.static(__dirname + "/myApp-client-only"));
app.listen(8081);
components
Layout
The main layout component, provides the central display area, an optional side bar on the left and a title bar. If supplied the sidebar will be responsive to changes in layout and screen size and can be toggled from the title bar navigation button.
properties
- content - [functional React component] the main layout content
- sideBarContent - [functional React component] optional content to appear in the left side bar
- title - [string] the title, displayed in the app bar
example
import framework from "nqm-app-framework";
import Home from "../core/components/home";
import AppSideBar from "./components/app-side-bar";
const Layout = framework.ui.Layout;
export default function(injectDeps, context) {
const {store} = context;
const history = syncHistoryWithStore(browserHistory, store);
const RouterCtx = () => (
<Router history={history}>
<Route path="/" component={Layout}>
<IndexRoute title="lorem upsum" components={{content: Home, sideBarContent: AppSideBar}} />
</Route>
</Router>
);
ReactDOM.render(
React.createElement(injectDeps(RouterCtx)),
document.getElementById("render-root")
);
}
ModalLayout
A simple modal layout component that provides a central display area and title bar. The title bar supports customisable save and close handlers. Use in conjection with ModalPageBase.
properties
- content - [functional React component] the main layout content
- title - [string] the title, displayed in the app bar
example
import framework from "nqm-app-framework";
import About from "../core/containers/about";
const ModalLayout = framework.ui.ModalLayout;
export default function(injectDeps, context) {
const {store} = context;
const history = syncHistoryWithStore(browserHistory, store);
const RouterCtx = () => (
<Router history={history}>
<Route path="/modal" component={ModalLayout}>
<IndexRoute components={{content: About}} />
</Route>
</Router>
);
ReactDOM.render(
React.createElement(injectDeps(RouterCtx)),
document.getElementById("render-root")
);
}
SideBarContent
This is a container component for side bar panels. It has no properties and acts as a simple container for SideBarPanel
components. The SideBar
component expects one and only one SideBarContent
child.
properties
none
example
import React from "react";
import framework from "nqm-app-framework";
// Import app-specific bar panels
import AppMenu from "../containers/app-menu";
import AppOptions from "../containers/app-options";
// Sidebar framework
const SideBarContent = framework.ui.SideBarContent;
const SideBarPanel = framework.ui.SideBarPanel;
const AppSideBar = () => {
return (
<SideBarContent>
<SideBarPanel title="menu" value="menu" icon="apps">
<AppMenu />
</SideBarPanel>
<SideBarPanel title="options" value="options" icon="settings">
<AppOptions />
</SideBarPanel>
</SideBarContent>
);
};
SideBarPanel
Defines a panel within the side bar. A tab button will be added to the side bar tabs, and clicking the button will activate
the panel within the side bar. If the route
property is specified, the panel will only be visible if the current route
matches the property. See react router api for
details of how the route matching works.
properties
- title - [string] a short, descriptive title for the panel
- value - [string] an identifier that uniquely identifies this panel
- icon - [string] the name of the material-icons icon to use for the panel
- route - [string] optional route path. The panel will only be visible if the current route matches
example
import React from "react";
import framework from "nqm-app-framework";
// Import app-specific bar panels
import AppMenu from "../containers/app-menu";
import AppOptions from "../containers/app-options";
// Sidebar framework
const SideBarContent = framework.ui.SideBarContent;
const SideBarPanel = framework.ui.SideBarPanel;
const AppSideBar = () => {
return (
<SideBarContent>
<SideBarPanel title="menu" value="menu" icon="apps">
<AppMenu />
</SideBarPanel>
<SideBarPanel title="resource options" value="resource" icon="settings" route="/resource">
<AppOptions />
</SideBarPanel>
</SideBarContent>
);
};
NotificationUi
Snackbar style notification area, similar to the notifactions section on the TDX. Supports fire and forget actions as well as longer running notification chains.
example
// Container
const depsMapper = (context, actions) => ({
addNotification: actions.notification.addNotification,
updateNotification: actions.notification.updateNotification,
});
// addNotification
// autoExpire is a boolean, if true then message will display for 4 seconds or, if specified, the duration in milliseconds then automatically be removed, otherwise it will remain until marked as finished with updateNotification
const id = addNotification("message", autoExpire, (duration));
// The returned id may be used to update an existing notification, finished is a boolean, if true the notification will be removed 4 seconds after the message is updated
updateNotification(id, "Progress", finished);
const id = addNotification(`Deleting resource ${resourceId}`);
tdxApi.deleteDatasetAsync(resourceId)
.then(() => {
updateNotification(id, `Deleted resource ${resourceId}`, true);
})
.catch((err) => {
updateNotification(id, `Failed to delete resource ${resourceId}: ${err.message}`, true);
});
data loaders
The framework encourages separating the data loading aspects of an app from it's UI components. Doing this involves 3 types of 'mapping' data from various data sources to properties of the component. These data sources are:
- application context - application-wide context, such as access to the TDX connection manager and the application state store.
- application state - the current state of the application, usually created as a result of user actions such as the active view or selected sort order etc. Application state is not usually persisted and should not be used to store 'domain data'.
- domain data - the data required by the application, usually residing in the TDX, such as LSOA lists, region boundaries, sensor definitions and latest readings etc.
The idea is for each component to define the data it requires using React properties. The component does not concern itself with where that data comes from or how it is loaded or saved. This approach ensures components are self-contained and able to be shared across applications and/or domains if required. It also makes it possible to write tests for the component in a generic way.
Component containers are used to 'map' data from the various data sources described above to the properties defined by the component.
compose
This is a general purpose helper function that provides the 'glue' between the mapping function you provide and the component properties.
merge
This is a general-purpose helper that lets you map data to your component properties using more than one data source, by chaining together one or more compose invocations.
useDeps
Short for 'use dependencies', use this function to declare the aspects of application context your component container needs to access in order to fulfill the data source to property mappings.
trackerFactory
A factory function that creates a reactive wrapper around your data loading function. See the Meteor Tracker documentation for more information about Meteor reactivity.
reduxFactory
For example, if your component has a 'sortOrder' property you probably want to synchronise it with the currently selected sort order from application state.