MacGyver
Dynamic form widgets for Vue.
MacGyver is a component library and dynamic form designer for Vue.
It can function either as a simple set of powerful independent components or by using a powerful JSON form layout definition.
Prerequisites
This package requires the following as pre-requisite dependencies in the parent:
These are listed as peerDependencies
in package.json.
Example
<mg-form
:data="{}" // Populated as the control values change
:config="{ // Our form specification
type: "mgContainer",
items: [
{
id: "textInput",
type: "mgText",
title: "Example Text",
},
{
id: "toggleControl",
type: "mgToggle",
default: true,
},
],
}"
@change="data = $event"
/>
or use MacGyver widgets as standalone Vue components:
<mg-number
:value="formData.number"
@change="formData.number = $event"
:min="0"
:max="100"
/>
Creating MacGyver widgets
Each MagGyver widget begins with mg
and should be registered via Vue.mgComponent(name, definitionObject)
.
Vue.mgComponent('mgText', {
meta: {
title: 'Textbox',
icon: 'fa fa-pencil-square-o',
},
props: {
placeholder: {type: 'mgText', help: 'Ghost text to display when the textbox has no value'},
},
}));
The Vue.mgComponent()
function processes the MacGyver widget definition and automatically calls Vue.component()
to register the Vue counterpart (after converting prop types and adding other syntax glue to the original object).
Widget setup
MacGyver widgets follow the regular Vue component syntax with some additions:
Property | Type | Default | Description |
---|---|---|---|
meta |
object |
{} |
Meta information object - used by the form editor to configure the widget |
meta.id |
string |
Computed from name | The name of the macgyver component. Always camelcased and prefixed with mg e.g. mgText
|
meta.title |
string |
The ID via _.startCase()
|
The human friendly title of the widget |
meta.icon |
string |
"far fa-rectangle-wide" |
The icon CSS class to use in the mgFormEditor UI |
meta.shorthand |
array |
[] |
Other aliases the widget answers to in shorthand mode |
meta.category |
string |
"Misc" |
Human readable category to add the widget to in the editor |
meta.preferId |
boolean |
false |
Whether the component should be auto-allocated an ID if the user doesn't provide one |
meta.format |
boolean / function
|
true |
How to return the short value of the widget, see NOTES |
meta.formatClass |
string |
"" |
CSS classes to attach when rendering the format value, typically used for text alignment |
props |
object |
{} |
Vue props definition |
props.$type |
string |
Name | The MacGyver type (e.g. mgText ) |
props.$dataPath |
string |
Computed | Dotted notation path within the form of the value to set when this.data changes |
props.$specPath |
string |
Computed | Dotted notation path of the widget spec within the parent $mgForm layout |
props.change |
function |
none | Function to execute when the value changes |
props{}.value |
* |
none | If using the widget as a standalone, set the initial value |
props{}.title |
string |
Prop ID via _.startCase()
|
Human readable name for the property in the editor |
props{}.default |
* |
undefined |
Regular property default value |
props{}.type |
string |
none | Unlike Vue props, this should be an mg* widget with its config |
props{}.vueType |
string / array
|
Computed | Type validator to pass to view, this should be an array or string, not native types |
props{}.help |
string |
"" |
Additional help text in the editor |
props{}.advanced |
boolean |
false |
Label the property for advanced users only |
props{}.* |
* |
none | All other MacGyver widget config for the props{}.type widget |
childProps |
object |
none | All properties that are inherited by direct child components within a container |
inject |
array / object
|
['$mgForm'] |
What to inject into each MacGyver component, overridable if needed |
data |
function |
none | Data to provide to the component, this.data is always available |
methods |
object |
{} |
Additional methods to provide, the below methods are always available |
methods.mgSetup |
function |
See code | Called during the created lifecycle hook to populate the data default and setup bindings |
created |
function |
none | Creation hook, superclassed to call mgSetup() . If Specified it is called after this |
* |
* |
none | Various other Vue lifecycle hooks and properties, all are carried into the Vue component |
NOTES:
-
meta.format
returns the human readable value of the component when rendered in tables - iftrue
the value is used unchanged. If a function it is called as(value, config)
and can return a scalar string or a Promisable value which should resolve into a string. -
props{}.type
should always be a MacGyver component + config so it can be correctly displayed in the editor. Passing JS primative classes (e.g.{foo: Number}
) will raise a warning message and is discouraged for anything other than testing -
props{}.advanced
can be used to hide more complex properties unless the editor is in advanced editing mode -
props{}.vueType
can override the "guessed" type to pass to Vue as a prop validator. Pass strings or arrays of strings not native types - for example{vueType: 'string'}
or{vueType: ['array', 'number']}
are both correct,{vueType: String}
is not supported -
data()
is called as per the usual Vue setup process, however thedata
value is always provided and is watched by MacGyver for changes. Mutate this value to set new values or emitmgChange
if needed -
childProps
sets up the properties inherited into the$props
setup by direct children. Items such astitle
are used by elements such asmgContainer
to render the title of the widget and not the widget itself
Injectables
The following injectables can be subscribed to within each component:
Injectable | Description |
---|---|
$mgForm |
The optional parent mgForm component, automatically applied if vm.inject isn't overriden |
$mgFormEditor |
The optional wrapping mgFormEditor component, if present. This is outside the mgForm parent, its presense can be used to determine whether to display special edit component features |
To specify optional injectables (e.g. that $mgFormEditor
may be included but is not essencial) use:
{
inject: {
$mgForm: {from: '$mgForm', default: false},
$mgFormEditor: {from: '$mgFormEditor', default: false},
},
}
NOTES:
-
$mgForm
is only injected if the component is used inside a<mg-form/>
component, otherwise its falsy. This usually occurs when the component is used as a standalone e.g.<mg-button/>
-
$mgFormEditor
is only injected if the form is wrapped within a editor context, check for its truthy status to determine if the widget should apply editable behaviours
Events
Event | Cardinality | Params | Description |
---|---|---|---|
change |
Optional | (*) |
Emitted when the widget is a standalone component and its value has changed |
mgChange |
Always | ({path, value}) |
Used to respond to changes in the component state |
mgIdentify |
Always | (reply <function>) |
Used to retrieve all components, component will reply with its VueComponent instance |
mgRefresh |
Always | () |
Used to force refresh the state of a component |
mgRefreshForm |
Always | () |
Used to force refresh the state of all components in a form |
mgValidate |
Optional | (reply <function>) |
Used to validate each component, each component should call reply() with string errors if validation fails |
NOTES:
- All of the above events are automatically added by the
mgSetup()
method when the component is created
Component Skeleton
In essence the following base skeleton is used for any component that is instantiated via Vue.mgComponent(name, spec)
.
This contains all the injectables, default data props, default events and other hooks.
This structure can be considered a Vue.mixin()
/ _.merge()
equivalent where all items are deep merged.
Vue.mgComponent('mgAcmeComponent', {
meta: {
title: 'mgAcmeComponent', // Calculated via _.startCase() from the name
icon: 'far fa-rectangle-wide',
category: 'Misc',
preferId: false,
shorthand: [],
format: true,
formatClass: '',
},
inject: {
$mgForm: {from: '$mgForm', default: false}, // Will be falsy if the component is stand-alone
},
props: {
$dataPath: {type: String}, // Default is set when component is populated (when inside an mg-form)
$specPath: {type: String}, // Default is set when component is popualted (when inside an mg-form)
value: {}, // Default is populated when a stand-alone component
},
methods: {
mgSetup() { // Function which sets up event handlers and data watcher
// ... //
},
},
created() {
this.mgSetup(); // Called on every create
// ... // // component created() lifecycle hook is called here
},
// ... // // Remaining component properties, methods and lifecycle hooks
});
API
$macgyver.compileSpec(spec)
Attempt to compile up a 'rough' MacGyver spec into a pristine one.
This function performs various sanity checks on nested elements e.g. checking each item has a valid ID and if not adding one.
It returns an object composed of the form {spec}
$macgyver.widgets
An object containing data on each valid MacGyver widget registered. If running on the front-end this is updated as new widgets register themselves. On the backend this uses the computed version located in ./dist/widgets.json
.