@momsfriendlydevco/macgyver

Dynamic form widgets for Angular

Usage no npm install needed!

<script type="module">
  import momsfriendlydevcoMacgyver from 'https://cdn.skypack.dev/@momsfriendlydevco/macgyver';
</script>

README

MacGyver

Dynamic form widgets for Angular.

MacGyver is a form designer for Angular that works by being given a form definition and a data set.

LIVE DEMO

Example

angular
    .module('macgyver')
    .component('myComponent', {
        controller: function() {
            var $ctrl = this;

            $ctrl.data = {}; // This will be populated by MacGyver

            $ctrl.form = { // Our form specification
                type: "mgContainer",
                items: [
                    {
                        id: "textInput",
                        type: "mgText",
                        title: "Example Text",
                    },
                    {
                        id: "toggleControl",
                        type: "mgToggle",
                        default: true,
                    },
                ],
            };
        },
        template: `
            <mg-form config="$ctrl.form" data="$ctrl.data"></mg-form>
        `,
    });

Editing a form

MacGyver also ships with a form editor. To use simply make a HTML page with the mgFormEditor component for an interactive UI.

<mg-form-editor config="$ctrl.form" data="$ctrl.data"></mg-form-editor>

API Methods

MagGyver works by using Angular's template system to nest widgets with two-way data binding.

Creating MacGyver widgets

Each MagGyver widget begins with mg and should be registered via $macgyver.register().

angular
    .module('macgyver')
    .config($macgyverProvider => $macgyverProvider.register('mgText', {
        title: 'Textbox',
        icon: 'fa fa-pencil-square-o',
        config: {
            placeholder: {type: 'mgText', help: 'Ghost text to display when the textbox has no value'},
        },
    }))
    .component('mgText', {
        bindings: {
            config: '<', // Config for this widget
            data: '=', // Data state for this widget
        },
        controller: function($macgyver, $scope) {
            var $ctrl = this;
            $macgyver.inject($scope, $ctrl);

            // Adopt default if no data value is given
            $scope.$watch('$ctrl.data', ()=> { if (_.isUndefined($ctrl.data) && _.has($ctrl, 'config.default')) $ctrl.data = $ctrl.config.default });

            // Optionally respond to validation requests
            $ctrl.validate = ()=> {
                if ($ctrl.config.required && !$ctrl.data) return `${$ctrl.config.title} is required`;
            };
        },
        template: `
            <input ng-model="$ctrl.data" type="text" class="form-control" placeholder="{{$ctrl.config.placeholder}}"/>
        `,
    })

$macgyver

The main service / provider within Angular.

NOTE: $macgyverProvider and $macgyver are the same. The provider is available as an alias to allow registration of components during the Angular Config phase.

$macgyver.register(id, [properties])

Register a widget for use by name. Each id should begin with id and be in camelCase form.

Widgets can contain the following meta properties:

Property Type Default Description
config Object none Object detailing each optional property the widget can take as a mgContainer specification
icon string none The icon CSS class to use in the mgFormEditor UI
isContainer boolean false Indicates that the widget can contain other widgets (under the items array)
isContainerArray boolean false Addition to isContainer that indicates the widget will contain an array of rows (like a table)
template string <COMPONENT_NAME/> Rendering template to be used to draw the element
title string The ID via _.startCase() The human friendly title of the widget
userPlaceable boolean false Whether to hide the object from the user in the mgFormEditor UI

$macgyver.getForm($scope)

Finds and returns the component scope of the first available form under the given scope.

$macgyver.inject($scope, $ctrl)

Function used by MacGyver components to register themselves against the standard event hooks during initialization.

$macgyver.getDataTree(root, [useDefaults=false])

Generate an empty data entity from a given widget container. This function can be used to return the 'blank' form contents. If useDefaults == true the defaults for each widget will be set as the value.

$macgyver.neatenSpec(spec)

Attempt to neaten 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.

$macgyver.specDataPrototype(spec)

Create an empty data structure based on the specification. This is really just used to make sure that the deeply nested objects-within-objects (or arrays) are present when Angular tries to bind to them.

$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.

API Events

MacGyver components are also expected to optionally respond to the following events:

mg.get(event, register)

Used by MacGyver to 'ping' controls. Each control is expected to populate the register object with its ID and $ctrl instance. This event is automatically responded to if the component calls $macgyver.inject() during its init cycle. If the component does not respond higher-level events such as validation will not be able to reach the component.

mg.getForm(event, form)

Used by MacGyver during $macgyver.getForm() calls to retrieve the forms under the scope. The responding form is expected to populate the form.form object with its controller instance.