xpfw

xpfw is a collection of packages geared towards turning your JSON-Schema into a React UI. Check out the live demo to get a quick practical overview.

Customization

xpfw is all about custimziation. You can connect @xpfw/data to any backend. @xpfw/form and @xpfw/data also both provide React Hooks for easy themability.

React Native

XPFW supports react native. An example application can be found here.

Usage

To render a field use the SharedField component and pass it your JSON-Schema.

Click here to see a live demo of @xpfw/form

import { SharedField } from "@xpfw/form"
import { registerComponents } from "@xpfw/form-bulma"
// Making sure that @xpfw/form-shared can find components
registerComponents()
const StaticFieldRenderer = () => <SharedField field={{mapTo: "myField", type: FieldType.Text}} />

If you require readily usable create / edit pages and more check out @xpfw/data.

Usage

To render a field use the SharedField component and pass it your JSON-Schema.

Click here to see a live demo of @xpfw/form

import { SharedField } from "@xpfw/form"
import { registerComponents } from "@xpfw/form-bulma"
// Making sure that @xpfw/form-shared can find components
registerComponents()
const StaticFieldRenderer = () => <SharedField field={{mapTo: "myField", type: FieldType.Text}} />

If you require readily usable create / edit pages and more check out @xpfw/data.

Theming

Theming a validation type is possible by writing a React Component and registering it in the ComponentRegistry via registerComponent

import { ComponentRegistry } from "@xpfw/form"
ComponentRegistry.registerComponent("number", "guided", GuidedNumbersField)

With this we have registered a theme named guided for the Number-FieldType. To render a number field with said theme pass the theme-property to a SharedField.

import { SharedField } from "@xpfw/form"

const RenderThemed = () => <SharedField 
  schema={{title: "myGuidedNumber", type: "number"}}
  theme="guided"
/>

Click here to see a live demo of this example field component

This is the code for the registered component.

import { ComponentRegistry, IFieldProps, memo, useFieldWithValidation } from "@xpfw/form"
import { observer } from "mobx-react"
import * as React from "react"

const GuidedNumbersField: React.FunctionComponent<IFieldProps> = observer((props) => {
  const fieldProps = useFieldWithValidation(props.schema, props.mapTo, props.prefix)
  const memoVals = [props.mapTo, props.prefix, JSON.stringify(props.schema)]
  const randomize = memo(() => () => fieldProps.setValue(Math.round(Math.random() * 100)), memoVals)
  React.useEffect(randomize, memoVals)
  return (
    <div className="is-flex centerJustify has-text-centered is-size-5 marginTop marginBottom">
      <a className="button" onClick={() => {
        fieldProps.setValue(fieldProps.value - 1)
      }}>Decrease</a>
      <span style={{marginRight: "1rem", marginLeft: "1rem"}}>Value of <i>{props.schema.title}</i> is: <b>{fieldProps.value}</b></span>
      <a className="button" onClick={() => {
        fieldProps.setValue(fieldProps.value + 1)
      }}>Increase</a><a style={{marginLeft: "1rem"}} className="button" onClick={randomize}>Randomize</a>
    </div>
  )
})

Usage

@xpfw/data packages provide 4 hooks. One for each of the CRUD operations.

Click here to see a live demo of @xpfw/data

Quick Start

If you want a full CRUD UI from a JSON-Schema out of the box you can use use @xpfw/data-bulma. For custom styling you can use the hooks in your own components! The parameters are minimal: They all require an ExtendedJSONSchema and optionally take a prefix. If it's an edit, show or delete you also need to supply an id. If it's a show you don't even need a form just the collection name.

import {
  BulmaCreate, BulmaEdit, BulmaDelete, BulmaShow, BulmaList
} from "@xpfw/data-bulma"

// replace with your own
const form =  {
  model: "testModel",
  collection: "testCol",
  sections: [{fields: [{
    mapTo: "myString",
    type: FieldType.Text,
    validate: {required: {type: RequiredType.Always}}
  }]}]
}

<BulmaCreate schema={schema} prefix="optionalPrefix" />
<BulmaEdit schema={schema} prefix="optionalPrefix" id="idOfEditedItem" />
<BulmaDelete schema={schema} prefix="optionalPrefix" id="idOfEditedItem" />
<BulmaShow schema={schema} id="idOfItem" />
<BulmaList schema={schema} prefix="optionalPrefix" />

Hooks

The @xpfw/data-bulma package was built to provide a quick start. Most probably you will want to customize the components and let @xpfw/data only focus on providing the data. For every of the 4 CRUD operation's there is a React hook as well as for lists and authentication (login / registration / logout):

Create, Read, Update, Remove, List, Authentication

For complete code examples of wrapped components you can look at the implementations used for the demo site:

Create, Read, Update, Remove, List

Create

Example usage of the useCreate Hook can be found here Expects:

  • schema => ExtendedJSONSchema conform JSON-Schema
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path Provides the following properties:
  • submitCreate => Call this and the create call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields
  • user => currently logged in user

Read

Example usage of the useGet hook can be found here Expects:

  • id => The ID of the requested record
  • collection => The name of the collection to fetch the record from Provides the following properties:
  • item => either null or the requested object from collection and id combination
  • loading => boolean indicating business

Edit

Example usage of the useEdit hook can be found here Expects:

  • id => The ID of the record to edit
  • schema => ExtendedJSONSchema conform JSON-Schema. The collection property is required if used in this hook!
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path Provides the following properties:
  • submitEdit => Call this and the edit call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields
  • user => currently logged in user

Delete

useRemove Example usage of the useRemove hook can be found here Expects:

  • id => The ID of the record to remove
  • schema => ExtendedJSONSchema conform JSON-Schema. The collection property is required if used in this hook! Provides the following properties:
  • submitRemove => Call this and the remove call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields

List

Example usage of the useList Hook can be found here Expects:

  • schema => ExtendedJSONSchema conform JSON-Schema
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path
  • (optional) options => optional advanced options Provides the following properties:
  • nextPage => Increase page count, load content of next page and hence change content of list
  • prevPage => Decrease page count, load content of previous page and hence change content of list
  • list => Array of found items you can render
  • currentPage => null if none otherwise array of invalid fields
  • maxPage => null if none otherwise array of invalid fields
  • showNextPage => convenience prop to know whether there are more pages available or not based on comparison of currentPage and maxPage
  • showPrevPage => convenience prop to know whether there are earlier pages available or not based on comparison of currentPage and maxPage

Authentication

The useAuth hook provides information about the current user. It provides the following properties:

  • submitLogin => Call this and the login will be called with the value of MailField and PwField. error, user and loading will update respectively
  • submitLogout => Call this and the logout will be called with the value of MailField and PwField. error, user and loading will update respectively
  • submitRegister => Call this and the registration will be called with the value of MailField and PwField. error, user and loading will update respectively
  • loggedIn => true if successfully logged in
  • connected => Whether an active connection to the server exists
  • loading => whether user login/out/register related activites are ongoing
  • user => Currently logged in user. Null if not logged in
  • error => null if none otherwise array of invalid fields

Usage

@xpfw/data packages provide 4 hooks. One for each of the CRUD operations.

Click here to see a live demo of @xpfw/data

Quick Start

If you want a full CRUD UI from a JSON-Schema out of the box you can use use @xpfw/data-bulma. For custom styling you can use the hooks in your own components! The parameters are minimal: They all require an ExtendedJSONSchema and optionally take a prefix. If it's an edit, show or delete you also need to supply an id. If it's a show you don't even need a form just the collection name.

import {
  BulmaCreate, BulmaEdit, BulmaDelete, BulmaShow, BulmaList
} from "@xpfw/data-bulma"

// replace with your own
const form =  {
  model: "testModel",
  collection: "testCol",
  sections: [{fields: [{
    mapTo: "myString",
    type: FieldType.Text,
    validate: {required: {type: RequiredType.Always}}
  }]}]
}

<BulmaCreate schema={schema} prefix="optionalPrefix" />
<BulmaEdit schema={schema} prefix="optionalPrefix" id="idOfEditedItem" />
<BulmaDelete schema={schema} prefix="optionalPrefix" id="idOfEditedItem" />
<BulmaShow schema={schema} id="idOfItem" />
<BulmaList schema={schema} prefix="optionalPrefix" />

Hooks

The @xpfw/data-bulma package was built to provide a quick start. Most probably you will want to customize the components and let @xpfw/data only focus on providing the data. For every of the 4 CRUD operation's there is a React hook as well as for lists and authentication (login / registration / logout):

Create, Read, Update, Remove, List, Authentication

For complete code examples of wrapped components you can look at the implementations used for the demo site:

Create, Read, Update, Remove, List

Create

Example usage of the useCreate Hook can be found here Expects:

  • schema => ExtendedJSONSchema conform JSON-Schema
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path Provides the following properties:
  • submitCreate => Call this and the create call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields
  • user => currently logged in user

Read

Example usage of the useGet hook can be found here Expects:

  • id => The ID of the requested record
  • collection => The name of the collection to fetch the record from Provides the following properties:
  • item => either null or the requested object from collection and id combination
  • loading => boolean indicating business

Edit

Example usage of the useEdit hook can be found here Expects:

  • id => The ID of the record to edit
  • schema => ExtendedJSONSchema conform JSON-Schema. The collection property is required if used in this hook!
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path Provides the following properties:
  • submitEdit => Call this and the edit call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields
  • user => currently logged in user

Delete

useRemove Example usage of the useRemove hook can be found here Expects:

  • id => The ID of the record to remove
  • schema => ExtendedJSONSchema conform JSON-Schema. The collection property is required if used in this hook! Provides the following properties:
  • submitRemove => Call this and the remove call will be called. state, loading and error will update respectively
  • state => Status of the creation
  • loading => boolean indicating business
  • error => null if none otherwise array of invalid fields

List

Example usage of the useList Hook can be found here Expects:

  • schema => ExtendedJSONSchema conform JSON-Schema
  • (optional) mapTo => use for FormStore path instead of schema.title
  • (optional) prefix => prepend to FormStore path
  • (optional) options => optional advanced options Provides the following properties:
  • nextPage => Increase page count, load content of next page and hence change content of list
  • prevPage => Decrease page count, load content of previous page and hence change content of list
  • list => Array of found items you can render
  • currentPage => null if none otherwise array of invalid fields
  • maxPage => null if none otherwise array of invalid fields
  • showNextPage => convenience prop to know whether there are more pages available or not based on comparison of currentPage and maxPage
  • showPrevPage => convenience prop to know whether there are earlier pages available or not based on comparison of currentPage and maxPage

Authentication

The useAuth hook provides information about the current user. It provides the following properties:

  • submitLogin => Call this and the login will be called with the value of MailField and PwField. error, user and loading will update respectively
  • submitLogout => Call this and the logout will be called with the value of MailField and PwField. error, user and loading will update respectively
  • submitRegister => Call this and the registration will be called with the value of MailField and PwField. error, user and loading will update respectively
  • loggedIn => true if successfully logged in
  • connected => Whether an active connection to the server exists
  • loading => whether user login/out/register related activites are ongoing
  • user => Currently logged in user. Null if not logged in
  • error => null if none otherwise array of invalid fields

IBackendClient

Connecting your own backend to xpfw is done by implementing the IBackendClient interface.

The implementation of the NeDB and feathers client offer a great example on how to do on and offline backends!

For an offline plugin you only need to implement

  • get
  • create
  • remove
  • find
  • patch

For an online plugin you also need to implement

  • connectTo
  • disconnect
  • login
  • register
  • logout

Since xpfw is strongly typed your IDE should tell you what parameters to expect. In case you are not using typescript see an abstract from the API docs below.

Stacks

XPFW was mainly developed with feathers and NeDB in mind. Nevertheless it was written so that it can be easily connected to any backend via the IBackendClient interface. The implementation of the NeDB and feathers client offer a great example on how to do on and offline backends!

Frontend

xpfw was initially written for feathers, but later this was exchanged by IBackendClient to prevent lock-in.

If you have a working feathers backend you can use @xpfw/data-feathers like this

import { BackendClient, UserStore } from "@xpfw/data"
import { FeathersClient } from "@xpfw/data-feathers"

BackendClient.client = FeathersClient

BackendClient.client.connectTo("yourBackend.com", {
    // For react-native replace with AsyncStorage
    authOptions: {storage: get(global, "window.localStorage")},
    userStore: UserStore
})

Backend

If you want to use feathers as backend with xpfw we provide the @xpfw/feathers-hooks package. Add the permission and validate hooks to your database services and you're all set!

import { permission, validate } from "@xpfw/feathers-hooks"
import { Application } from "@feathersjs/feathers"

// use with app.configure(xpfwConfiguration)
const xpfwConfiguration = (app: Application) => {
  const service = app.service("users")
  service.hooks({
    before: {create: [
        permission.create(permissions),
        validate.general(schema, "create")
    ]}})
}

import { IPermissionSchema, Permission } from "@xpfw/permission"
import { ExtendedJSONSchema } from "@xpfw/form"
const permissions: IPermissionSchema = {
  required:{
    create:Permission.Public,
    update:Permission.User
  }
}
const schema: ExtendedJSONSchema = {
  title: "userModel",
  collection: "users",
  properties: {
    "email": {type: "string"},
    "password": {type: "string"}
  }
}

NeDB

If you want to write an offline application @xpfw/data-nedb is the adapter you are looking for.

Overwrite the client property of BackendClient and your xpfw powered offline application is ready to go.

import { BackendClient } from '@xpfw/data';
import NedbClient from '@xpfw/data-nedb';
BackendClient.client = NedbClient

Testing

xpfw delivers premade tests which you can include to get increase code coverage out of your custom styled components!

All tests follow the same "API". It is an exported function, which you give your readily usable React Component. That will be rendered then.

Form Tests

For tests regarding form component themes there is @xpfw/form-tests It contains the following tests:

  • booleanTest
  • stringTest
  • arrayTest
  • dateTest
  • locationTest
  • objectTest
  • selectTest

Data Tests

For tests regarding data / CRUD components there is @xpfw/data-tests It provides the following tests:

  • authTest
  • createTest
  • editTest
  • removeTest
  • showTest
  • listTest
  • relationshipTest

Caution! MongoDB required

A running MongoDB instance on localhost is required to run the data tests!