@oarepo/data-editor
A library for editing of rendered JSON data.
Installation
@oarepo/data-editor
requires @oarepo/data-renderer
to be installed as well.
yarn add @oarepo/data-editor
yarn add @oarepo/data-renderer
To register/configure the library, add a new boot file to quasar (or main.js for vue-cli projects):
import DataEditor from '@oarepo/data-editor'
import DataRenderer from '@oarepo/data-renderer'
export default async ({ Vue, store, router }) => {
Vue.use(DataEditor, {})
Vue.use(DataRenderer, {})
}
Usage
To use data editor, add data-editor
to template. data-editor
acceptsrecord
, options
, layout
and pathLayouts
and renders interface to edit received data with buttons for editing, addition and removal based on data type.
Dialog components and default values can be passed to data-editor
.
Record
record
contains json data to be passed to data-editor.
- simple object:
record: { object: { a: 1 } }
- simple array:
record: { array: [1, 2] }
- complex array:
record: { array: [{ a: 1 }}, { b: 2 }] }
- empty record:
record: {}
Options
options
may consist of schema, extraProps.
-
schema
of rendered data (inline, block, table, flex), defaults to table- table
options: { schema: table }
- inline
options: { schema: inline }
- table
-
extraProps
(submit, cancel method for data editor)options: { extraProps: { submit: { submit: this.submit, cancel: this.cancel } } }
Layout
layout
can be used to define the layout of an empty record, to associate dialog components or default values with props, to define children.
- basic layout:
layout: [{ 'path': 'a', 'label': 'a' }]
- layout with dialog component:
layout: [{ 'path': 'a', 'label': 'a', dialogComponent: DialogComponent, dynamic: true }]
- layout with default value:
layout: [{ 'path': 'a', 'label': 'a', defaultValue: () => 1, dynamic: true }]
- layout with default value as a function:
layout: [{ 'path': 'a', 'label': 'a', defaultValue: defaultValue, dynamic: true }]
pathLayouts
can be used to associate dialog components or default values with objects or arrays regardless of their position within the layout.
* simple default value options: { pathLayouts: { simpleArray: defaultValue: () => 1 } }
* complex default value options: { pathLayouts: { complexArray: defaultValue: () => { a: 1 } } }
If there is a complex value in layout
and not in data
, then this complex value must be created by clicking displayed button before it and its content can be edited.
layout
and pathLayouts
are described in more detail here: https://github.com/oarepo/data-renderer/
Dialog component
Specifies dialog component to be used with data editor, e.g. dialog to add a new object with default property to array, dialog to enter property and value to be added to object etc.
Examples
Examples are located at /src/components:
simple object
Example of a simple object. Src at /src/components/SimpleEdit.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
import Vue from 'vue'
export default {
name: 'simple-edit',
data: function () {
return {
record: {
Contact: {
Phone: '+420123123123',
Email: 'mary.black@gmail.com'
}
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
},
layout: {
showEmpty: true
}
}
},
methods: {
submit ({ context, prop, value, op }) {
isNaN(value)
if (op === 'add') {
Vue.set(context, prop, value)
}
if (op === 'replace') {
if (context[prop] === undefined) {
Vue.set(context, prop, value)
} else {
context[prop] = value
}
}
},
cancel (props) {
console.log('cancelling')
}
}
}
</script>
Simple array
Example of a simple array. Src at /src/components/SimpleArray.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
import Vue from 'vue'
export default {
name: 'array-edit',
data: function () {
return {
record: {
keywords: ['first keyword', 'second keyword']
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
},
layout: {
children: [
{
prop: 'keywords',
label: {
label: 'keywordArray'
},
item: {
label: {
label: 'keyword'
}
}
}
]
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
context[prop] = value
}
}
if (op === 'replace') {
Vue.set(context, prop, value)
}
if (op === 'remove') {
if (Array.isArray(context)) {
context.splice(prop, 1)
} else {
delete context[prop]
}
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>
Complex array with default value
Example of a complex array with default value for newly added items. Src at /src/components/SimpleArray.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
export default {
name: 'default-value-complex-array-edit',
data: function () {
return {
record: {
contact: [{ email: 1 }, { phone: '+420123123123' }]
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
},
layout: {
children: [{
prop: 'contact',
additionalProps: { defaultValue: () => ({ phone: '+420123123124' }) }
}]
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
context[prop] = value
}
}
if (op === 'replace') {
context[prop] = value
}
if (op === 'remove') {
if (Array.isArray(context)) {
context.splice(prop, 1)
} else {
delete context[prop]
}
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>
Complex array with dialog
Example of a complex array with dialog for addition of new items. Src at /src/components/ComplexArrayDialogEdit.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
import Vue from 'vue'
import DialogComponent from './DialogComponent'
export default {
name: 'complex-array-dialog-edit',
data: function () {
return {
record: {
contact: [{ phone: '+420123123124' }, { email: 'mary.black@gmail.com' }]
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel,
dialogComponent: DialogComponent
}
},
layout: {
children: [
{
prop: 'contact'
}
]
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
Vue.set(context, prop, value)
}
}
if (op === 'replace') {
context[prop] = value
}
if (op === 'remove') {
if (Array.isArray(context)) {
context.splice(prop, 1)
} else {
delete context[prop]
}
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>
Record with dialog component and no data
Example of an empty record with layout and dialog. Src at /src/components/NonExistingObjectDialogEdit.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
import Vue from 'vue'
import DialogWithPropertyComponent from './DialogWithPropertyComponent'
export default {
name: 'non-existing-object-dialog-edit',
data: function () {
return {
record: {},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
},
layout: {
showEmpty: true,
children: [
{
prop: 'object',
additionalProps: { dialogComponent: DialogWithPropertyComponent },
children: []
}
]
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
Vue.set(context, prop, value)
}
}
if (op === 'replace') {
context[prop] = value
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>
Example of a dialog component. Src at /src/components/DialogComponent.vue:
<template lang="pug">
q-dialog(ref="dialog" @hide="onDialogHide")
q-card
q-card-section
q-form(ref="form")
q-input(label="value" v-model="value")
q-card-actions(align="right")
q-btn(color="primary" type="submit" label="OK" @click="onOKClick")
q-btn(color="primary" label="Cancel" @click="onCancelClick")
</template>
<script>
export default {
name: 'dialog-component',
data: function () {
return {
value: null
}
},
props: {
initialValue: Object
},
mounted () {
if (this.initialValue) {
this.value = this.initialValue
}
},
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
},
async onOKClick () {
if (await this.$refs.form.validate()) {
this.$emit('ok', { phone: this.value })
this.hide()
}
},
onCancelClick () {
this.hide()
}
}
}
</script>
Empty object with dialog component and default value function
Example of object with default value as a function and dialog component. Src at /src/components/AdditionalPropsEdit.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout")
</template>
<script>
import DialogWithPropertyComponent from './DialogWithPropertyComponent'
import Vue from 'vue'
function defaultValue ({ context, layout }) {
for (const prop of 'abcdefghijklmnopqrstuvwxyz'.split('')) {
if (context[layout.prop][prop] === undefined) {
return { prop: prop, value: 'keyword' }
}
}
}
export default {
name: 'additional-props-edit',
data: function () {
return {
record: {
creator: { name: 'Mary Black' },
contact: { phone: '+420123123124' },
keywords: {}
},
layout: {
children: [
{ prop: 'creator', additionalProps: { dialogComponent: DialogWithPropertyComponent } },
{ prop: 'contact', additionalProps: { dialogComponent: DialogWithPropertyComponent } },
{ prop: 'keywords', additionalProps: { defaultValue: defaultValue } }]
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
Vue.set(context, prop, value)
}
}
if (op === 'replace') {
context[prop] = value
}
if (op === 'remove') {
if (Array.isArray(context)) {
context.splice(prop, 1)
} else {
delete context[prop]
}
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>
Record as tree
Example of a record as tree with a complex default value for addition of new items. Src at /src/components/TreeEdit.vue:
<template lang="pug">
data-editor(:record="record" :options="options" :layout="layout" :path-layouts="pathLayouts")
</template>
<script>
import Vue from 'vue'
export default {
name: 'tree-edit',
data: function () {
return {
record: {
object: [
{ creator: 'Mary Black' },
{
contact: [
{ phone: '+420123123123' },
{ email: ['mary.black@gmail.com'] }]
}]
},
options: {
schema: 'table',
extraProps: {
submit: this.submit,
cancel: this.cancel
}
},
layout: {
children: [
{
prop: 'object',
additionalProps: { defaultValue: () => ({ keywords: ['first keyword', 'second keyword'] }) }
}
]
}
}
},
methods: {
submit ({ path, context, prop, value, op, pathValues }) {
if (op === 'add') {
if (Array.isArray(context)) {
context.push(value)
} else {
Vue.set(context, prop, value)
}
}
if (op === 'replace') {
context[prop] = value
}
if (op === 'remove') {
if (Array.isArray(context)) {
context.splice(prop, 1)
} else {
delete context[prop]
}
}
},
cancel ({ props }) {
console.log('cancelling')
}
}
}
</script>