Home

Awesome

📋 Vue Described-Data Form

A fresh way to create simple or complex forms – without programming!

🕹 Demo

Try it yourself in a Live Demo!

📖 About

vue-dd-form aims to reduce the of time creating forms, while keeping the flexibility and data-model persistence. This project is a first implementation of the Described-Data approach.

What is Descibed-Data?

Described-Data is a concept inspired by the JSON Schema. Basically, each node in the data set is assigned a view (input), which it then represents on the UI. In practise, vue-dd-form only needs your data and descriptions to render itself!

"I use a JSON Schema, what is the difference?"

🛠️ Getting started

  1. Install the package
npm install --save vue-dd-form
  1. Import the component
import ddForm from 'vue-dd-form';
components: {
  ddForm,
}
  1. And use it!
<dd-form
    :data="exampleDataSet"
    :descriptions="exampleDescriptions"
    @submit="submit">
</dd-form>

...inside your data() tag

  exampleDataSet: {},
  exampleDescriptions: {
	  "name": { view: 'text', label: 'Full name'}
  }

Output of this simple example looks like this:

⬇️ More about all params & events below.


🔍 Usage

Descriptions

Your JS Data is described by another JS Object targeting all nodes which should be rendered on the screen. Imagine a data model of a IMDB movie:

{
   author: {
      name: "J. Cameron",
      birth: "2020-09-08T12:11:03.332Z",
      active: true
    }
    title: "Titanic",
    description: "Lorem ipsum ...",
    language: "English",
    genres: ["Action", "Drama"]
}

...the descriptions could look something like this:

{
  author: { view: "group", label: "Author" },
  author.name: { view: "text", label: "Name of author" },
  author.birth: { view: "datetime", label: "Date of birth" },
  author.active: { view: "tick", label: "Is the author still active?" },
  title: { view: "text", label: "Movie title" },
  description: { view: "area", "label": "Description" },
  genres: { view: "checkbox", "label: "Genres", options: ["Action", "Comedy", "Drama"] },
  language: { view: "select", label: "Original language", "options: ["English", "Spanish", "Korean"] }
}

Description paths

So as you can see nodes are targeted by a dotted notation. Each description object has a required value of view, which specifies, to which UI element should the node be rendered. Currently there is 11 basic view types, though you can specify your own (see Custom views).

Wildcard paths

If you want to describe every array's child, you can use the wildcard path. Look closely at this data set:

{
    programme: [
	    // Day 1
          {
            header: 'Friday',
            subheader: '25th September 2020',
            items: [
            // Item 1
              {
                title: 'Arrival',
                subtitle: 'please be on time',
                time: '9AM'
              },
            // Item 2
			 {
                title: 'Workshop 1',
                subtitle: 'hosted by John Doe',
                time: '11AM'
             }
             // Item 3 ...
          },
	    // Day 2 ...
        ],
}

The wildcard paths can be leveraged like this:

programme: { view: 'collection' },
programme[*]: { view: 'group', label: 'Day no. {_index}' }, // more about the {_index} at Dynamic values
programme[*].header: { type: 'text', label: 'Title' },
programme[*].subheader: { type: 'text', label: 'Short description' },
programme[*].items: { type: 'collection', label: 'Sessions' },
programme[*].items[*]: { type: 'group' },
programme[*].items[*].title: { type: 'text', label: 'Session name' },
programme[*].items[*].subtitle: { type: 'area', label: 'About' },
programme[*].items[*].time: { type: 'text', label: 'Time of start' },

Dynamic values

Any view type can have a String in description value. If the string is found in provided functions set, given function gets executed. The function also receives the child's path and value as a parameter.

{
   view: 'group'
   hidden: '_groupHidden'
}

...while in JS code

<dd-form
  ...
  :functions="myFunctions"
>
</dd-form>

data() {
  return {
      myFunctions: {
        '_groupHidden': this.groupHidden,
        '_groupIndex': this.groupIndex,
      },
    };
},

methods: {
  groupHidden({path, value}) {
    return value.item < 0;
  },
  groupIndex({path}) {
    return this.getIndex(path);
   }
},

Methods can be also put inline to the string. For these cases, the dynamic value needs to be wrapped inside the brackets.

{
   view: 'group'
   label: 'Day no. {_groupIndex}'
}

Check the demo's source code to see it in use.

Side note to Descriptions

The order of descriptions defines the order of the views on the UI. It is also needed to describe first the Object and then it's sub-nodes.


View types

There is 11 basic view types included in this package:

collection

Serves as Array container and expects to have multiple inner children. Collection shows just an add-child button by default. It is only an abstraction and has no visual representation.

Usage

{
    view: 'collection',
}

Possible value types

(Array): Consumes only array

Arguments

group

Serves as Object container and expects to have nested children. It is only an abstraction and has no visual representation.

Usage

{
    view: 'group',
}

Possible value types

(Object) or null: Consumes only object

Arguments

text

Acts as a basic single-line text field.

Usage

{
    view: 'text',
}

Possible value types

String: Consumes only String

Arguments

select

Acts as a drow-down box.

Usage

{
    view: 'select',
}

Possible value types

String or null: Consumes String or null

Arguments

checkbox

Acts as a checkbox set.

Usage

{
    view: 'checkbox',
}

Possible value types

Array: Consumes Array of strings

Arguments

counter

Acts as a counter box.

Usage

{
    view: 'counter',
}

Possible value types

Number: Consumes a Number

Arguments

datetime

Acts as a date-time selection field.

Usage

{
    view: 'datetime',
}

Possible value types

Date or String: Consumes a Date, String or Timestamp (Firebase) but always returns Date

Arguments

* String in a Date format

upload

Acts as a box for file uploads and renders into preview box, if consumable by web (image, video, ...).

Usage

{
    view: 'upload',
}

Possible value types

String: Consumes a String

Arguments

* Endpoint URL receives a POST call with FormData (image and payload)

⚠️ Note from author File upload functionality is heavily domain-oriented, thus I'd advise you to clone the ViewUpload.vue, modify it by your needs and use it as a Custom view.

area

Acts as a multi-line text field with optional WYSIWYG editor.

Usage

{
  view: 'area',
}

Possible value types

String: Consumes only String

Arguments

radio

Acts as a radio button set.

Usage

{
  view: 'radio',
}

Possible value types

String: Consumes a String or null

Arguments

tick

Acts as a single checkbox.

Usage

{
  view: 'tick',
}

Possible value types

Boolean: Consumes a Boolean

Arguments

Custom views

Any custom view can be inserted via :views property. Custom views are superior to the default ones, so text can be overwritten by a custom one.

Usage:

Check the demo's source code to see it in use.

Advanced: Sub-elements

Some sub-elements can be replaced too: button-add, button-remove, button-submit, headline.

    customViews: {
	   'collection.button-add': customButtonAdd,
	   'collection.headline': customHeadline,
	   'collection.button-remove': customButtonRemove,
	   'group.headline': customHeadline,
	   'group.button-submit': customButtonSubmit,
    }

Other features

Language customization

vue-dd-form provides a :lang property, which can rewrite the default language wording.

<dd-form
  :lang="customLang",
  ...
></dd-form>

...and the lang object

customLang: {
  add: 'Add item',
  remove: 'Remove item',
  save: 'Save data'
}

Wrappers (beta)

Views can be wrapped inside separate boxes (DIVs) so we can position them in any way. Look at example data:

{
    programme: [
    {
      header: 'Friday',
      subheader: '25th September 2020',
      items: [
        ...
	],
      }

If we wanted, for instance, to have header and sub-header on the left side of the screen and items on the right, the descriptions would look as follows:

programme[*].header: { type: 'text' },
programme[*].subheader: { type: 'text', wrapper: 'left' },
programme[*].items: { type: 'collection', wrapper: 'left' },

In result HTML, the views would look like this

<div class="left">
	<!-- header div -->
	<div>...</div>
	<!-- subheader div-->
	<div>...</div>
</div>
<div class="right">
	<!-- items div-->
	<div>...</div>
</div>

Afterwards, only thing to be defined are the CSS classes. Check the demo's source code to see it in use.


Events

@change fired after any value change, emits path, value changed & current data set

change({ path, value, data }) {
      // eslint-disable-next-line no-console
      console.log(path, value); // ex. => 'programme[*].header', 'Friday'
      console.log(data); // ex. => { ... }
},

@submit fired after form submit, emits data value

submit({ data }) {
  // eslint-disable-next-line no-console
  console.log(data); // ex. => { ... }
},

@add fired after a item is added to a collection, emits path, value added & current data set

add({ path, value, data }) {
      // eslint-disable-next-line no-console
      console.log(path, value); // ex. => 'programme[*].options', { header: 'Friday', ... }
      console.log(data); // ex. => { ... }
},

@remove fired after a item is added to a collection, emits path, value removed, index & current data set

remove({ path, value, data, index }) {
      // eslint-disable-next-line no-console
      console.log(path, value); //  ex. => 'programme[*].options', { header: 'Friday', ... }
      console.log(data); // ex. => { ... }
      console.log(index); // ex. => 1
},


Styling

Form can be styled in verious ways depending on the depth of the adjustment:

Helper classes

Other classes appearing in UI

🏁 Conclusion

There are many useful things which are still not yet implemented (validation, CDN availability etc.). I'd be very thankful for any contributions! Described-Data would work the best if it was multi-platform so if you feel like cooperating (React etc.), hit me up!

License

The MIT License