roadhouse
v1.3.0
Published
Angular Admin CRUD Tools
Downloads
13
Readme
roadhouse
Angular Admin CRUD Tools
Why
At Vokal we often build CRUD UI for the administration of our API services with Angular. There are certain patterns those show up pretty universally for these admin sites. Roadhouse is a set of patterns encapsulated in Angular directives and services that remove all repetitive HTML for these admin sites.
Roadhouse relies heavily on JavaScript objects for data definitions, but provides the flexibility needed to build most practical CRUD interfaces.
Requirements
The HTML that Roadhouse creates is designed to be used with Bootstrap CSS, but doesn't assume the use of any of the Bootstrap JavaScript components. The subset of CSS from bootstrap that Roadhouse uses is actually quite small and would be reasonably easy for someone to optimize.
There are a few relatively light npm dependencies:
- alertify.js
- angular-datetime
- angular-slugify
- ng-dialog
Directives
rh-field
Binds models to appropriate field types.
rh-form
Wraps a number of rh-fields to display an editable record with some buttons to potentially create, update, and delete records.
rh-valid
Attach validation functions to fields, used by rh-field in particular.
rh-table
The table list view for record sets. Essentially creates the required columns, column headings, and binds cell contents for loaded records.
rh-paginator
A simple paginator that displays a list of numbers corresponding to pages to show in an rh-table and when needed left and right overflow arrows indicating additional pages exist.
Record Definition Object
In Roadhouse, the relationship between the records returned from the database and displayed in the UI is in a definition object.
The simplest possible definition would look like this and allow create, update, and delete for a record with just one field named field1:
{
field1: {}
}But all these options are available:
{
meta: { // meta is a specially named key
title: String,
refreshOnSave: Boolean,
canCreate: Boolean | Function that returns Boolean | Object { max: Number },
canEdit: Boolean | Function that returns Boolean,
canDelete: Boolean | Function that returns Boolean,
editHeading: String,
deleteHeading: String
},
field1: { // all other key names match the field name from the API
name: String,
canView: Boolean | Function that returns Boolean,
canEdit: "initial" | "not-initial" | "empty" | Boolean | Function that returns Boolean,
canViewList: Boolean,
required: Boolean,
type: String, any valid HTML input type,
"default": String | Boolean | Number,
options: Array of Objects { value: String | Boolean, name: String },
validate: Promise,
href: String | Function that returns String,
pattern: String of regular expression,
placeholder: String,
fieldDirective: String,
fieldAttrs: Object, additional attributes and values to add to `rh-field`
},
fieldN: { ... },
...
}Meta Definition
title
String | Default: ""
Title is displayed as a header in rh-form and on the button to add a record.
refreshOnSave
Boolean | Default: true
Whether the value returned on POST or PUT will be used to refresh the record on the web. This is particularly useful for fields generated on the API.
canCreate
Boolean, Function that returns Boolean, Object { max: Number } | Default: true
Whether a record can be added.
canEdit
Boolean, Function that returns Boolean | Default: true
Whether a record can be edited.
canDelete
Boolean, Function that returns Boolean | Default: true
Whether a record can be deleted.
editHeading
String | Default: ""
The heading of the edit column in rh-table when it is visible.
deleteHeading
String | Default: ""
The heading of the delete column in rh-table when it is visible.
Field Definition
name
String | Default: Name of key
The name is used for rh-table column headings and rh-field labels.
canView
Boolean, "edit", Function that returns Boolean | Default: true
Whether the field can be viewed. The most common reason this would be set to false is to hide ID fields. When "edit" the field is only visible when editing, so not during creation.
canEdit
"initial", "empty", Boolean, Function that returns Boolean | Default: true
Whether the field can be edited. When set to false an rh-field will be disabled. When "initial" the field is only editable during rh-form record creation, and the opposite for "not-initial". When "empty" the field is only editable if it starts out empty when editing.
canViewList
Boolean | Default: true
Whether the field is displayed in rh-table.
required
Boolean | Default: false
Whether the field is required by rh-field validation.
type
String | Default: "text"
The field type for the rh-field. Options include any valid value for the type of an HTML input element, and "select". Certain values will trigger custom pattern values or different input controls. A checkbox will be rendered as a "yes" and "no" button, while a "date" type will use the angular-datetime directive.
default
String, Boolean, Number | Default: null
The default value for an empty field.
options
Array of Objects { value: String | Boolean, name: String } | Default: null
rh-field can display a list of discrete options instead of a text input using a list of buttons. To use a select field instead, type should be set to "select". If you have more than five options, you likely want to use type: "select" for design reasons, but there is no enforced limit.
validate
Promise | Default: null
rh-field can delegate complex validations to rh-valid using a supplied promise. A promise is used to allow for validations that might require network calls to complete, but can also be used to do any field comparison, or whatever else is needed.
href
String, Function that returns String | Default: null
rh-table will display a link if href is provided. To include values from the current row, the property name is item, so a link to a page with the id of the current widget might be href: "widget/{{ item.id }}".
pattern
String or a regular expression | Default: null
rh-field can use custom patterns for field validation.
placeholder
String | Default: null
rh-field can display a placeholder.
fieldDirective
String | Default: null
Add a directive to the rh-field by injecting this value directly into a <div>. For example, if you'd like to use the directive angular-years-and-days to edit the field value instead of a standard HTML input type and the data type should be seconds, use fieldDirective: 'years-and-days data-as-seconds="true"'. This will cause rh-field to be a <div> instead of an input type and will write that text directly into the tag.
fieldAttrs
Object | Default: null
Additional attributes to be added to rh-field
Basic HTML
Each of the directives in Roadhouse can be used individually, but to create a typical paged table, create HTML like this.
<div rh-table
rh-definition="list.definition"
rh-list="list.list"
rh-on-create="list.create"
rh-on-update="list.update"
rh-on-delete="list.delete"
rh-loading="list.loading"></div>
<div rh-paginator
rh-paginator-page-count="list.pageCount"
rh-paginator-current-page="list.currentPage"
rh-paginator-on-page-selected="list.getList"></div>In the above:
rh-definitionis the definition object, explained aboverh-listis an array of recordsrh-on-createis a callback functionrh-on-updateis a callback functionrh-on-deleteis a callback functionrh-loadingis a boolean to tell the table whether loading is in progress, this is important so the table knows whether records are loading or the list is loaded with no records as both statuses are displayed for the userrh-paginator-page-countis the total number of pagesrh-paginator-current-pageis the current page numberrh-paginator-on-page-selectedis a callback function
The controllers should generally be as simple as:
- Set
rh-loadingto true, request the list from the API, assign results torh-list, assign page count resultsrh-paginator-page-count, and setrh-loadingto false - Use
rh-on-createto POST the item to the API - Use
rh-on-updateto PUT the item to the API - Use
rh-on-deleteto DELETE the item from the API
For example:
ctrl.list = [];
ctrl.definition = {...};
ctrl.getList = function ()
{
ctrl.loading = true;
return API.getList()
.then( function ( response )
{
ctrl.list = response.data.results;
ctrl.loading = false;
},
function ()
{
alertify.error( "The list could not be loaded" );
ctrl.loading = false;
} );
};
ctrl.create = function ( item ) { /* POST item to API */ };
ctrl.update = function ( item ) { /* PUT item to API */ };
ctrl.delete = function ( item ) { /* DELETE item from API */ };
ctrl.getList(); // on load