Browse Source

feat: Child creation from has many cell

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/341/head
Pranav C 3 years ago
parent
commit
8dd4807610
  1. 20
      packages/nc-gui/components/project/spreadsheet/components/cell.vue
  2. 23
      packages/nc-gui/components/project/spreadsheet/components/editableCell.vue
  3. 53
      packages/nc-gui/components/project/spreadsheet/components/editableCell/textAreaCell.vue
  4. 95
      packages/nc-gui/components/project/spreadsheet/components/editableCell/textAreaCellOld.vue
  5. 17
      packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue
  6. 2
      packages/nc-gui/components/project/spreadsheet/components/headerCell.vue
  7. 11
      packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue
  8. 6
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue
  9. 183
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue
  10. 8
      packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue
  11. 16
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  12. 47
      packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue
  13. 6
      packages/nc-gui/config/vuetify.options.js
  14. 7
      packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

20
packages/nc-gui/components/project/spreadsheet/components/tableCell.vue → packages/nc-gui/components/project/spreadsheet/components/cell.vue

@ -1,12 +1,14 @@
<template> <template>
<editable-attachment-cell <v-lazy>
:isLocked="isLocked" <editable-attachment-cell
:db-alias="dbAlias" :isLocked="isLocked"
@click.stop="$emit('enableedit')" v-if="isAttachment" :value="value" :column="column"></editable-attachment-cell> :db-alias="dbAlias"
<set-list-cell @click.stop="$emit('enableedit')" v-else-if="isSet" :value="value" :column="column"></set-list-cell> @click.stop="$emit('enableedit')" v-if="isAttachment" :value="value" :column="column"></editable-attachment-cell>
<!-- <enum-list-editable-cell @click.stop="$emit('enableedit')" v-else-if="isEnum && selected" :value="value" :column="column"></enum-list-editable-cell>--> <set-list-cell @click.stop="$emit('enableedit')" v-else-if="isSet" :value="value" :column="column"></set-list-cell>
<enum-cell @click.stop="$emit('enableedit')" v-else-if="isEnum" :value="value" :column="column"></enum-cell> <!-- <enum-list-editable-cell @click.stop="$emit('enableedit')" v-else-if="isEnum && selected" :value="value" :column="column"></enum-list-editable-cell>-->
<span v-else>{{ value }}</span> <enum-cell @click.stop="$emit('enableedit')" v-else-if="isEnum" :value="value" :column="column"></enum-cell>
<span v-else>{{ value }}</span>
</v-lazy>
</template> </template>
<script> <script>
@ -20,7 +22,7 @@ import EnumListEditableCell from "@/components/project/spreadsheet/components/ed
export default { export default {
name: "tableCell", name: "tableCell",
components: {EnumListEditableCell, EditableAttachmentCell, AttachmentCell, EnumCell, SetListCell}, components: {EnumListEditableCell, EditableAttachmentCell, AttachmentCell, EnumCell, SetListCell},
props: ['value','dbAlias','isLocked','selected'], props: ['value', 'dbAlias', 'isLocked', 'selected'],
mixins: [cell], mixins: [cell],
computed: {} computed: {}
} }

23
packages/nc-gui/components/project/spreadsheet/components/editableCell.vue

@ -1,4 +1,5 @@
<template> <template>
<v-lazy>
<div <div
@keydown.stop.left @keydown.stop.left
@keydown.stop.right @keydown.stop.right
@ -13,7 +14,6 @@
v-model="localState"></editable-attachment-cell> v-model="localState"></editable-attachment-cell>
<boolean-cell :isForm="isForm" v-else-if="isBoolean" v-on="parentListeners" <boolean-cell :isForm="isForm" v-else-if="isBoolean" v-on="parentListeners"
v-model="localState" @input="$emit('change');"></boolean-cell> v-model="localState" @input="$emit('change');"></boolean-cell>
@ -57,7 +57,8 @@
<text-area-cell <text-area-cell
:is-form="isForm" :is-form="isForm"
v-else-if="isTextArea" @input="$emit('save')" v-model="localState" v-else-if="isTextArea"
v-model="localState"
v-on="parentListeners" v-on="parentListeners"
></text-area-cell> ></text-area-cell>
<!--<set-list-checkbox-cell :column="column" v-else-if="isSet" v-model="localState" <!--<set-list-checkbox-cell :column="column" v-else-if="isSet" v-model="localState"
@ -65,6 +66,7 @@
<text-cell v-else v-model="localState" v-on="$listeners"></text-cell> <text-cell v-else v-model="localState" v-on="$listeners"></text-cell>
</div> </div>
</v-lazy>
</template> </template>
<script> <script>
@ -85,6 +87,7 @@ import EditableAttachmentCell from "@/components/project/spreadsheet/components/
import EnumCell from "@/components/project/spreadsheet/components/cell/enumCell"; import EnumCell from "@/components/project/spreadsheet/components/cell/enumCell";
import SetListEditableCell from "@/components/project/spreadsheet/components/editableCell/setListEditableCell"; import SetListEditableCell from "@/components/project/spreadsheet/components/editableCell/setListEditableCell";
import SetListCell from "@/components/project/spreadsheet/components/cell/setListCell"; import SetListCell from "@/components/project/spreadsheet/components/cell/setListCell";
import debounce from "debounce";
export default { export default {
name: "editable-cell", name: "editable-cell",
@ -107,11 +110,13 @@ export default {
meta: Object, meta: Object,
ignoreFocus: Boolean, ignoreFocus: Boolean,
isForm: Boolean, isForm: Boolean,
active: Boolean, active: Boolean
}, },
data: () => ({ data: () => ({
changed: false, changed: false,
destroyed: false
}), }),
mounted() { mounted() {
// this.$refs.input.focus(); // this.$refs.input.focus();
}, },
@ -119,6 +124,17 @@ export default {
if (this.changed && !(this.isAttachment || this.isEnum || this.isBoolean || this.isSet || this.isTime)) { if (this.changed && !(this.isAttachment || this.isEnum || this.isBoolean || this.isSet || this.isTime)) {
this.$emit('change'); this.$emit('change');
} }
this.destroyed = true;
},
methods: {
syncDataDebounce: debounce(async function (self) {
await self.syncData()
}, 1000),
syncData() {
if (!this.destroyed) {
this.$emit('update')
}
}
}, },
computed: { computed: {
localState: { localState: {
@ -128,6 +144,7 @@ export default {
set(val) { set(val) {
this.changed = true; this.changed = true;
this.$emit('input', val); this.$emit('input', val);
this.syncDataDebounce(this);
} }
}, },
parentListeners() { parentListeners() {

53
packages/nc-gui/components/project/spreadsheet/components/editableCell/textAreaCell.vue

@ -1,15 +1,8 @@
<template> <template>
<div> <textarea v-on="parentListeners" ref="textarea" v-model="localState" rows="3"
<div class="d-flex ma-1" v-if="!isForm"> @keydown.alt.enter.stop
<v-spacer> @keydown.shift.enter.stop
</v-spacer> ></textarea>
<v-btn v-if="!isForm" outlined x-small class="mr-1" @click="$emit('cancel')">Cancel</v-btn>
<v-btn v-if="!isForm" x-small color="primary" @click="save">Save</v-btn>
</div>
<textarea v-on="parentListeners" ref="textarea" v-model="localState" rows="3"
@input="isForm && save()"
@keydown.stop.enter></textarea>
</div>
</template> </template>
@ -17,41 +10,31 @@
export default { export default {
name: "textAreaCell", name: "textAreaCell",
props: { props: {
value: String, value: String
isForm: Boolean
}, },
data: () => ({
localState: ''
}),
created() { created() {
this.localState = this.value; this.localState = this.value;
}, },
mounted() { mounted() {
this.$refs.textarea && this.$refs.textarea.focus(); this.$refs.textarea && this.$refs.textarea.focus();
}, watch: {
value(val) {
this.localState = val;
},
localState(val) {
if (this.isForm) {
this.$emit('input', val)
}
}
},
methods: {
save() {
this.$emit('input', this.localState)
}
}, },
computed:{ computed: {
parentListeners(){ localState: {
get() {
return this.value
},
set(val) {
this.$emit('input', val);
}
},
parentListeners() {
const $listeners = {}; const $listeners = {};
if(this.$listeners.blur){ if (this.$listeners.blur) {
$listeners.blur = this.$listeners.blur; $listeners.blur = this.$listeners.blur;
} }
if(this.$listeners.focus){ if (this.$listeners.focus) {
$listeners.focus = this.$listeners.focus; $listeners.focus = this.$listeners.focus;
} }
@ -64,7 +47,7 @@ export default {
<style scoped> <style scoped>
input, textarea { input, textarea {
width: 100%; width: 100%;
min-height:60px; min-height: 60px;
height: calc(100% - 28px); height: calc(100% - 28px);
color: var(--v-textColor-base); color: var(--v-textColor-base);
} }

95
packages/nc-gui/components/project/spreadsheet/components/editableCell/textAreaCellOld.vue

@ -0,0 +1,95 @@
<template>
<div>
<div class="d-flex ma-1" v-if="!isForm">
<v-spacer>
</v-spacer>
<v-btn v-if="!isForm" outlined x-small class="mr-1" @click="$emit('cancel')">Cancel</v-btn>
<v-btn v-if="!isForm" x-small color="primary" @click="save">Save</v-btn>
</div>
<textarea v-on="parentListeners" ref="textarea" v-model="localState" rows="3"
@input="isForm && save()"
@keydown.stop.enter></textarea>
</div>
</template>
<script>
export default {
name: "textAreaCell",
props: {
value: String,
isForm: Boolean
},
data: () => ({
localState: ''
}),
created() {
this.localState = this.value;
},
mounted() {
this.$refs.textarea && this.$refs.textarea.focus();
}, watch: {
value(val) {
this.localState = val;
},
localState(val) {
if (this.isForm) {
this.$emit('input', val)
}
}
},
methods: {
save() {
this.$emit('input', this.localState)
}
},
computed:{
parentListeners(){
const $listeners = {};
if(this.$listeners.blur){
$listeners.blur = this.$listeners.blur;
}
if(this.$listeners.focus){
$listeners.focus = this.$listeners.focus;
}
return $listeners;
},
}
}
</script>
<style scoped>
input, textarea {
width: 100%;
min-height:60px;
height: calc(100% - 28px);
color: var(--v-textColor-base);
}
</style>
<!--
/**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd
*
* @author Naveen MR <oof1lab@gmail.com>
* @author Pranav C Balan <pranavxc@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
-->

17
packages/nc-gui/components/project/spreadsheet/components/expandedForm.vue

@ -61,6 +61,7 @@
</label> </label>
<virtual-cell <virtual-cell
v-if="col.virtual" v-if="col.virtual"
:disabledColumns="disabledColumns"
:column="col" :column="col"
:row="localState" :row="localState"
:nodes="nodes" :nodes="nodes"
@ -69,12 +70,13 @@
:active="true" :active="true"
:sql-ui="sqlUi" :sql-ui="sqlUi"
@loadTableData="reload" @loadTableData="reload"
:is-new="isNew"
></virtual-cell> ></virtual-cell>
<div <div
style="height:100%; width:100%" style="height:100%; width:100%"
class="caption xc-input" class="caption xc-input"
v-if="col.ai || (col.pk && !selectedRowMeta.new)" v-if="col.ai || (col.pk && !selectedRowMeta.new) || disabledColumns[col._cn]"
@click="col.ai && $toast.info('Auto Increment field is not editable').goAway(3000)" @click="col.ai && $toast.info('Auto Increment field is not editable').goAway(3000)"
> >
<input <input
@ -158,6 +160,8 @@
</v-text-field> </v-text-field>
</div> </div>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
</v-card-text> </v-card-text>
@ -210,7 +214,13 @@ export default {
}, },
availableColumns: [Object, Array], availableColumns: [Object, Array],
nodes: [Object], nodes: [Object],
queryParams: Object queryParams: Object,
disabledColumns:{
type:Object,
default(){
return {}
}
}
}, },
name: "expanded-form", name: "expanded-form",
data: () => ({ data: () => ({
@ -376,6 +386,7 @@ export default {
.row-col { .row-col {
& > div > input, & > div > input,
& > div div >input,
& > div > .xc-input > input, & > div > .xc-input > input,
& > div > select, & > div > select,
& > div > .xc-input > select, & > div > .xc-input > select,
@ -406,6 +417,7 @@ export default {
background: #363636; background: #363636;
.row-col { .row-col {
& > div div > input,
& > div > input, & > div > input,
& > div > .xc-input > input, & > div > .xc-input > input,
& > div > select, & > div > select,
@ -421,6 +433,7 @@ export default {
.row-col { .row-col {
& > div > input, & > div > input,
& > div div >input,
& > div > .xc-input > input, & > div > .xc-input > input,
& > div > select, & > div > select,
& > div > .xc-input > select, & > div > .xc-input > select,

2
packages/nc-gui/components/project/spreadsheet/components/headerCell.vue

@ -20,6 +20,8 @@
{{ value }} {{ value }}
<span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span>
<v-spacer> <v-spacer>
</v-spacer> </v-spacer>

11
packages/nc-gui/components/project/spreadsheet/components/virtualCell.vue

@ -1,5 +1,6 @@
<template> <template>
<div> <div>
<v-lazy>
<has-many-cell <has-many-cell
v-if="hm" v-if="hm"
:row="row" :row="row"
@ -9,6 +10,7 @@
:nodes="nodes" :nodes="nodes"
:active="active" :active="active"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:is-new="isNew"
v-on="$listeners" v-on="$listeners"
/> />
<many-to-many-cell <many-to-many-cell
@ -20,9 +22,11 @@
:nodes="nodes" :nodes="nodes"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:active="active" :active="active"
:is-new="isNew"
v-on="$listeners" v-on="$listeners"
/> />
<belongs-to-cell <belongs-to-cell
:disabled-columns="disabledColumns"
v-else-if="bt" v-else-if="bt"
:active="active" :active="active"
:row="row" :row="row"
@ -32,8 +36,10 @@
:nodes="nodes" :nodes="nodes"
:api="api" :api="api"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:is-new="isNew"
v-on="$listeners" v-on="$listeners"
/> />
</v-lazy>
</div> </div>
</template> </template>
@ -57,6 +63,11 @@ export default {
api: [Object, Function], api: [Object, Function],
active: Boolean, active: Boolean,
sqlUi: [Object, Function], sqlUi: [Object, Function],
isNew: {
type: Boolean,
default: false
},
disabledColumns:Object
}, },
computed: { computed: {
hm() { hm() {

6
packages/nc-gui/components/project/spreadsheet/components/virtualCell/belogsToCell.vue

@ -8,7 +8,7 @@
</v-chip> </v-chip>
</template> </template>
</div> </div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }"> <div v-if="!isNew" class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{ <x-icon small :color="['primary','grey']" @click="showNewRecordModal">{{
value ? 'mdi-pencil' : 'mdi-plus' value ? 'mdi-pencil' : 'mdi-plus'
}} }}
@ -75,7 +75,9 @@ export default {
row: [Object], row: [Object],
api: [Object, Function], api: [Object, Function],
sqlUi: [Object, Function], sqlUi: [Object, Function],
active: Boolean active: Boolean,
isNew:Boolean,
disabledColumns:Object
}, },
data: () => ({ data: () => ({
newRecordModal: false, newRecordModal: false,

183
packages/nc-gui/components/project/spreadsheet/components/virtualCell/hasManyCell.vue

@ -4,9 +4,22 @@
<template v-if="value"> <template v-if="value">
<v-chip <v-chip
small small
v-for="(v,i) in value.map(v=>Object.values(v)[1])" v-for="(ch,i) in value"
:color="colors[i%colors.length]" :key="i"> :key="i"
{{ v }} :color="colors[i%colors.length]"
@click="editChild(ch)"
>
{{ Object.values(ch)[1] }}
<div v-show="active" class="mr-n1 ml-2 mt-n1">
<x-icon
:color="['text' , 'textLight']"
x-small
icon.class="unlink-icon"
@click.stop="unlinkChild(ch)"
>mdi-close-thick
</x-icon>
</div>
</v-chip> </v-chip>
</template> </template>
</div> </div>
@ -15,36 +28,40 @@
<x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon> <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>
</div> </div>
<!-- <list-items <!-- <list-items
:count="10" :count="10"
:meta="childMeta" :meta="childMeta"
:primary-col="childPrimaryCol" :primary-col="childPrimaryCol"
:primary-key="childPrimaryKey" :primary-key="childPrimaryKey"
v-model="newRecordModal" v-model="newRecordModal"
:api="childApi" :api="childApi"
></list-items>--> ></list-items>-->
<v-dialog v-if="newRecordModal" v-model="newRecordModal" width="600"> <v-dialog v-if="newRecordModal" v-model="newRecordModal" width="600">
<v-card width="600" color="backgroundColor"> <v-card width="600" color="backgroundColor">
<v-card-title class="textColor--text mx-2">Add Record <v-card-title class="textColor--text mx-2 justify-center">Link Record
<v-spacer>
</v-spacer>
<v-btn small class="caption" color="primary">
<v-icon small>mdi-plus</v-icon>&nbsp;
Add New Record
</v-btn>
</v-card-title> </v-card-title>
<v-card-text>
<v-card-title>
<v-text-field <v-text-field
hide-details hide-details
dense dense
outlined outlined
placeholder="Search record" placeholder="Search records"
class="mb-2 mx-2 caption" class=" caption search-field ml-2"
/> />
<v-spacer></v-spacer>
<v-btn small class="caption mr-2" color="primary" @click="insertAndAddNewChildRecord">
<v-icon small>mdi-plus</v-icon>&nbsp;
New Record
</v-btn>
</v-card-title>
<v-card-text>
<div class="items-container"> <div class="items-container">
<template v-if="list"> <template v-if="list && list.list && list.list.length">
<v-card <v-card
v-for="(ch,i) in list.list" v-for="(ch,i) in list.list"
class="ma-2 child-card" class="ma-2 child-card"
@ -53,20 +70,29 @@
@click="addChildToParent(ch)" @click="addChildToParent(ch)"
:key="i" :key="i"
> >
<v-card-title class="primary-value textColor--text text--lighten-2">{{ ch[childPrimaryCol] }} <v-card-text class="primary-value textColor--text text--lighten-2 d-flex">
<span class="grey--text caption primary-key" <span class="font-weight-bold"> {{ ch[childPrimaryCol] }}</span>
<span class="grey--text caption primary-key "
v-if="childPrimaryKey">(Primary Key : {{ ch[childPrimaryKey] }})</span> v-if="childPrimaryKey">(Primary Key : {{ ch[childPrimaryKey] }})</span>
</v-card-title> <v-spacer/>
<v-chip v-if="ch[meta._tn]" x-small>
{{ ch[meta._tn][primaryCol] }}
</v-chip>
</v-card-text>
</v-card> </v-card>
</template> </template>
<div v-else class="text-center py-15 textLight--text">
No items found
</div>
</div> </div>
</v-card-text> </v-card-text>
<v-card-actions class="justify-center py-2 flex-column"> <v-card-actions class="justify-center py-2 flex-column">
<pagination <pagination
v-if="list" v-if="list && list.list && list.list.length"
:size="listPagination.size" :size="listPagination.size"
:count="list.count" :count="list.count"
v-model="listPagination.page" v-model="listPagination.page"
@ -164,7 +190,7 @@
:has-many="childMeta.hasMany" :has-many="childMeta.hasMany"
:belongs-to="childMeta.belongsTo" :belongs-to="childMeta.belongsTo"
@cancel="selectedChild = null" @cancel="selectedChild = null"
@input="$emit('loadTableData');showChildListModal();" @input="$emit('loadTableData');loadChildList();"
:table="childMeta.tn" :table="childMeta.tn"
v-model="selectedChild" v-model="selectedChild"
:old-row="{...selectedChild}" :old-row="{...selectedChild}"
@ -176,8 +202,11 @@
icon-color="warning" icon-color="warning"
:nodes="nodes" :nodes="nodes"
:query-params="childQueryParams" :query-params="childQueryParams"
ref="expandedForm"
:is-new="isNewChild"
:disabled-columns="disabledChildColumns"
></component> ></component>
{{childQueryParams}} {{ childQueryParams }}
</v-dialog> </v-dialog>
@ -204,7 +233,8 @@ export default {
nodes: [Object], nodes: [Object],
row: [Object], row: [Object],
sqlUi: [Object, Function], sqlUi: [Object, Function],
active: Boolean active: Boolean,
isNew: Boolean
}, },
data: () => ({ data: () => ({
newRecordModal: false, newRecordModal: false,
@ -224,13 +254,17 @@ export default {
childListPagination: { childListPagination: {
page: 1, page: 1,
size: 10 size: 10
} },
isNewChild: false
}), }),
methods: { methods: {
async showChildListModal() { async showChildListModal() {
this.childListModal = true; this.childListModal = true;
await this.getChildMeta(); await this.loadChildMeta();
await this.loadChildList();
},
async loadChildList() {
const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___');
const _cn = this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn; const _cn = this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn;
this.childList = await this.childApi.paginatedList({ this.childList = await this.childApi.paginatedList({
@ -239,6 +273,7 @@ export default {
offset: this.childListPagination.size * (this.childListPagination.page - 1), offset: this.childListPagination.size * (this.childListPagination.page - 1),
...this.childQueryParams ...this.childQueryParams
}) })
}, },
async deleteChild(child) { async deleteChild(child) {
this.dialogShow = true; this.dialogShow = true;
@ -257,23 +292,30 @@ export default {
} }
}, },
async unlinkChild(child) { async unlinkChild(child) {
// todo:
// this.dialogShow = true; // this.dialogShow = true;
// this.confirmMessage = // this.confirmMessage =
// 'Do you want to delete the record?'; // 'Do you want to unlink the record?';
// this.confirmAction = async act => { // this.confirmAction = async act => {
// if (act === 'hideDialog') { // if (act === 'hideDialog') {
// this.dialogShow = false; // this.dialogShow = false;
// } else { // } else {
// const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); await this.loadChildMeta();
// await this.childApi.delete(id) const column = this.childMeta.columns.find(c => c.cn === this.hm.cn);
// this.showChildListModal(); if (column.rqd) {
// this.dialogShow = false; this.$toast.info('Unlink is not possible, add to another record.').goAway(3000)
// this.$emit('loadTableData') return
// } }
const _cn = column._cn;
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
await this.childApi.update(id, {[_cn]: null}, child)
this.$emit('loadTableData')
if (this.childListModal) {
this.showChildListModal()
}
// }
// } // }
}, },
async getChildMeta() { async loadChildMeta() {
// todo: optimize // todo: optimize
if (!this.childMeta) { if (!this.childMeta) {
const childTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{ const childTableData = await this.$store.dispatch('sqlMgr/ActSqlOp', [{
@ -288,32 +330,51 @@ export default {
}, },
async showNewRecordModal() { async showNewRecordModal() {
this.newRecordModal = true; this.newRecordModal = true;
await this.getChildMeta(); await this.loadChildMeta();
const _cn = this.childForeignKey;
this.list = await this.childApi.paginatedList({ this.list = await this.childApi.paginatedList({
...this.childQueryParams,
limit: this.listPagination.size, limit: this.listPagination.size,
offset: this.listPagination.size * (this.listPagination.page - 1) offset: this.listPagination.size * (this.listPagination.page - 1),
where: `~not(${_cn},eq,${this.parentId})~or(${_cn},is,null)`
}) })
}, },
async addChildToParent(child) { async addChildToParent(child) {
const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___'); const id = this.childMeta.columns.filter((c) => c.pk).map(c => child[c._cn]).join('___');
const pid = this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___'); const _cn = this.childForeignKey;
const _cn = this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn;
this.newRecordModal = false; this.newRecordModal = false;
await this.childApi.update(id, { await this.childApi.update(id, {
[_cn]: pid [_cn]: this.parentId
}, { }, {
[_cn]: child[this.childPrimaryKey] [_cn]: child[this.childPrimaryKey]
}); });
this.$emit('loadTableData') this.$emit('loadTableData')
if(this.childListModal){ if (this.childListModal) {
await this.showChildListModal() await this.showChildListModal()
} }
}, },
async editChild(child) { async editChild(child) {
await this.loadChildMeta();
this.isNewChild = false;
this.selectedChild = child; this.selectedChild = child;
this.showExpandModal = true; this.showExpandModal = true;
setTimeout(() => {
this.$refs.expandedForm && this.$refs.expandedForm.reload()
}, 500)
},
async insertAndAddNewChildRecord() {
this.newRecordModal = false;
await this.loadChildMeta();
this.isNewChild = true;
this.selectedChild = {
[this.childForeignKey]: this.parentId
};
this.showExpandModal = true;
setTimeout(() => {
this.$refs.expandedForm && this.$refs.expandedForm.$set(this.$refs.expandedForm.changedColumns, this.childForeignKey, true)
}, 500)
} }
}, },
computed: { computed: {
@ -325,9 +386,18 @@ export default {
childPrimaryCol() { childPrimaryCol() {
return this.childMeta && (this.childMeta.columns.find(c => c.pv) || {})._cn return this.childMeta && (this.childMeta.columns.find(c => c.pv) || {})._cn
}, },
primaryCol() {
return this.meta && (this.meta.columns.find(c => c.pv) || {})._cn
},
childPrimaryKey() { childPrimaryKey() {
return this.childMeta && (this.childMeta.columns.find(c => c.pk) || {})._cn return this.childMeta && (this.childMeta.columns.find(c => c.pk) || {})._cn
}, },
childForeignKey() {
return this.childMeta && this.childMeta.columns.find(c => c.cn === this.hm.cn)._cn
},
disabledChildColumns() {
return {[this.childForeignKey]: true}
},
// todo: // todo:
form() { form() {
return () => import("@/components/project/spreadsheet/components/expandedForm") return () => import("@/components/project/spreadsheet/components/expandedForm")
@ -352,6 +422,9 @@ export default {
parents: (this.childMeta && this.childMeta.belongsTo && this.childMeta.belongsTo.map(hm => hm.rtn).join()) || '', parents: (this.childMeta && this.childMeta.belongsTo && this.childMeta.belongsTo.map(hm => hm.rtn).join()) || '',
many: (this.childMeta && this.childMeta.manyToMany && this.childMeta.manyToMany.map(mm => mm.rtn).join()) || '' many: (this.childMeta && this.childMeta.manyToMany && this.childMeta.manyToMany.map(mm => mm.rtn).join()) || ''
} }
},
parentId() {
return this.meta && this.meta.columns ? this.meta.columns.filter((c) => c.pk).map(c => this.row[c._cn]).join('___') : '';
} }
} }
} }
@ -410,6 +483,24 @@ export default {
margin: 3px auto; margin: 3px auto;
} }
::v-deep {
.unlink-icon {
padding: 0px 1px 2px 1px;
margin-top: 2px;
margin-right: -2px;
}
.search-field {
input {
max-height: 28px !important;
}
.v-input__slot {
min-height: auto !important;
}
}
}
</style> </style>
<!-- <!--
/** /**

8
packages/nc-gui/components/project/spreadsheet/components/virtualCell/manyToManyCell.vue

@ -13,7 +13,7 @@
</div> </div>
<div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }"> <div class=" align-center justify-center px-1 flex-shrink-1" :class="{'d-none': !active, 'd-flex':active }">
<x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon> <x-icon small :color="['primary','grey']" @click="showNewRecordModal">mdi-plus</x-icon>
<!-- <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>--> <!-- <x-icon x-small :color="['primary','grey']" @click="showChildListModal" class="ml-2">mdi-arrow-expand</x-icon>-->
</div> </div>
@ -150,7 +150,8 @@ export default {
row: [Object], row: [Object],
api: [Object, Function], api: [Object, Function],
sqlUi: [Object, Function], sqlUi: [Object, Function],
active: Boolean active: Boolean,
isNew: Boolean
}, },
data: () => ({ data: () => ({
newRecordModal: false, newRecordModal: false,
@ -161,7 +162,8 @@ export default {
childList: null, childList: null,
dialogShow: false, dialogShow: false,
confirmAction: null, confirmAction: null,
confirmMessage: '' confirmMessage: '',
selectedChild:null
}), }),
methods: { methods: {

16
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

@ -699,6 +699,11 @@ export default {
}) && pks.length && pks.every(col => !rowObj[col._cn])) { }) && pks.length && pks.every(col => !rowObj[col._cn])) {
return this.$toast.info('Primary column is empty please provide some value').goAway(3000); return this.$toast.info('Primary column is empty please provide some value').goAway(3000);
} }
if (this.availableColumns.some((col) => {
return col.rqd && (rowObj[col._cn] === undefined || rowObj[col._cn] === null) && !col.default
})) {
return;
}
const insertObj = this.availableColumns.reduce((o, col) => { const insertObj = this.availableColumns.reduce((o, col) => {
if (!col.ai && (rowObj && rowObj[col._cn]) !== null) { if (!col.ai && (rowObj && rowObj[col._cn]) !== null) {
@ -728,7 +733,15 @@ export default {
} }
} }
}, },
async onCellValueChange(col, row, column) {
onCellValueChangeDebounce: debounce(async function (col, row, column, self) {
await self.onCellValueChangeFn(col, row, column)
}, 300),
onCellValueChange(col, row, column) {
this.onCellValueChangeDebounce(col, row, column, this)
},
async onCellValueChangeFn(col, row, column) {
if (!this.data[row]) return; if (!this.data[row]) return;
const {row: rowObj, rowMeta, oldRow} = this.data[row]; const {row: rowObj, rowMeta, oldRow} = this.data[row];
if (rowMeta.new) { if (rowMeta.new) {
@ -806,6 +819,7 @@ export default {
const {rowMeta} = this.data[this.data.length - 1]; const {rowMeta} = this.data[this.data.length - 1];
this.expandRow(this.data.length - 1, rowMeta) this.expandRow(this.data.length - 1, rowMeta)
} }
this.save()
}, },

47
packages/nc-gui/components/project/spreadsheet/views/xcGridView.vue

@ -14,7 +14,7 @@
:class="$store.state.windows.darkTheme ? 'grey darken-3 grey--text text--lighten-1' : 'grey lighten-4 grey--text text--darken-2'" :class="$store.state.windows.darkTheme ? 'grey darken-3 grey--text text--lighten-1' : 'grey lighten-4 grey--text text--darken-2'"
v-xc-ver-resize v-xc-ver-resize
v-for="(col,i) in availableColumns" v-for="(col,i) in availableColumns"
:key="col.cn" :key="i + '_' + col._cn"
v-show="showFields[col._cn]" v-show="showFields[col._cn]"
@xcresize="onresize(col._cn,$event)" @xcresize="onresize(col._cn,$event)"
@xcresizing="resizingCol = col._cn" @xcresizing="resizingCol = col._cn"
@ -69,7 +69,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody v-click-outside="() => {this.selected.col=null;this.selected.row=null}">
<tr <tr
v-for="({row:rowObj, rowMeta, oldRow},row) in data" v-for="({row:rowObj, rowMeta, oldRow},row) in data"
:key="row" :key="row"
@ -105,23 +105,22 @@
</div> </div>
</td> </td>
<td <td
class="cell pointer" class="cell pointer"
v-for="(columnObj,col) in availableColumns" v-for="(columnObj,col) in availableColumns"
:key="columnObj._cn" :key="row + '_' + col + columnObj._cn"
:class="{ :class="{
active : !isPublicView && selected.col === col && selected.row === row && isEditable , 'active' : !isPublicView && selected.col === col && selected.row === row && isEditable ,
'primary-column' : primaryValueColumn === columnObj._cn, 'primary-column' : primaryValueColumn === columnObj._cn,
'text-center': isCentrallyAligned(columnObj) 'text-center': isCentrallyAligned(columnObj),
'required':columnObj.rqd && (rowObj[columnObj._cn]===undefined || rowObj[columnObj._cn]===null) && !columnObj.default
}" }"
@dblclick="makeEditable(col,row,columnObj.ai)" @dblclick="makeEditable(col,row,columnObj.ai)"
@click="makeSelected(col,row);" @click="makeSelected(col,row);"
v-show="showFields[columnObj._cn]" v-show="showFields[columnObj._cn]"
:data-col="columnObj._cn" :data-col="columnObj._cn"
> >
<virtual-cell <virtual-cell
v-if="columnObj.virtual" v-if="columnObj.virtual "
:column="columnObj" :column="columnObj"
:row="rowObj" :row="rowObj"
:nodes="nodes" :nodes="nodes"
@ -132,6 +131,12 @@
v-on="$listeners" v-on="$listeners"
></virtual-cell> ></virtual-cell>
<!--
<span
v-if="columnObj.virtual "
></span>
-->
<editable-cell <editable-cell
v-else-if=" v-else-if="
!isLocked !isLocked
@ -146,12 +151,11 @@
@save="editEnabled = {}" @save="editEnabled = {}"
@cancel="editEnabled = {}" @cancel="editEnabled = {}"
@update="onCellValueChange(col, row, columnObj)" @update="onCellValueChange(col, row, columnObj)"
@blur="onCellValueChange(col, row, columnObj,'blur')"
@change="onCellValueChange(col, row, columnObj)" @change="onCellValueChange(col, row, columnObj)"
:sql-ui="sqlUi" :sql-ui="sqlUi"
:db-alias="nodes.dbAlias" :db-alias="nodes.dbAlias"
/> />
<!-- @change="changed(col,row)"-->
<!-- />-->
<div v-else-if="columnObj.cn in hasMany" class="hasmany-col d-flex "> <div v-else-if="columnObj.cn in hasMany" class="hasmany-col d-flex ">
{{ rowObj[columnObj._cn] }} {{ rowObj[columnObj._cn] }}
@ -227,7 +231,7 @@
<v-divider <v-divider
:key="i + idCol + '_div'"></v-divider> :key="i + '_' + idCol + '_div'"></v-divider>
<v-list-item <v-list-item
class="py-1" class="py-1"
@click="addNewRelationTab( @click="addNewRelationTab(
@ -241,7 +245,7 @@
rowObj, rowObj,
rowObj[primaryValueColumn] rowObj[primaryValueColumn]
)" )"
:key="i + idCol" :key="i + '_' + idCol"
dense dense
> >
<v-list-item-icon class="mx-1"> <v-list-item-icon class="mx-1">
@ -301,7 +305,7 @@
import HeaderCell from "@/components/project/spreadsheet/components/headerCell"; import HeaderCell from "@/components/project/spreadsheet/components/headerCell";
import EditableCell from "@/components/project/spreadsheet/components/editableCell"; import EditableCell from "@/components/project/spreadsheet/components/editableCell";
import EditColumn from "@/components/project/spreadsheet/components/editColumn"; import EditColumn from "@/components/project/spreadsheet/components/editColumn";
import TableCell from "@/components/project/spreadsheet/components/tableCell"; import TableCell from "@/components/project/spreadsheet/components/cell";
import colors from "@/mixins/colors"; import colors from "@/mixins/colors";
import columnStyling from "@/components/project/spreadsheet/helpers/columnStyling"; import columnStyling from "@/components/project/spreadsheet/helpers/columnStyling";
import HasManyCell from "@/components/project/spreadsheet/components/virtualCell/hasManyCell"; import HasManyCell from "@/components/project/spreadsheet/components/virtualCell/hasManyCell";
@ -398,6 +402,15 @@ export default {
case 13: case 13:
this.makeEditable(this.selected.col, this.selected.row) this.makeEditable(this.selected.col, this.selected.row)
break; break;
default: {
if (this.editEnabled.col != null && this.editEnabled.row != null) {
return;
}
console.log(this.selected, this.data[this.selected.row], this.availableColumns[this.selected.col], '')
this.$set(this.data[this.selected.row].row, this.availableColumns[this.selected.col]._cn, '')
this.editEnabled = {...this.selected}
}
} }
}, },
onNewColCreation() { onNewColCreation() {
@ -411,8 +424,8 @@ export default {
showRowContextMenu($event, rowObj, rowMeta, row) { showRowContextMenu($event, rowObj, rowMeta, row) {
this.$emit('showRowContextMenu', $event, rowObj, rowMeta, row) this.$emit('showRowContextMenu', $event, rowObj, rowMeta, row)
}, },
onCellValueChange(col, row, column) { onCellValueChange(col, row, column, ev) {
this.$emit('onCellValueChange', col, row, column) this.$emit('onCellValueChange', col, row, column, ev);
}, },
addNewRelationTab(...args) { addNewRelationTab(...args) {
this.$emit('addNewRelationTab', ...args) this.$emit('addNewRelationTab', ...args)
@ -743,6 +756,10 @@ tbody tr:hover {
.cell { .cell {
font-size: 13px; font-size: 13px;
&.required {
box-shadow: inset 0 0 0 1px red;
}
} }
th::before { th::before {

6
packages/nc-gui/config/vuetify.options.js

@ -18,13 +18,17 @@ export default function ({app}) {
primary: '#0989ff', primary: '#0989ff',
'x-active': '#e91e63', 'x-active': '#e91e63',
textColor: '#ffffff', textColor: '#ffffff',
backgroundColor: '#363636', text: '#ffffff',
textLight: '#b3b3b3',
backgroundColor: '#969696',
backgroundColorDefault: '#1f1f1f' backgroundColorDefault: '#1f1f1f'
}, },
light: { light: {
primary: '#0989ff', primary: '#0989ff',
'x-active': '#e91e63', 'x-active': '#e91e63',
textColor: '#333333', textColor: '#333333',
text: '#333333',
textLight: '#929292',
backgroundColor: '#f7f7f7', backgroundColor: '#f7f7f7',
backgroundColorDefault: '#ffffff', backgroundColorDefault: '#ffffff',
} }

7
packages/nocodb/src/lib/noco/common/BaseApiBuilder.ts

@ -1551,7 +1551,12 @@ export default abstract class BaseApiBuilder<T extends Noco> implements XcDynami
// Update metadata of tables which have manytomany relation // Update metadata of tables which have manytomany relation
// and recreate basemodel with new meta information // and recreate basemodel with new meta information
for (const meta of metas) { for (const meta of metas) {
meta.v = [...meta.v, ...meta.manyToMany.map(mm => ({mm, _cn:`${mm._tn} <=> ${mm._rtn}`}))] meta.v = [
...meta.v.filter(vc => vc.bt && meta.manyToMany.some(mm => vc.bt.rtn === mm.vtn)),
...meta.manyToMany.map(mm => ({
mm,
_cn: `${mm._tn} <=> ${mm._rtn}`
}))]
await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', { await this.xcMeta.metaUpdate(this.projectId, this.dbAlias, 'nc_models', {
meta: JSON.stringify(meta) meta: JSON.stringify(meta)
}, {title: meta.tn}) }, {title: meta.tn})

Loading…
Cancel
Save